build and develop commands done

This commit is contained in:
Kovid Goyal 2009-09-06 19:45:40 -06:00
parent b262a4bfe5
commit df9284b30d
20 changed files with 1326 additions and 815 deletions

View File

@ -13,6 +13,7 @@ src/calibre/manual/cli/
build build
dist dist
docs docs
resources
nbproject/ nbproject/
src/calibre/gui2/pictureflow/Makefile.Debug src/calibre/gui2/pictureflow/Makefile.Debug
src/calibre/gui2/pictureflow/Makefile.Release src/calibre/gui2/pictureflow/Makefile.Release

34
INSTALL Normal file
View File

@ -0,0 +1,34 @@
calibre supports installation from source only on Linux. On Windows and OS X use the provided installers and usethe facilities provided by calibre-debug to hack on the calibre source.
On Linux, there are two kinds of installation from source possible. Note that both kinds require lots of dependencies as well as a full development environment (compilers, headers files, etc.)
All installation related functions are accessed by the command::
python setup.py
Install
==========
The first type of install will actually "install" calibre to your computer by putting its files into the system in the following locations:
- Binaries (actually python wrapper scripts) in <prefix>/bin
- Python and C modules in <prefix>/lib/calibre
- Resources like icons, etc. in <prefix>/share/calibre
This type of install can be run by the command::
sudo python setup.py install
<prefix> is normally the installation prefix of python, usually /usr. It can be controlled by the --prefix
option.
Develop
=============
This type of install is designed to let you run calibre from your home directory, making it easy to hack on it. It will only install binaries into /usr/bin, but all the actual code and resource files will be read from the calibre source tree in your home drectory (or wherever you choose to put it).
This type of install can be run with the command::
sudo python setup.py develop

17
README Normal file
View File

@ -0,0 +1,17 @@
calibre is an e-book library manager. It can view, convert and catalog e-books \
in most of the major e-book formats. It can also talk to e-book reader \
devices. It can go out to the internet and fetch metadata for your books. \
It can download newspapers and convert them into e-books for convenient \
reading. It is cross platform, running on Linux, Windows and OS X.
For screenshots: https://calibre.kovidgoyal.net/wiki/Screenshots
For installation/usage instructions please see
http://calibre.kovidgoyal.net
For source code access:
bzr branch lp:calibre
To update your copy of the source code:
bzr merge

View File

@ -1,279 +0,0 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Build PyQt extensions. Integrates with distutils (but uses the PyQt build system).
'''
from distutils.core import Extension as _Extension
from distutils.command.build_ext import build_ext as _build_ext
from distutils.dep_util import newer_group
from distutils import log
from distutils.spawn import find_executable
import sipconfig, os, sys, string, glob, shutil
from PyQt4 import pyqtconfig
iswindows = 'win32' in sys.platform
isosx = 'darwin' in sys.platform
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
if find_executable('qmake-qt4'):
QMAKE = find_executable('qmake-qt4')
elif find_executable('qmake'):
QMAKE = find_executable('qmake')
QMAKE = os.environ.get('QMAKE', QMAKE)
WINDOWS_PYTHON = ['C:/Python26/libs']
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
if not os.path.exists(OSX_SDK):
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
leopard_build = '10.5' in OSX_SDK
def replace_suffix(path, new_suffix):
return os.path.splitext(path)[0] + new_suffix
class Extension(_Extension):
def __init__(self, *args, **kwargs):
if leopard_build:
prev = kwargs.get('extra_compile_args', [])
prev.extend(['-arch', 'ppc64', '-arch', 'x86_64'])
kwargs['extra_compile_args'] = prev
_Extension.__init__(self, *args, **kwargs)
if iswindows:
from distutils import msvc9compiler
msvc = msvc9compiler.MSVCCompiler()
msvc.initialize()
nmake = msvc.find_exe('nmake.exe')
rc = msvc.find_exe('rc.exe')
class PyQtExtension(Extension):
def __init__(self, name, sources, sip_sources, **kw):
'''
:param sources: Qt .cpp and .h files needed for this extension
:param sip_sources: List of .sip files this extension depends on. The
first .sip file will be used toactually build the extension.
'''
self.module_makefile = pyqtconfig.QtGuiModuleMakefile
self.sip_sources = map(lambda x: x.replace('/', os.sep), sip_sources)
Extension.__init__(self, name, sources, **kw)
class build_ext(_build_ext):
def make(self, makefile):
make = nmake if iswindows else 'make'
self.spawn([make, '-f', makefile])
def build_qt_objects(self, ext, bdir):
if not iswindows:
bdir = os.path.join(bdir, 'qt')
if not os.path.exists(bdir):
os.makedirs(bdir)
cwd = os.getcwd()
sources = map(os.path.abspath, ext.sources)
os.chdir(bdir)
archs = 'x86_64 ppc64' if leopard_build else 'x86 ppc'
try:
headers = set([f for f in sources if f.endswith('.h')])
sources = set(sources) - headers
name = ext.name.rpartition('.')[-1]
pro = '''\
TARGET = %s
TEMPLATE = lib
HEADERS = %s
SOURCES = %s
VERSION = 1.0.0
CONFIG += %s
'''%(name, ' '.join(headers), ' '.join(sources), archs)
open(name+'.pro', 'wb').write(pro)
self.spawn([QMAKE, '-o', 'Makefile.qt', name+'.pro'])
if leopard_build:
raw = open('Makefile.qt', 'rb').read()
open('Makefile.qt', 'wb').write(raw.replace('ppc64', 'x86_64'))
self.make('Makefile.qt')
pat = 'release\\*.obj' if iswindows else '*.o'
return map(os.path.abspath, glob.glob(pat))
finally:
os.chdir(cwd)
def build_sbf(self, sip, sbf, bdir):
print '\tBuilding sbf...'
sip_bin = self.sipcfg.sip_bin
pyqt_sip_flags = []
if hasattr(self, 'pyqtcfg'):
pyqt_sip_flags += ['-I', self.pyqtcfg.pyqt_sip_dir]
pyqt_sip_flags += self.pyqtcfg.pyqt_sip_flags.split()
self.spawn([sip_bin,
"-c", bdir,
"-b", sbf,
] + pyqt_sip_flags +
[sip])
def build_pyqt(self, bdir, sbf, ext, qtobjs, headers):
makefile = ext.module_makefile(configuration=self.pyqtcfg,
build_file=sbf, dir=bdir,
makefile='Makefile.pyqt',
universal=OSX_SDK, qt=1)
makefile.extra_libs = ext.libraries
makefile.extra_lib_dirs = ext.library_dirs
makefile.extra_cxxflags = ext.extra_compile_args
if 'win32' in sys.platform:
makefile.extra_lib_dirs += WINDOWS_PYTHON
makefile.extra_include_dirs = list(set(map(os.path.dirname, headers)))
makefile.extra_include_dirs += ext.include_dirs
makefile.extra_lflags += qtobjs
makefile.generate()
cwd = os.getcwd()
os.chdir(bdir)
if leopard_build:
mf = 'Makefile.pyqt'
raw = open(mf, 'rb').read()
raw = raw.replace('ppc64 x86_64', 'x86_64')
for x in ('ppc64', 'ppc', 'i386'):
raw = raw.replace(x, 'x86_64')
open(mf, 'wb').write(raw)
try:
self.make('Makefile.pyqt')
finally:
os.chdir(cwd)
def build_extension(self, ext):
self.inplace = True # Causes extensions to be built in the source tree
fullname = self.get_ext_fullname(ext.name)
if self.inplace:
# ignore build-lib -- put the compiled extension into
# the source tree along with pure Python modules
modpath = string.split(fullname, '.')
package = string.join(modpath[0:-1], '.')
base = modpath[-1]
build_py = self.get_finalized_command('build_py')
package_dir = build_py.get_package_dir(package)
ext_filename = os.path.join(package_dir,
self.get_ext_filename(base))
else:
ext_filename = os.path.join(self.build_lib,
self.get_ext_filename(fullname))
bdir = os.path.abspath(os.path.join(self.build_temp, fullname))
if not os.path.exists(bdir):
os.makedirs(bdir)
if not isinstance(ext, PyQtExtension):
if not iswindows:
return _build_ext.build_extension(self, ext)
c_sources = [f for f in ext.sources if os.path.splitext(f)[1].lower() in ('.c', '.cpp', '.cxx')]
compile_args = '/c /nologo /Ox /MD /W3 /EHsc /DNDEBUG'.split()
compile_args += ext.extra_compile_args
self.swig_opts = ''
inc_dirs = self.include_dirs + [x.replace('/', '\\') for x in ext.include_dirs]
cc = [msvc.cc] + compile_args + ['-I%s'%x for x in list(set(inc_dirs))]
objects = []
for f in c_sources:
o = os.path.join(bdir, os.path.basename(f)+'.obj')
objects.append(o)
inf = '/Tp' if f.endswith('.cpp') else '/Tc'
compiler = cc + [inf+f, '/Fo'+o]
self.spawn(compiler)
out = os.path.join(bdir, base+'.pyd')
linker = [msvc.linker] + '/DLL /nologo /INCREMENTAL:NO'.split()
linker += ['/LIBPATH:'+x for x in self.library_dirs+ext.library_dirs]
linker += [x+'.lib' for x in ext.libraries]
linker += ['/EXPORT:init'+base] + objects + ['/OUT:'+out]
self.spawn(linker)
for src in (out, out+'.manifest'):
shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src)))
return
if not os.path.exists(bdir):
os.makedirs(bdir)
ext.sources2 = map(os.path.abspath, ext.sources)
qt_dir = 'qt\\release' if iswindows else 'qt'
objects = set(map(lambda x: os.path.join(bdir, qt_dir, replace_suffix(os.path.basename(x), '.o')),
[s for s in ext.sources2 if not s.endswith('.h')]))
newer = False
for object in objects:
if newer_group(ext.sources2, object, missing='newer'):
newer = True
break
headers = [f for f in ext.sources2 if f.endswith('.h')]
if self.force or newer:
log.info('building \'%s\' extension', ext.name)
objects = self.build_qt_objects(ext, bdir)
self.sipcfg = sipconfig.Configuration()
self.pyqtcfg = pyqtconfig.Configuration()
sbf_sources = []
for sip in ext.sip_sources:
sipbasename = os.path.basename(sip)
sbf = os.path.join(bdir, replace_suffix(sipbasename, ".sbf"))
sbf_sources.append(sbf)
if self.force or newer_group(ext.sip_sources, sbf, 'newer'):
self.build_sbf(sip, sbf, bdir)
generated_sources = []
for sbf in sbf_sources:
generated_sources += self.get_sip_output_list(sbf, bdir)
depends = generated_sources + list(objects)
mod = os.path.join(bdir, os.path.basename(ext_filename))
if self.force or newer_group(depends, mod, 'newer'):
self.build_pyqt(bdir, sbf_sources[0], ext, list(objects), headers)
if self.force or newer_group([mod], ext_filename, 'newer'):
if os.path.exists(ext_filename):
os.unlink(ext_filename)
shutil.copyfile(mod, ext_filename)
shutil.copymode(mod, ext_filename)
if self.force or newer_group([mod], ext_filename, 'newer'):
if os.path.exists(ext_filename):
os.unlink(ext_filename)
shutil.copyfile(mod, ext_filename)
shutil.copymode(mod, ext_filename)
def get_sip_output_list(self, sbf, bdir):
"""
Parse the sbf file specified to extract the name of the generated source
files. Make them absolute assuming they reside in the temp directory.
"""
for L in file(sbf):
key, value = L.split("=", 1)
if key.strip() == "sources":
out = []
for o in value.split():
out.append(os.path.join(bdir, o))
return out
raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf
def run_sip(self, sip_files):
sip_bin = self.sipcfg.sip_bin
sip_sources = [i[0] for i in sip_files]
generated_sources = []
for sip, sbf in sip_files:
if not (self.force or newer_group(sip_sources, sbf, 'newer')):
log.info(sbf + ' is up to date')
continue
self.spawn([sip_bin,
"-c", self.build_temp,
"-b", sbf,
'-I', self.pyqtcfg.pyqt_sip_dir,
] + self.pyqtcfg.pyqt_sip_flags.split()+
[sip])
generated_sources += self.get_sip_output_list(sbf)
return generated_sources

View File

@ -1,5 +1,5 @@
" Project wide builtins " Project wide builtins
let g:pyflakes_builtins += ["dynamic_property", "__"] let g:pyflakes_builtins += ["dynamic_property", "__", "P"]
python << EOFPY python << EOFPY
import os import os

340
setup.py
View File

@ -1,298 +1,64 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, re, os, subprocess
sys.path.append('src')
iswindows = re.search('win(32|64)', sys.platform)
isosx = 'darwin' in sys.platform
islinux = not isosx and not iswindows
src = open('src/calibre/constants.py', 'rb').read()
VERSION = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
print 'Setup', APPNAME, 'version:', VERSION
epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).search(open('src/%s/linux.py'%APPNAME, 'rb').read()).group(1)
entry_points = eval(epsrc, {'__appname__': APPNAME})
def _ep_to_script(ep, base='src'):
return (base+os.path.sep+re.search(r'.*=\s*(.*?):', ep).group(1).replace('.', '/')+'.py').strip()
scripts = { import sys, os, optparse
'console' : [_ep_to_script(i) for i in entry_points['console_scripts']],
'gui' : [_ep_to_script(i) for i in entry_points['gui_scripts']],
}
def _ep_to_basename(ep): sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
return re.search(r'\s*(.*?)\s*=', ep).group(1).strip()
basenames = {
'console' : [_ep_to_basename(i) for i in entry_points['console_scripts']],
'gui' : [_ep_to_basename(i) for i in entry_points['gui_scripts']],
}
def _ep_to_module(ep): import setup.commands as commands
return re.search(r'.*=\s*(.*?)\s*:', ep).group(1).strip() from setup import prints, get_warnings
main_modules = {
'console' : [_ep_to_module(i) for i in entry_points['console_scripts']],
'gui' : [_ep_to_module(i) for i in entry_points['gui_scripts']],
}
def _ep_to_function(ep): def check_version_info():
return ep[ep.rindex(':')+1:].strip() vi = sys.version_info
main_functions = { if vi[0] == 2 and vi[1] > 5:
'console' : [_ep_to_function(i) for i in entry_points['console_scripts']], return None
'gui' : [_ep_to_function(i) for i in entry_points['gui_scripts']], return 'calibre requires python >= 2.6'
}
def setup_mount_helper(): def option_parser():
def warn(): parser = optparse.OptionParser()
print 'WARNING: Failed to compile mount helper. Auto mounting of', return parser
print 'devices will not work'
if os.geteuid() != 0: def main(args=sys.argv):
return warn() if len(args) == 1 or args[1] in ('-h', '--help'):
import stat print 'Usage: python', args[0], 'command', '[options]'
src = os.path.join('src', 'calibre', 'devices', 'linux_mount_helper.c') print '\nWhere command is one of:', ', '.join(commands.__all__)
dest = '/usr/bin/calibre-mount-helper' print '\nTo get help on a particular command, run:'
p = subprocess.Popen(['gcc', '-Wall', src, '-o', dest]) print '\tpython', args[0], 'command -h'
ret = p.wait() return 1
if ret != 0:
return warn() command = args[1]
os.chown(dest, 0, 0) if command not in commands.__all__:
os.chmod(dest, print command, 'is not a recognized command.'
stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) print 'Valid commands:', ', '.join(commands.__all__)
return 1
command = getattr(commands, command)
parser = option_parser()
command.add_all_options(parser)
lines = parser.usage.splitlines()
lines[0] = 'Usage: python setup.py %s [options]'%args[1]
parser.set_usage('\n'.join(lines))
opts, args = parser.parse_args(args)
command.run_all(opts)
warnings = get_warnings()
if warnings:
print
prints('There were', len(warnings), 'warning(s):')
print
for args, kwargs in warnings:
prints(*args, **kwargs)
print
return 0
if __name__ == '__main__': if __name__ == '__main__':
from setuptools import setup, find_packages sys.exit(main())
from pyqtdistutils import PyQtExtension, build_ext, Extension, QMAKE
from upload import sdist, pot, build, build_py, manual, \
resources, clean, gui, translations, update, \
tag_release, upload_demo, build_linux, build_windows, \
build_osx, upload_installers, upload_user_manual, \
upload_to_pypi, stage3, stage2, stage1, upload, \
upload_rss, betas, build_linux32, build_linux64, \
build_osx64, get_translations
resources.SCRIPTS = {}
for x in ('console', 'gui'):
for name in basenames[x]:
resources.SCRIPTS[name] = x
list(basenames['console']+basenames['gui'])
entry_points['console_scripts'].append(
'calibre_postinstall = calibre.linux:post_install')
optional = []
def qmake_query(arg=''):
cmd = [QMAKE, '-query']
if arg:
cmd += [arg]
return subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout.read()
qt_inc = qt_lib = None
qt_inc = qmake_query('QT_INSTALL_HEADERS').splitlines()[0]
qt_inc = qt_inc if qt_inc not in ('', '**Unknown**') and os.path.isdir(qt_inc) else None
qt_lib = qmake_query('QT_INSTALL_LIBS').splitlines()[0]
qt_lib = qt_lib if qt_lib not in ('', '**Unknown**') and os.path.isdir(qt_lib) else None
if qt_lib is None or qt_inc is None:
print '\n\nWARNING: Could not find QT librariers and headers.',
print 'Is qmake in your PATH?\n\n'
if iswindows:
optional.append(Extension('calibre.plugins.winutil',
sources=['src/calibre/utils/windows/winutil.c'],
libraries=['shell32', 'setupapi'],
include_dirs=os.environ.get('INCLUDE',
'C:/WinDDK/6001.18001/inc/api/;'
'C:/WinDDK/6001.18001/inc/crt/').split(';'),
extra_compile_args=['/X']
))
poppler_inc = '/usr/include/poppler/qt4'
poppler_lib = '/usr/lib'
poppler_libs = []
if iswindows:
poppler_inc = r'C:\cygwin\home\kovid\poppler\include\poppler\qt4'
poppler_lib = r'C:\cygwin\home\kovid\poppler\lib'
poppler_libs = ['QtCore4', 'QtGui4']
if isosx:
poppler_inc = '/Volumes/sw/build/poppler-0.10.7/qt4/src'
poppler_lib = '/Users/kovid/poppler/lib'
poppler_inc = os.environ.get('POPPLER_INC_DIR', poppler_inc)
if os.path.exists(os.path.join(poppler_inc, 'poppler-qt4.h'))\
and qt_lib is not None and qt_inc is not None:
optional.append(Extension('calibre.plugins.calibre_poppler',
sources=['src/calibre/utils/poppler/poppler.cpp'],
libraries=(['poppler', 'poppler-qt4']+poppler_libs),
library_dirs=[os.environ.get('POPPLER_LIB_DIR',
poppler_lib), qt_lib],
include_dirs=[poppler_inc, qt_inc]))
else:
print '\n\nWARNING: Poppler not found on your system. Various PDF related',
print 'functionality will not work. Use the POPPLER_INC_DIR and',
print 'POPPLER_LIB_DIR environment variables.\n\n'
podofo_inc = '/usr/include/podofo' if islinux else \
'C:\\podofo\\include\\podofo' if iswindows else \
'/usr/local/include/podofo'
podofo_lib = '/usr/lib' if islinux else r'C:\podofo' if iswindows else \
'/usr/local/lib'
podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc)
if os.path.exists(os.path.join(podofo_inc, 'podofo.h')):
optional.append(Extension('calibre.plugins.podofo',
sources=['src/calibre/utils/podofo/podofo.cpp'],
libraries=['podofo'],
library_dirs=[os.environ.get('PODOFO_LIB_DIR', podofo_lib)],
include_dirs=[podofo_inc]))
else:
print '\n\nWARNING: PoDoFo not found on your system. Various PDF related',
print 'functionality will not work. Use the PODOFO_INC_DIR and',
print 'PODOFO_LIB_DIR environment variables.\n\n'
fc_inc = '/usr/include/fontconfig' if islinux else \
r'C:\cygwin\home\kovid\fontconfig\include\fontconfig' if iswindows else \
'/Users/kovid/fontconfig/include/fontconfig'
fc_lib = '/usr/lib' if islinux else \
r'C:\cygwin\home\kovid\fontconfig\lib' if iswindows else \
'/Users/kovid/fontconfig/lib'
fc_inc = os.environ.get('FC_INC_DIR', fc_inc)
fc_lib = os.environ.get('FC_LIB_DIR', fc_lib)
if not os.path.exists(os.path.join(fc_inc, 'fontconfig.h')):
print '\n\nERROR: fontconfig not found on your system.',
print 'Use the FC_INC_DIR and FC_LIB_DIR environment variables.\n\n'
raise SystemExit(1)
ext_modules = optional + [
Extension('calibre.plugins.fontconfig',
sources = ['src/calibre/utils/fonts/fontconfig.c'],
include_dirs = [fc_inc],
libraries=['fontconfig'],
library_dirs=[fc_lib]),
Extension('calibre.plugins.lzx',
sources=['src/calibre/utils/lzx/lzxmodule.c',
'src/calibre/utils/lzx/compressor.c',
'src/calibre/utils/lzx/lzxd.c',
'src/calibre/utils/lzx/lzc.c',
'src/calibre/utils/lzx/lzxc.c'],
include_dirs=['src/calibre/utils/lzx']),
Extension('calibre.plugins.msdes',
sources=['src/calibre/utils/msdes/msdesmodule.c',
'src/calibre/utils/msdes/des.c'],
include_dirs=['src/calibre/utils/msdes']),
Extension('calibre.plugins.cPalmdoc',
sources=['src/calibre/ebooks/compression/palmdoc.c']),
PyQtExtension('calibre.plugins.pictureflow',
['src/calibre/gui2/pictureflow/pictureflow.cpp',
'src/calibre/gui2/pictureflow/pictureflow.h'],
['src/calibre/gui2/pictureflow/pictureflow.sip']
)
]
if isosx:
ext_modules.append(Extension('calibre.plugins.usbobserver',
sources=['src/calibre/devices/usbobserver/usbobserver.c'],
extra_link_args=['-framework', 'IOKit'])
)
if not iswindows:
plugins = ['plugins/%s.so'%(x.name.rpartition('.')[-1]) for x in ext_modules]
else:
plugins = ['plugins/%s.pyd'%(x.name.rpartition('.')[-1]) for x in ext_modules] + \
['plugins/%s.pyd.manifest'%(x.name.rpartition('.')[-1]) \
for x in ext_modules if 'pictureflow' not in x.name]
setup(
name = APPNAME,
packages = find_packages('src'),
package_dir = { '' : 'src' },
version = VERSION,
author = 'Kovid Goyal',
author_email = 'kovid@kovidgoyal.net',
url = 'http://%s.kovidgoyal.net'%APPNAME,
package_data = {'calibre':plugins},
entry_points = entry_points,
zip_safe = False,
options = { 'bdist_egg' : {'exclude_source_files': True,}, },
ext_modules = ext_modules,
description =
'''
E-book management application.
''',
long_description =
'''
%s is an e-book library manager. It can view, convert and catalog e-books \
in most of the major e-book formats. It can also talk to e-book reader \
devices. It can go out to the internet and fetch metadata for your books. \
It can download newspapers and convert them into e-books for convenient \
reading. It is cross platform, running on Linux, Windows and OS X.
For screenshots: https://%s.kovidgoyal.net/wiki/Screenshots
For installation/usage instructions please see
http://%s.kovidgoyal.net
For source code access:
bzr branch lp:%s
To update your copy of the source code:
bzr merge
'''%(APPNAME, APPNAME, APPNAME, APPNAME),
license = 'GPL',
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: X11 Applications :: Qt',
'Intended Audience :: Developers',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Hardware :: Hardware Drivers'
],
cmdclass = {
'build_ext' : build_ext,
'build' : build,
'build_py' : build_py,
'pot' : pot,
'manual' : manual,
'resources' : resources,
'translations' : translations,
'get_translations': get_translations,
'gui' : gui,
'clean' : clean,
'sdist' : sdist,
'update' : update,
'tag_release' : tag_release,
'upload_demo' : upload_demo,
'build_linux' : build_linux,
'build_linux32' : build_linux32,
'build_linux64' : build_linux64,
'build_windows' : build_windows,
'build_osx' : build_osx,
'build_osx64' : build_osx64,
'upload_installers': upload_installers,
'upload_user_manual': upload_user_manual,
'upload_to_pypi': upload_to_pypi,
'upload_rss' : upload_rss,
'stage3' : stage3,
'stage2' : stage2,
'stage1' : stage1,
'publish' : upload,
'betas' : betas,
},
)
if 'develop' in ' '.join(sys.argv) and islinux:
subprocess.check_call('calibre_postinstall --do-not-reload-udev-hal', shell=True)
setup_mount_helper()
if 'install' in sys.argv and islinux:
subprocess.check_call('calibre_postinstall', shell=True)
setup_mount_helper()

193
setup/__init__.py Normal file
View File

@ -0,0 +1,193 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, re, os
iswindows = re.search('win(32|64)', sys.platform)
isosx = 'darwin' in sys.platform
islinux = not isosx and not iswindows
SRC = os.path.abspath('src')
__version__ = __appname__ = modules = functions = basenames = scripts = None
def initialize_constants():
global __version__, __appname__, modules, functions, basenames, scripts
src = open('src/calibre/constants.py', 'rb').read()
__version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
__appname__ = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).\
search(open('src/calibre/linux.py', 'rb').read()).group(1)
entry_points = eval(epsrc, {'__appname__': __appname__})
def e2b(ep):
return re.search(r'\s*(.*?)\s*=', ep).group(1).strip()
def e2s(ep, base='src'):
return (base+os.path.sep+re.search(r'.*=\s*(.*?):', ep).group(1).replace('.', '/')+'.py').strip()
def e2m(ep):
return re.search(r'.*=\s*(.*?)\s*:', ep).group(1).strip()
def e2f(ep):
return ep[ep.rindex(':')+1:].strip()
basenames, functions, modules, scripts = {}, {}, {}, {}
for x in ('console', 'gui'):
y = x + '_scripts'
basenames[x] = list(map(e2b, entry_points[y]))
functions[x] = list(map(e2f, entry_points[y]))
modules[x] = list(map(e2m, entry_points[y]))
scripts[x] = list(map(e2s, entry_points[y]))
initialize_constants()
preferred_encoding = 'utf-8'
def prints(*args, **kwargs):
'''
Print unicode arguments safely by encoding them to preferred_encoding
Has the same signature as the print function from Python 3, except for the
additional keyword argument safe_encode, which if set to True will cause the
function to use repr when encoding fails.
'''
file = kwargs.get('file', sys.stdout)
sep = kwargs.get('sep', ' ')
end = kwargs.get('end', '\n')
enc = preferred_encoding
safe_encode = kwargs.get('safe_encode', False)
for i, arg in enumerate(args):
if isinstance(arg, unicode):
try:
arg = arg.encode(enc)
except UnicodeEncodeError:
if not safe_encode:
raise
arg = repr(arg)
if not isinstance(arg, str):
try:
arg = str(arg)
except ValueError:
arg = unicode(arg)
if isinstance(arg, unicode):
try:
arg = arg.encode(enc)
except UnicodeEncodeError:
if not safe_encode:
raise
arg = repr(arg)
file.write(arg)
if i != len(args)-1:
file.write(sep)
file.write(end)
warnings = []
def get_warnings():
return list(warnings)
class Command(object):
SRC = SRC
RESOURCES = os.path.join(os.path.dirname(SRC), 'resources')
description = ''
sub_commands = []
def __init__(self):
self.d = os.path.dirname
self.j = os.path.join
self.a = os.path.abspath
self.b = os.path.basename
self.s = os.path.splitext
self.e = os.path.exists
self.real_uid = os.environ.get('SUDO_UID', None)
self.real_gid = os.environ.get('SUDO_GID', None)
self.real_user = os.environ.get('SUDO_USER', None)
def drop_privileges(self):
if not islinux or isosx:
return
if self.real_user is not None:
self.info('Dropping privileges to those of', self.real_user+':',
self.real_uid)
if self.real_uid is not None:
os.seteuid(int(self.real_uid))
#if self.real_gid is not None:
# os.setegid(int(self.real_gid))
def regain_privileges(self):
if not islinux or isosx:
return
if os.geteuid() != 0:
self.info('Trying to get root privileges')
os.seteuid(0)
#if os.getegid() != 0:
# os.setegid(0)
def pre_sub_commands(self, opts):
pass
def run_all(self, opts):
self.pre_sub_commands(opts)
for cmd in self.sub_commands:
self.info('Running', cmd.__class__.__name__)
cmd.run(opts)
self.info('Running', self.__class__.__name__)
self.run(opts)
def add_all_options(self, parser):
import setup.commands as commands
self.sub_commands = [getattr(commands, cmd) for cmd in
self.sub_commands]
for cmd in self.sub_commands:
cmd.add_options(parser)
self.add_options(parser)
def run(self, opts):
raise NotImplementedError
def add_options(self, parser):
pass
def clean(self):
pass
@classmethod
def newer(cls, targets, sources):
'''
Return True if sources is newer that targets or if targets
does not exist.
'''
if isinstance(targets, basestring):
targets = [targets]
if isinstance(sources, basestring):
sources = [sources]
for f in targets:
if not os.path.exists(f):
return True
ttimes = map(lambda x: os.stat(x).st_mtime, targets)
stimes = map(lambda x: os.stat(x).st_mtime, sources)
newest_source, oldest_target = max(stimes), min(ttimes)
return newest_source > oldest_target
def info(self, *args, **kwargs):
prints(*args, **kwargs)
sys.stdout.flush()
def warn(self, *args, **kwargs):
print '\n'+'_'*20, 'WARNING','_'*20
prints(*args, **kwargs)
print '_'*50
warnings.append((args, kwargs))
sys.stdout.flush()

101
setup/build_environment.py Normal file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
from distutils.spawn import find_executable
from PyQt4 import pyqtconfig
from setup import isosx, iswindows
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
if not os.path.exists(OSX_SDK):
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
leopard_build = '10.5' in OSX_SDK
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.5' if leopard_build else '10.4'
NMAKE = RC = msvc = MT = win_inc = win_lib = None
if iswindows:
from distutils import msvc9compiler
msvc = msvc9compiler.MSVCCompiler()
msvc.initialize()
NMAKE = msvc.find_exe('nmake.exe')
RC = msvc.find_exe('rc.exe')
SDK = os.environ.get('WINSDK', r'C:\Program Files\Microsoft SDKs\Windows\v6.0A')
win_inc = os.environ['include'].split(';')
win_lib = os.environ['lib'].split(';')
for p in win_inc:
if 'SDK' in p:
MT = os.path.join(os.path.dirname(p), 'bin', 'mt.exe')
MT = os.path.join(SDK, 'bin', 'mt.exe')
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
if find_executable('qmake-qt4'):
QMAKE = find_executable('qmake-qt4')
elif find_executable('qmake'):
QMAKE = find_executable('qmake')
QMAKE = os.environ.get('QMAKE', QMAKE)
pyqt = pyqtconfig.Configuration()
qt_inc = pyqt.qt_inc_dir
qt_lib = pyqt.qt_lib_dir
fc_inc = '/usr/include/fontconfig'
fc_lib = '/usr/lib'
poppler_inc = '/usr/include/poppler/qt4'
poppler_lib = '/usr/lib'
poppler_libs = []
podofo_inc = '/usr/include/podofo'
podofo_lib = '/usr/lib'
if iswindows:
fc_inc = r'C:\cygwin\home\kovid\fontconfig\include\fontconfig'
fc_lib = r'C:\cygwin\home\kovid\fontconfig\lib'
poppler_inc = r'C:\cygwin\home\kovid\poppler\include\poppler\qt4'
poppler_lib = r'C:\cygwin\home\kovid\poppler\lib'
poppler_libs = ['QtCore4', 'QtGui4']
podofo_inc = 'C:\\podofo\\include\\podofo'
podofo_lib = r'C:\podofo'
if isosx:
fc_inc = '/Users/kovid/fontconfig/include/fontconfig'
fc_lib = '/Users/kovid/fontconfig/lib'
poppler_inc = '/Volumes/sw/build/poppler-0.10.7/qt4/src'
poppler_lib = '/Users/kovid/poppler/lib'
podofo_inc = '/usr/local/include/podofo'
podofo_lib = '/usr/local/lib'
fc_inc = os.environ.get('FC_INC_DIR', fc_inc)
fc_lib = os.environ.get('FC_LIB_DIR', fc_lib)
fc_error = None if os.path.exists(os.path.join(fc_inc, 'fontconfig.h')) else \
('fontconfig header files not found on your system. '
'Try setting the FC_INC_DIR and FC_LIB_DIR environment '
'variables.')
poppler_inc = os.environ.get('POPPLER_INC_DIR', poppler_inc)
poppler_lib = os.environ.get('POPPLER_LIB_DIR', poppler_lib)
poppler_error = None if os.path.exists(os.path.join(poppler_inc,
'poppler-qt4.h')) else \
('Poppler not found on your system. Various PDF related',
' functionality will not work. Use the POPPLER_INC_DIR and',
' POPPLER_LIB_DIR environment variables.')
podofo_lib = os.environ.get('PODOFO_LIB_DIR', podofo_lib)
podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc)
podofo_error = None if os.path.exists(os.path.join(podofo_inc, 'podofo.h')) else \
('PoDoFo not found on your system. Various PDF related',
' functionality will not work. Use the PODOFO_INC_DIR and',
' PODOFO_LIB_DIR environment variables.')

71
setup/commands.py Normal file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
__all__ = [
'pot', 'translations', 'get_translations', 'iso639',
'build',
'develop',
'clean'
]
import os, shutil
from setup.translations import POT, GetTranslations, Translations, ISO639
from setup import Command
pot = POT()
translations = Translations()
get_translations = GetTranslations()
iso639 = ISO639()
from setup.extensions import Build
build = Build()
from setup.install import Develop
develop = Develop()
class Clean(Command):
description='''Delete all computer generated files in the source tree'''
sub_commands = __all__
def add_options(self, parser):
opt = parser.remove_option('--only')
help = 'Only run clean for the specified command. Choices: '+\
', '.join(__all__)
parser.add_option('-1', '--only', default='all',
choices=__all__+['all'], help=help)
def run_all(self, opts):
self.info('Cleaning...')
only = None if opts.only == 'all' else commands[opts.only]
for cmd in self.sub_commands:
if only is not None and only is not cmd:
continue
self.info('\tCleaning', command_names[cmd])
cmd.clean()
def clean(self):
for root, _, files in os.walk(self.d(self.SRC)):
for name in files:
for t in ('.pyc', '.pyo', '~', '.swp', '.swo'):
if name.endswith(t):
os.remove(os.path.join(root, name))
break
for dir in ('dist', os.path.join('src', 'calibre.egg-info')):
shutil.rmtree(dir, ignore_errors=True)
clean = Clean()
commands = {}
for x in __all__:
commands[x] = locals()[x]
command_names = dict(zip(commands.values(), commands.keys()))

348
setup/extensions.py Normal file
View File

@ -0,0 +1,348 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import textwrap, os, shlex, subprocess, glob, shutil
from distutils import sysconfig
from PyQt4.pyqtconfig import QtGuiModuleMakefile
from setup import Command, islinux, isosx, SRC, iswindows
from setup.build_environment import fc_inc, fc_lib, qt_inc, qt_lib, \
fc_error, poppler_libs, poppler_lib, poppler_inc, podofo_inc, \
podofo_lib, podofo_error, poppler_error, pyqt, OSX_SDK, NMAKE, \
leopard_build, QMAKE, msvc, MT, win_inc, win_lib
isunix = islinux or isosx
make = 'make' if isunix else NMAKE
class Extension(object):
def absolutize(self, paths):
return [x if os.path.isabs(x) else os.path.join(SRC, x.replace('/',
os.sep)) for x in paths]
def __init__(self, name, sources, **kwargs):
self.name = name
self.needs_cxx = bool([1 for x in sources if os.path.splitext(x)[1] in
('.cpp', '.c++', '.cxx')])
self.sources = self.absolutize(sources)
self.headers = self.absolutize(kwargs.get('headers', []))
self.sip_files = self.absolutize(kwargs.get('sip_files', []))
self.inc_dirs = self.absolutize(kwargs.get('inc_dirs', []))
self.lib_dirs = self.absolutize(kwargs.get('lib_dirs', []))
self.error = kwargs.get('error', None)
self.libraries = kwargs.get('libraries', [])
self.cflags = kwargs.get('cflags', [])
self.ldflags = kwargs.get('ldflags', [])
self.optional = kwargs.get('optional', False)
extensions = [
Extension('lzx',
['calibre/utils/lzx/lzxmodule.c',
'calibre/utils/lzx/compressor.c',
'calibre/utils/lzx/lzxd.c',
'calibre/utils/lzx/lzc.c',
'calibre/utils/lzx/lzxc.c'],
headers=['calibre/utils/lzx/msstdint.h',
'calibre/utils/lzx/lzc.h',
'calibre/utils/lzx/lzxmodule.h',
'calibre/utils/lzx/system.h',
'calibre/utils/lzx/lzxc.h',
'calibre/utils/lzx/lzxd.h',
'calibre/utils/lzx/mspack.h'],
inc_dirs=['calibre/utils/lzx']),
Extension('fontconfig',
['calibre/utils/fonts/fontconfig.c'],
inc_dirs = [fc_inc],
libraries=['fontconfig'],
lib_dirs=[fc_lib],
error=fc_error),
Extension('msdes',
['calibre/utils/msdes/msdesmodule.c',
'calibre/utils/msdes/des.c'],
headers=['calibre/utils/msdes/spr.h',
'calibre/utils/msdes/d3des.h'],
inc_dirs=['calibre/utils/msdes']),
Extension('cPalmdoc',
['calibre/ebooks/compression/palmdoc.c']),
Extension('calibre_poppler',
['calibre/utils/poppler/poppler.cpp'],
libraries=(['poppler', 'poppler-qt4']+poppler_libs),
lib_dirs=[os.environ.get('POPPLER_LIB_DIR',
poppler_lib), qt_lib],
inc_dirs=[poppler_inc, qt_inc],
error=poppler_error,
optional=True),
Extension('podofo',
['calibre/utils/podofo/podofo.cpp'],
libraries=['podofo'],
lib_dirs=[podofo_lib],
inc_dirs=[podofo_inc],
error=podofo_error),
Extension('pictureflow',
['calibre/gui2/pictureflow/pictureflow.cpp'],
inc_dirs = ['calibre/gui2/pictureflow'],
headers = ['calibre/gui2/pictureflow/pictureflow.h'],
sip_files = ['calibre/gui2/pictureflow/pictureflow.sip']
)
]
if iswindows:
extensions.append(Extension('winutil',
['calibre/utils/windows/winutil.c'],
libraries=['shell32', 'setupapi'],
include_dirs=os.environ.get('INCLUDE',
'C:/WinDDK/6001.18001/inc/api/;'
'C:/WinDDK/6001.18001/inc/crt/').split(';'),
cflags=['/X']
))
if isosx:
extensions.append(Extension('usbobserver',
['calibre/devices/usbobserver/usbobserver.c'],
ldflags=['-framework', 'IOKit'])
)
if isunix:
cc = os.environ.get('CC', 'gcc')
cxx = os.environ.get('CXX', 'g++')
cflags = '-O3 -Wall -DNDEBUG -fPIC -fno-strict-aliasing -pipe'.split()
ldflags = ['-Wall']
cflags += shlex.split(os.environ.get('CFLAGS', ''))
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
if islinux:
cflags.append('-pthread')
ldflags.append('-shared')
cflags.append('-I'+sysconfig.get_python_inc())
ldflags.append('-lpython'+sysconfig.get_python_version())
if isosx:
x, p = ('x86_64', 'ppc64') if leopard_build else ('i386', 'ppc')
archs = ['-arch', x, '-arch', p, '-isysroot',
OSX_SDK]
cflags.extend(archs)
ldflags.extend(archs)
ldflags.extend('-bundle -undefined dynamic_lookup'.split())
cflags.extend(['-fno-common', '-dynamic'])
cflags.append('-I'+sysconfig.get_python_inc())
if iswindows:
cc = cxx = msvc.cc
cflags = '/c /nologo /Ox /MD /W3 /EHsc /DNDEBUG'.split()
ldflags = '/DLL /nologo /INCREMENTAL:NO'.split()
for p in win_inc:
cflags.append('-I'+p)
for p in win_lib:
ldflags.append('/LIBPATH:'+p)
cflags.append('-I%s'%sysconfig.get_python_inc())
ldflags.append('/LIBPATH:'+os.path.join(sysconfig.PREFIX, 'libs'))
class Build(Command):
def add_options(self, parser):
parser.set_usage(parser.usage + textwrap.dedent('''
calibre depends on several python extensions written in C/C++.
This command will compile them. You can influence the compile
process by several environment variables, listed below:
CC - C Compiler defaults to gcc
CXX - C++ Compiler, defaults to g++
CFLAGS - Extra compiler flags
LDFLAGS - Extra linker flags
FC_INC_DIR - fontconfig header files
FC_LIB_DIR - fontconfig library
POPPLER_INC_DIR - poppler header files
POPPLER_LIB_DIR - poppler-qt4 library
PODOFO_INC_DIR - podofo header files
PODOFO_LIB_DIR - podofo library files
QMAKE - Path to qmake
VS90COMNTOOLS - Location of Microsoft Visual Studio 9 Tools
'''))
choices = [e.name for e in extensions]+['all']
parser.add_option('-1', '--only', choices=choices, default='all',
help=('Build only the named extension. Available: '+
', '.join(choices)+'. Default:%default'))
def run(self, opts):
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
if not os.path.exists(self.obj_dir):
os.makedirs(self.obj_dir)
for ext in extensions:
if opts.only != 'all' and opts.only != ext.name:
continue
if ext.error is not None:
if ext.optional:
self.warn(ext.error)
continue
else:
raise Exception(ext.error)
dest = self.dest(ext)
self.info('\n####### Building extension', ext.name, '#'*7)
self.build(ext, dest)
def dest(self, ext):
ex = '.pyd' if iswindows else '.so'
return os.path.join(SRC, 'calibre', 'plugins', ext.name)+ex
def inc_dirs_to_cflags(self, dirs):
return ['-I'+x for x in dirs]
def lib_dirs_to_ldflags(self, dirs):
pref = '/LIBPATH:' if iswindows else '-L'
return [pref+x for x in dirs]
def libraries_to_ldflags(self, dirs):
pref = '' if iswindows else '-l'
suff = '.lib' if iswindows else ''
return [pref+x+suff for x in dirs]
def build(self, ext, dest):
if ext.sip_files:
return self.build_pyqt_extension(ext, dest)
compiler = cxx if ext.needs_cxx else cc
linker = msvc.linker if iswindows else compiler
objects = []
einc = self.inc_dirs_to_cflags(ext.inc_dirs)
obj_dir = self.j(self.obj_dir, ext.name)
if not os.path.exists(obj_dir):
os.makedirs(obj_dir)
for src in ext.sources:
obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+'.o')
objects.append(obj)
if self.newer(obj, [src]+ext.headers):
inf = '/Tp' if src.endswith('.cpp') else '/Tc'
sinc = [inf+src] if iswindows else ['-c', src]
oinc = ['/Fo'+obj] if iswindows else ['-o', obj]
cmd = [compiler] + cflags + ext.cflags + einc + sinc + oinc
self.info(' '.join(cmd))
subprocess.check_call(cmd)
dest = self.dest(ext)
elib = self.lib_dirs_to_ldflags(ext.lib_dirs)
xlib = self.libraries_to_ldflags(ext.libraries)
if self.newer(dest, objects):
print 'Linking', ext.name
cmd = [linker]
if iswindows:
cmd += ldflags + ext.ldflags + elib + xlib + \
['/EXPORT:init'+ext.name] + objects + ['/OUT:'+dest]
else:
cmd += objects + ['-o', dest] + ldflags + ext.ldflags + elib + xlib
print ' '.join(cmd)
subprocess.check_call(cmd)
if iswindows:
manifest = dest+'.manifest'
cmd = [MT, '-manifest', manifest, '-outputresource:%s;2'%dest]
self.info(*cmd)
subprocess.check_call(cmd)
os.remove(manifest)
for x in ('.exp', '.lib'):
x = os.path.splitext(dest)[0]+x
if os.path.exists(x):
os.remove(x)
def build_qt_objects(self, ext):
obj_pat = 'release\\*.obj' if iswindows else '*.o'
objects = glob.glob(obj_pat)
if not objects or self.newer(objects, ext.sources+ext.headers):
archs = 'x86_64 ppc64' if leopard_build else 'x86 ppc'
pro = textwrap.dedent('''\
TARGET = %s
TEMPLATE = lib
HEADERS = %s
SOURCES = %s
VERSION = 1.0.0
CONFIG += %s
''')%(ext.name, ' '.join(ext.headers), ' '.join(ext.sources), archs)
open(ext.name+'.pro', 'wb').write(pro)
subprocess.check_call([QMAKE, '-o', 'Makefile', ext.name+'.pro'])
if leopard_build:
raw = open('Makefile', 'rb').read()
open('Makefile', 'wb').write(raw.replace('ppc64', 'x86_64'))
subprocess.check_call([make, '-f', 'Makefile'])
objects = glob.glob(obj_pat)
return list(map(self.a, objects))
def build_pyqt_extension(self, ext, dest):
pyqt_dir = self.j(self.d(self.SRC), 'build', 'pyqt')
src_dir = self.j(pyqt_dir, ext.name)
qt_dir = self.j(src_dir, 'qt')
if not self.e(qt_dir):
os.makedirs(qt_dir)
cwd = os.getcwd()
try:
os.chdir(qt_dir)
qt_objects = self.build_qt_objects(ext)
finally:
os.chdir(cwd)
sip_files = ext.sip_files
ext.sip_files = []
sipf = sip_files[0]
sbf = self.j(src_dir, self.b(sipf)+'.sbf')
if self.newer(sbf, [sipf]+ext.headers):
exe = '.exe' if iswindows else ''
cmd = [pyqt.sip_bin+exe, '-w', '-c', src_dir, '-b', sbf, '-I'+\
pyqt.pyqt_sip_dir] + shlex.split(pyqt.pyqt_sip_flags) + [sipf]
self.info(' '.join(cmd))
subprocess.check_call(cmd)
module = self.j(src_dir, self.b(dest))
if self.newer(dest, [sbf]+qt_objects):
mf = self.j(src_dir, 'Makefile')
makefile = QtGuiModuleMakefile(configuration=pyqt, build_file=sbf,
makefile=mf, universal=OSX_SDK, qt=1)
makefile.extra_lflags = qt_objects
makefile.extra_include_dirs = ext.inc_dirs
makefile.generate()
if leopard_build:
raw = open(mf, 'rb').read()
raw = raw.replace('ppc64 x86_64', 'x86_64')
for x in ('ppc64', 'ppc', 'i386'):
raw = raw.replace(x, 'x86_64')
open(mf, 'wb').write(raw)
subprocess.check_call([make, '-f', mf], cwd=src_dir)
shutil.copy2(module, dest)
def clean(self):
for ext in extensions:
dest = self.dest(ext)
for x in (dest, dest+'.manifest'):
if os.path.exists(x):
os.remove(x)
shutil.rmtree(self.j(self.d(self.SRC), 'build'))

92
setup/install.py Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, os, textwrap
from setup import Command, islinux, basenames, modules, functions
TEMPLATE = '''\
#!/usr/bin/env python
"""
This is the standard runscript for all of calibre's tools.
Do not modify it unless you know what you are doing.
"""
import sys
sys.path.insert(0, {path!r})
sys.resources_location = {resources!r}
sys.extensions_location = {extensions!r}
from {module} import {func!s}
sys.exit({func!s}())
'''
class Develop(Command):
description = 'Setup a development environment'
MODE = 0755
sub_commands = ['build']
def add_options(self, parser):
parser.set_usage(textwrap.dedent('''\
***
Setup a development environment for calibre.
This allows you to run calibre directly from the source tree.
Binaries will be installed in <prefix>/bin where <prefix> is
the prefix of your python installation. This can be controlled
via the --prefix option.
'''))
parser.add_option('--prefix',
help='Binaries will be installed in <prefix>/bin')
def pre_sub_commands(self, opts):
if not islinux:
self.info('\nSetting up a development environment is only '
'supported on linux. On other platforms, install the calibre '
'binary and use the calibre-debug command.')
raise SystemExit(1)
if not os.geteuid() == 0:
self.info('\nError: This command must be run as root.')
raise SystemExit(1)
self.drop_privileges()
def run(self, opts):
self.regain_privileges()
self.find_locations(opts)
self.write_templates(opts)
self.success()
def success(self):
self.info('\nDevelopment environment successfully setup')
def find_locations(self, opts):
self.path = self.SRC
self.resources = self.j(self.d(self.SRC), 'resources')
self.extensions = self.j(self.SRC, 'calibre', 'plugins')
def write_templates(self, opts):
for typ in ('console', 'gui'):
for name, mod, func in zip(basenames[typ], modules[typ],
functions[typ]):
script = TEMPLATE.format(
module=mod, func=func,
path=self.path, resources=self.resources,
extensions=self.extensions)
prefix = opts.prefix
if prefix is None:
prefix = sys.prefix
path = self.j(prefix, 'bin', name)
self.info('Installing binary:', path)
open(path, 'wb').write(script)
os.chmod(path, self.MODE)

View File

@ -164,8 +164,7 @@ DEFAULTKEYWORDS = ', '.join(default_keywords)
EMPTYSTRING = '' EMPTYSTRING = ''
from calibre.constants import __appname__ from setup import __appname__, __version__ as version
from calibre.constants import __version__ as version
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
# there. # there.

218
setup/translations.py Normal file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, cStringIO, tempfile, shutil, atexit, subprocess, glob, re
from distutils import sysconfig
from setup import Command, __appname__
from setup.pygettext import main as pygettext
class POT(Command):
description = 'Update the .pot translation template'
PATH = os.path.join(Command.SRC, __appname__, 'translations')
def source_files(self):
ans = []
for root, _, files in os.walk(os.path.dirname(self.PATH)):
for name in files:
if name.endswith('.py'):
ans.append(os.path.abspath(os.path.join(root, name)))
return ans
def run(self, opts):
files = self.source_files()
buf = cStringIO.StringIO()
self.info('Creating translations template...')
tempdir = tempfile.mkdtemp()
atexit.register(shutil.rmtree, tempdir)
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
src = buf.getvalue()
pot = os.path.join(self.PATH, __appname__+'.pot')
f = open(pot, 'wb')
f.write(src)
f.close()
self.info('Translations template:', os.path.abspath(pot))
return pot
class Translations(POT):
description='''Compile the translations'''
DEST = os.path.join(os.path.dirname(POT.SRC), 'resources', 'localization',
'locales')
def po_files(self):
return glob.glob(os.path.join(self.PATH, '*.po'))
def mo_file(self, po_file):
locale = os.path.splitext(os.path.basename(po_file))[0]
return locale, os.path.join(self.DEST, locale, 'LC_MESSAGES', 'messages.mo')
def run(self, opts):
for f in self.po_files():
locale, dest = self.mo_file(f)
base = os.path.dirname(dest)
if not os.path.exists(base):
os.makedirs(base)
if self.newer(dest, f):
self.info('\tCompiling translations for', locale)
subprocess.check_call(['msgfmt', '-o', dest, f])
if locale in ('en_GB', 'nds', 'te', 'yi'):
continue
pycountry = self.j(sysconfig.get_python_lib(), 'pycountry',
'locales', locale, 'LC_MESSAGES')
if os.path.exists(pycountry):
iso639 = self.j(pycountry, 'iso639.mo')
dest = self.j(self.d(dest), self.b(iso639))
if self.newer(dest, iso639):
self.info('\tCopying ISO 639 translations')
shutil.copy2(iso639, dest)
else:
self.warn('No ISO 639 translations for locale:', locale)
self.write_stats()
@property
def stats(self):
return self.j(self.d(self.DEST), 'stats.pickle')
def get_stats(self, path):
return subprocess.Popen(['msgfmt', '--statistics', '-o', '/dev/null',
path],
stderr=subprocess.PIPE).stderr.read()
def write_stats(self):
files = self.po_files()
dest = self.stats
if not self.newer(dest, files):
return
self.info('Calculating translation statistics...')
raw = self.get_stats(self.j(self.PATH, 'calibre.pot'))
total = int(raw.split(',')[-1].strip().split()[0])
stats = {}
for f in files:
raw = self.get_stats(f)
trans = int(raw.split()[0])
locale = self.mo_file(f)[0]
stats[locale] = min(1.0, float(trans)/total)
import cPickle
cPickle.dump(stats, open(dest, 'wb'), -1)
def clean(self):
if os.path.exists(self.stats):
os.remove(self.stats)
for f in self.po_files():
l, d = self.mo_file(f)
i = self.j(self.d(d), 'iso639.mo')
for x in (i, d):
if os.path.exists(x):
os.remove(x)
class GetTranslations(Translations):
description = 'Get updated translations from Launchpad'
BRANCH = 'lp:~kovid/calibre/translations'
@classmethod
def modified_translations(cls):
raw = subprocess.Popen(['bzr', 'status'],
stdout=subprocess.PIPE).stdout.read().strip()
for line in raw.splitlines():
line = line.strip()
if line.startswith(cls.PATH) and line.endswith('.po'):
yield line
def run(self, opts):
if len(list(self.modified_translations())) == 0:
subprocess.check_call(['bzr', 'merge', self.BRANCH])
if len(list(self.modified_translations())) == 0:
print 'No updated translations available'
else:
subprocess.check_call(['bzr', 'commit', '-m',
'IGN:Updated translations', self.PATH])
self.check_for_errors()
@classmethod
def check_for_errors(cls):
errors = os.path.join(tempfile.gettempdir(), 'calibre-translation-errors')
if os.path.exists(errors):
shutil.rmtree(errors)
os.mkdir(errors)
pofilter = ('pofilter', '-i', cls.PATH, '-o', errors,
'-t', 'accelerators', '-t', 'escapes', '-t', 'variables',
#'-t', 'xmltags',
#'-t', 'brackets',
#'-t', 'emails',
#'-t', 'doublequoting',
#'-t', 'filepaths',
#'-t', 'numbers',
'-t', 'options',
#'-t', 'urls',
'-t', 'printf')
subprocess.check_call(pofilter)
errfiles = glob.glob(errors+os.sep+'*.po')
subprocess.check_call(['gvim', '-f', '-p', '--']+errfiles)
for f in errfiles:
with open(f, 'r+b') as f:
raw = f.read()
raw = re.sub(r'# \(pofilter\).*', '', raw)
f.seek(0)
f.truncate()
f.write(raw)
subprocess.check_call(['pomerge', '-t', cls.PATH, '-i', errors, '-o',
cls.PATH])
if len(list(cls.modified_translations())) > 0:
subprocess.call(['bzr', 'diff', cls.PATH])
yes = raw_input('Merge corrections? [y/n]: ').strip()
if yes in ['', 'y']:
subprocess.check_call(['bzr', 'commit', '-m',
'IGN:Translation corrections', cls.PATH])
class ISO639(Command):
XML = '/usr/lib/python2.6/site-packages/pycountry/databases/iso639.xml'
def run(self, opts):
src = self.XML
if not os.path.exists(src):
raise Exception(src + ' does not exist')
dest = self.j(self.d(self.SRC), 'resources', 'localization',
'iso639.pickle')
if not self.newer(dest, src):
self.info('Pickled code is up to date')
return
self.info('Pickling ISO-639 codes to', dest)
from lxml import etree
root = etree.fromstring(open(src, 'rb').read())
by_2 = {}
by_3b = {}
by_3t = {}
codes2, codes3t, codes3b = set([]), set([]), set([])
for x in root.xpath('//iso_639_entry'):
name = x.get('name')
two = x.get('iso_639_1_code', None)
if two is not None:
by_2[two] = name
codes2.add(two)
by_3b[x.get('iso_639_2B_code')] = name
by_3t[x.get('iso_639_2T_code')] = name
codes3b.add(x.get('iso_639_2B_code'))
codes3t.add(x.get('iso_639_2T_code'))
from cPickle import dump
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
'codes3b':codes3b, 'codes3t':codes3t}
dump(x, open(dest, 'wb'), -1)

View File

@ -14,13 +14,14 @@ numeric_version = tuple(_ver)
Various run time constants. Various run time constants.
''' '''
import sys, locale, codecs, os import sys, locale, codecs
from calibre.utils.terminfo import TerminalController from calibre.utils.terminfo import TerminalController
terminal_controller = TerminalController(sys.stdout) terminal_controller = TerminalController(sys.stdout)
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower() iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
isosx = 'darwin' in sys.platform.lower() isosx = 'darwin' in sys.platform.lower()
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
islinux = not(iswindows or isosx) islinux = not(iswindows or isosx)
isfrozen = hasattr(sys, 'frozen') isfrozen = hasattr(sys, 'frozen')
@ -50,18 +51,7 @@ if plugins is None:
# Load plugins # Load plugins
def load_plugins(): def load_plugins():
plugins = {} plugins = {}
if isfrozen: plugin_path = sys.extensions_location
if iswindows:
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
sys.path.insert(1, os.path.dirname(sys.executable))
elif isosx:
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
elif islinux:
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
sys.path.insert(0, plugin_path)
else:
import pkg_resources
plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins')
sys.path.insert(0, plugin_path) sys.path.insert(0, plugin_path)
for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc', for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc',
@ -74,6 +64,7 @@ if plugins is None:
p = None p = None
err = str(err) err = str(err)
plugins[plugin] = (p, err) plugins[plugin] = (p, err)
sys.path.remove(plugin_path)
return plugins return plugins
plugins = load_plugins() plugins = load_plugins()

View File

@ -25,6 +25,12 @@ _run_once = False
if not _run_once: if not _run_once:
_run_once = True _run_once = True
################################################################################
# Setup resources
import calibre.utils.resources as resources
resources
################################################################################ ################################################################################
# Setup translations # Setup translations

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import subprocess, os, sys, time import subprocess, os, sys, time
from calibre.constants import iswindows, isosx, isfrozen from calibre.constants import iswindows, isosx, isfrozen, isnewosx
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
@ -16,8 +16,6 @@ if iswindows:
import win32process import win32process
_windows_null_file = open(os.devnull, 'wb') _windows_null_file = open(os.devnull, 'wb')
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
class Worker(object): class Worker(object):
''' '''
Platform independent object for launching child processes. All processes Platform independent object for launching child processes. All processes

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
_available_translations = None
def available_translations():
global _available_translations
if _available_translations is None:
base = P('resources/localization/locales')
_available_translations = os.listdir(base)
return _available_translations

View File

@ -0,0 +1,17 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import __builtin__, sys, os
def get_path(path):
path = path.replace(os.sep, '/')
return os.path.join(sys.resources_location, *path.split('/'))
__builtin__.__dict__['P'] = get_path

View File

@ -10,9 +10,8 @@ from calibre.web.feeds.news import BasicNewsRecipe
class ArsTechnica2(BasicNewsRecipe): class ArsTechnica2(BasicNewsRecipe):
title = u'Ars Technica' title = u'Ars Technica'
language = 'en' language = _('English')
__author__ = 'Darko Miletic and Sujata Raman'
__author__ = 'Darko Miletic'
description = 'The art of technology' description = 'The art of technology'
publisher = 'Ars Technica' publisher = 'Ars Technica'
category = 'news, IT, technology' category = 'news, IT, technology'
@ -23,13 +22,14 @@ class ArsTechnica2(BasicNewsRecipe):
remove_javascript = True remove_javascript = True
use_embedded_content = False use_embedded_content = False
html2lrf_options = [ extra_css = '''
'--comment', description .news-item-title{font-size: medium ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;}
, '--category', category .news-item-teaser{font-size: small ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;}
, '--publisher', publisher .news-item-byline{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
] .news-item-text{font-size:x-small;font-family:Arial,Helvetica,sans-serif;}
.news-item-figure-caption-text{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:bold;}
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' .news-item-figure-caption-byline{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
'''
keep_only_tags = [dict(name='div', attrs={'id':['news-item-info','news-item']})] keep_only_tags = [dict(name='div', attrs={'id':['news-item-info','news-item']})]
@ -58,9 +58,11 @@ class ArsTechnica2(BasicNewsRecipe):
str = self.tag_to_string(atag) str = self.tag_to_string(atag)
if str.startswith('Next'): if str.startswith('Next'):
soup2 = self.index_to_soup(atag['href']) soup2 = self.index_to_soup(atag['href'])
texttag = soup2.find('div', attrs={'class':'news-item-text'}) texttag = soup2.find('div', attrs={'class':'news-item-text'})
for it in texttag.findAll(style=True): for it in texttag.findAll(style=True):
del it['style'] del it['style']
newpos = len(texttag.contents) newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos) self.append_page(soup2,texttag,newpos)
texttag.extract() texttag.extract()
@ -69,10 +71,17 @@ class ArsTechnica2(BasicNewsRecipe):
def preprocess_html(self, soup): def preprocess_html(self, soup):
ftag = soup.find('div', attrs={'class':'news-item-byline'}) ftag = soup.find('div', attrs={'class':'news-item-byline'})
if ftag: if ftag:
ftag.insert(4,'<br /><br />') ftag.insert(4,'<br /><br />')
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
self.append_page(soup, soup.body, 3) self.append_page(soup, soup.body, 3)
return soup return soup

211
upload.py
View File

@ -6,10 +6,14 @@ __docformat__ = 'restructuredtext en'
import shutil, os, glob, re, cStringIO, sys, tempfile, time, textwrap, socket, \ import shutil, os, glob, re, cStringIO, sys, tempfile, time, textwrap, socket, \
struct, subprocess, platform struct, subprocess, platform
from datetime import datetime from datetime import datetime
from setuptools.command.build_py import build_py as _build_py, convert_path from stat import ST_MODE
from distutils.command.build_py import build_py as _build_py, convert_path
from distutils.command.install_scripts import install_scripts as _install_scripts
from distutils.command.install import install as _install
from distutils.core import Command from distutils.core import Command
from subprocess import check_call, call, Popen from subprocess import check_call, call, Popen
from distutils.command.build import build as _build from distutils.command.build import build as _build
from distutils import log
raw = open(os.path.join('src', 'calibre', 'constants.py'), 'rb').read() raw = open(os.path.join('src', 'calibre', 'constants.py'), 'rb').read()
__version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', raw).group(1) __version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', raw).group(1)
@ -25,6 +29,9 @@ TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/' MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
is64bit = platform.architecture()[0] == '64bit' is64bit = platform.architecture()[0] == '64bit'
iswindows = re.search('win(32|64)', sys.platform)
isosx = 'darwin' in sys.platform
islinux = not isosx and not iswindows
def get_ip_address(ifname): def get_ip_address(ifname):
import fcntl import fcntl
@ -66,6 +73,59 @@ class OptionlessCommand(Command):
for cmd_name in self.get_sub_commands(): for cmd_name in self.get_sub_commands():
self.run_command(cmd_name) self.run_command(cmd_name)
def setup_mount_helper(tdir):
def warn():
print 'WARNING: Failed to compile mount helper. Auto mounting of',
print 'devices will not work'
if os.geteuid() != 0:
return warn()
import stat
src = os.path.join('src', 'calibre', 'devices', 'linux_mount_helper.c')
dest = os.path.join(tdir, 'calibre-mount-helper')
log.info('Installing mount helper to '+ tdir)
p = subprocess.Popen(['gcc', '-Wall', src, '-o', dest])
ret = p.wait()
if ret != 0:
return warn()
os.chown(dest, 0, 0)
os.chmod(dest,
stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH)
return dest
class develop(_install_scripts):
def run(self):
if not iswindows and os.geteuid() != 0:
raise Exception('Must be root to run this command.')
if not self.skip_build:
self.run_command('build_ext')
self.run_command('build_scripts')
for script in os.listdir(self.build_dir):
script = os.path.join(self.build_dir, script)
raw = open(script, 'rb').read()
raw = re.sub(r'"""##DEVELOP_HOOK##([^#]+)##END_DEVELOP_HOOK##"""',
r'\1', raw)
raw = raw.replace('#!python', '#!'+sys.executable)
f = os.path.join(self.install_dir, os.path.basename(script))
open(f, 'wb').write(raw)
mode = ((os.stat(f)[ST_MODE]) | 0555) & 07777
log.info('changing mode of %s to %o'%(f, mode))
os.chmod(f, mode)
if islinux:
setup_mount_helper(self.install_dir)
subprocess.check_call('calibre_postinstall')
class install(_install):
def run(self):
_install.run(self)
if islinux:
setup_mount_helper(self.install_dir)
subprocess.check_call('calibre_postinstall')
class sdist(OptionlessCommand): class sdist(OptionlessCommand):
@ -77,39 +137,6 @@ class sdist(OptionlessCommand):
self.distribution.dist_files.append(('sdist', '', name)) self.distribution.dist_files.append(('sdist', '', name))
print 'Source distribution created in', os.path.abspath(name) print 'Source distribution created in', os.path.abspath(name)
class pot(OptionlessCommand):
description = '''Create the .pot template for all translatable strings'''
PATH = os.path.join('src', __appname__, 'translations')
def source_files(self):
ans = []
for root, _, files in os.walk(os.path.dirname(self.PATH)):
for name in files:
if name.endswith('.py'):
ans.append(os.path.abspath(os.path.join(root, name)))
return ans
def run(self):
sys.path.insert(0, os.path.abspath(self.PATH))
try:
pygettext = __import__('pygettext', fromlist=['main']).main
files = self.source_files()
buf = cStringIO.StringIO()
print 'Creating translations template'
tempdir = tempfile.mkdtemp()
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
src = buf.getvalue()
pot = os.path.join(self.PATH, __appname__+'.pot')
f = open(pot, 'wb')
f.write(src)
f.close()
print 'Translations template:', os.path.abspath(pot)
return pot
finally:
sys.path.remove(os.path.abspath(self.PATH))
class manual(OptionlessCommand): class manual(OptionlessCommand):
description='''Build the User Manual ''' description='''Build the User Manual '''
@ -249,99 +276,6 @@ class resources(OptionlessCommand):
if os.path.exists(path): if os.path.exists(path):
os.remove(path) os.remove(path)
class translations(OptionlessCommand):
description='''Compile the translations'''
PATH = os.path.join('src', __appname__, 'translations')
DEST = os.path.join(PATH, 'compiled.py')
def run(self):
sys.path.insert(0, os.path.abspath(self.PATH))
try:
files = glob.glob(os.path.join(self.PATH, '*.po'))
if newer([self.DEST], files):
msgfmt = __import__('msgfmt', fromlist=['main']).main
translations = {}
print 'Compiling translations...'
for po in files:
lang = os.path.basename(po).partition('.')[0]
buf = cStringIO.StringIO()
print 'Compiling', lang
msgfmt(buf, [po])
translations[lang] = buf.getvalue()
open(self.DEST, 'wb').write('translations = '+repr(translations))
else:
print 'Translations up to date'
finally:
sys.path.remove(os.path.abspath(self.PATH))
@classmethod
def clean(cls):
path = cls.DEST
if os.path.exists(path):
os.remove(path)
class get_translations(translations):
description = 'Get updated translations from Launchpad'
BRANCH = 'lp:~kovid/calibre/translations'
@classmethod
def modified_translations(cls):
raw = subprocess.Popen(['bzr', 'status'],
stdout=subprocess.PIPE).stdout.read().strip()
for line in raw.splitlines():
line = line.strip()
if line.startswith(cls.PATH) and line.endswith('.po'):
yield line
def run(self):
if len(list(self.modified_translations())) == 0:
subprocess.check_call(['bzr', 'merge', self.BRANCH])
if len(list(self.modified_translations())) == 0:
print 'No updated translations available'
else:
subprocess.check_call(['bzr', 'commit', '-m',
'IGN:Updated translations', self.PATH])
self.check_for_errors()
@classmethod
def check_for_errors(cls):
errors = os.path.join(tempfile.gettempdir(), 'calibre-translation-errors')
if os.path.exists(errors):
shutil.rmtree(errors)
os.mkdir(errors)
pofilter = ('pofilter', '-i', cls.PATH, '-o', errors,
'-t', 'accelerators', '-t', 'escapes', '-t', 'variables',
#'-t', 'xmltags',
#'-t', 'brackets',
#'-t', 'emails',
#'-t', 'doublequoting',
#'-t', 'filepaths',
#'-t', 'numbers',
'-t', 'options',
#'-t', 'urls',
'-t', 'printf')
subprocess.check_call(pofilter)
errfiles = glob.glob(errors+os.sep+'*.po')
subprocess.check_call(['gvim', '-f', '-p', '--']+errfiles)
for f in errfiles:
with open(f, 'r+b') as f:
raw = f.read()
raw = re.sub(r'# \(pofilter\).*', '', raw)
f.seek(0)
f.truncate()
f.write(raw)
subprocess.check_call(['pomerge', '-t', cls.PATH, '-i', errors, '-o',
cls.PATH])
if len(list(cls.modified_translations())) > 0:
subprocess.call(['bzr', 'diff', cls.PATH])
yes = raw_input('Merge corrections? [y/n]: ').strip()
if yes in ['', 'y']:
subprocess.check_call(['bzr', 'commit', '-m',
'IGN:Translation corrections', cls.PATH])
class gui(OptionlessCommand): class gui(OptionlessCommand):
description='''Compile all GUI forms and images''' description='''Compile all GUI forms and images'''
PATH = os.path.join('src', __appname__, 'gui2') PATH = os.path.join('src', __appname__, 'gui2')
@ -439,29 +373,6 @@ class gui(OptionlessCommand):
if os.path.exists(x): if os.path.exists(x):
os.remove(x) os.remove(x)
class clean(OptionlessCommand):
description='''Delete all computer generated files in the source tree'''
def run(self):
print 'Cleaning...'
manual.clean()
gui.clean()
translations.clean()
resources.clean()
for f in glob.glob(os.path.join('src', 'calibre', 'plugins', '*')):
os.remove(f)
for root, _, files in os.walk('.'):
for name in files:
for t in ('.pyc', '.pyo', '~'):
if name.endswith(t):
os.remove(os.path.join(root, name))
break
for dir in ('build', 'dist', os.path.join('src', 'calibre.egg-info')):
shutil.rmtree(dir, ignore_errors=True)
class build_py(_build_py): class build_py(_build_py):
def find_data_files(self, package, src_dir): def find_data_files(self, package, src_dir):