From df9284b30d3faae533a3dcb15bfc6be8c539064e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 6 Sep 2009 19:45:40 -0600 Subject: [PATCH 01/22] build and develop commands done --- .bzrignore | 1 + INSTALL | 34 ++ README | 17 + pyqtdistutils.py | 279 -------------- session.vim | 2 +- setup.py | 340 +++-------------- setup/__init__.py | 193 ++++++++++ setup/build_environment.py | 101 +++++ setup/commands.py | 71 ++++ setup/extensions.py | 348 ++++++++++++++++++ setup/install.py | 92 +++++ .../translations => setup}/pygettext.py | 5 +- setup/translations.py | 218 +++++++++++ src/calibre/constants.py | 19 +- src/calibre/startup.py | 6 + src/calibre/utils/ipc/launch.py | 4 +- src/calibre/utils/localization.py | 18 + src/calibre/utils/resources.py | 17 + .../web/feeds/recipes/recipe_ars_technica.py | 165 +++++---- upload.py | 211 +++-------- 20 files changed, 1326 insertions(+), 815 deletions(-) create mode 100644 INSTALL create mode 100644 README delete mode 100644 pyqtdistutils.py create mode 100644 setup/__init__.py create mode 100644 setup/build_environment.py create mode 100644 setup/commands.py create mode 100644 setup/extensions.py create mode 100644 setup/install.py rename {src/calibre/translations => setup}/pygettext.py (99%) create mode 100644 setup/translations.py create mode 100644 src/calibre/utils/localization.py create mode 100644 src/calibre/utils/resources.py diff --git a/.bzrignore b/.bzrignore index 0a44159b1e..081ed9f5ca 100644 --- a/.bzrignore +++ b/.bzrignore @@ -13,6 +13,7 @@ src/calibre/manual/cli/ build dist docs +resources nbproject/ src/calibre/gui2/pictureflow/Makefile.Debug src/calibre/gui2/pictureflow/Makefile.Release diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000000..623918f09e --- /dev/null +++ b/INSTALL @@ -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 /bin + - Python and C modules in /lib/calibre + - Resources like icons, etc. in /share/calibre + +This type of install can be run by the command:: + + sudo python setup.py install + + 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 + + diff --git a/README b/README new file mode 100644 index 0000000000..6697b3f7f9 --- /dev/null +++ b/README @@ -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 + diff --git a/pyqtdistutils.py b/pyqtdistutils.py deleted file mode 100644 index 550bf46da7..0000000000 --- a/pyqtdistutils.py +++ /dev/null @@ -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 - diff --git a/session.vim b/session.vim index f33590c838..50c2d5285e 100644 --- a/session.vim +++ b/session.vim @@ -1,5 +1,5 @@ " Project wide builtins -let g:pyflakes_builtins += ["dynamic_property", "__"] +let g:pyflakes_builtins += ["dynamic_property", "__", "P"] python << EOFPY import os diff --git a/setup.py b/setup.py index d8072af50d..76ddd8b43a 100644 --- a/setup.py +++ b/setup.py @@ -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 + __license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' - -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() +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' -scripts = { - 'console' : [_ep_to_script(i) for i in entry_points['console_scripts']], - 'gui' : [_ep_to_script(i) for i in entry_points['gui_scripts']], - } +import sys, os, optparse -def _ep_to_basename(ep): - 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']], - } +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) -def _ep_to_module(ep): - return re.search(r'.*=\s*(.*?)\s*:', ep).group(1).strip() -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']], - } +import setup.commands as commands +from setup import prints, get_warnings -def _ep_to_function(ep): - return ep[ep.rindex(':')+1:].strip() -main_functions = { - 'console' : [_ep_to_function(i) for i in entry_points['console_scripts']], - 'gui' : [_ep_to_function(i) for i in entry_points['gui_scripts']], - } +def check_version_info(): + vi = sys.version_info + if vi[0] == 2 and vi[1] > 5: + return None + return 'calibre requires python >= 2.6' -def setup_mount_helper(): - def warn(): - print 'WARNING: Failed to compile mount helper. Auto mounting of', - print 'devices will not work' +def option_parser(): + parser = optparse.OptionParser() + return parser - if os.geteuid() != 0: - return warn() - import stat - src = os.path.join('src', 'calibre', 'devices', 'linux_mount_helper.c') - dest = '/usr/bin/calibre-mount-helper' - 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) +def main(args=sys.argv): + if len(args) == 1 or args[1] in ('-h', '--help'): + print 'Usage: python', args[0], 'command', '[options]' + print '\nWhere command is one of:', ', '.join(commands.__all__) + print '\nTo get help on a particular command, run:' + print '\tpython', args[0], 'command -h' + return 1 + + command = args[1] + if command not in commands.__all__: + print command, 'is not a recognized command.' + 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__': - from setuptools import setup, find_packages - 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() + sys.exit(main()) diff --git a/setup/__init__.py b/setup/__init__.py new file mode 100644 index 0000000000..3be9df08c2 --- /dev/null +++ b/setup/__init__.py @@ -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 ' +__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() + diff --git a/setup/build_environment.py b/setup/build_environment.py new file mode 100644 index 0000000000..29a4577072 --- /dev/null +++ b/setup/build_environment.py @@ -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 ' +__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.') + + diff --git a/setup/commands.py b/setup/commands.py new file mode 100644 index 0000000000..721caa165c --- /dev/null +++ b/setup/commands.py @@ -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 ' +__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())) diff --git a/setup/extensions.py b/setup/extensions.py new file mode 100644 index 0000000000..25b4979022 --- /dev/null +++ b/setup/extensions.py @@ -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 ' +__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')) + + + + + + + + + + + diff --git a/setup/install.py b/setup/install.py new file mode 100644 index 0000000000..3d82cb3183 --- /dev/null +++ b/setup/install.py @@ -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 ' +__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 /bin where 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 /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) + diff --git a/src/calibre/translations/pygettext.py b/setup/pygettext.py similarity index 99% rename from src/calibre/translations/pygettext.py rename to setup/pygettext.py index 9578ef2d51..9ef2b7aa1b 100644 --- a/src/calibre/translations/pygettext.py +++ b/setup/pygettext.py @@ -164,8 +164,7 @@ DEFAULTKEYWORDS = ', '.join(default_keywords) EMPTYSTRING = '' -from calibre.constants import __appname__ -from calibre.constants import __version__ as version +from setup import __appname__, __version__ as version # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's # there. @@ -638,7 +637,7 @@ def main(outfile, args=sys.argv[1:]): fp.close() # write the output - eater.write(outfile) + eater.write(outfile) if __name__ == '__main__': main(sys.stdout) diff --git a/setup/translations.py b/setup/translations.py new file mode 100644 index 0000000000..fb38c52504 --- /dev/null +++ b/setup/translations.py @@ -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 ' +__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) + diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 6f1f895ab2..ef8882943c 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -14,13 +14,14 @@ numeric_version = tuple(_ver) Various run time constants. ''' -import sys, locale, codecs, os +import sys, locale, codecs from calibre.utils.terminfo import TerminalController terminal_controller = TerminalController(sys.stdout) iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower() isosx = 'darwin' in sys.platform.lower() +isnewosx = isosx and getattr(sys, 'new_app_bundle', False) islinux = not(iswindows or isosx) isfrozen = hasattr(sys, 'frozen') @@ -50,19 +51,8 @@ if plugins is None: # Load plugins def load_plugins(): plugins = {} - if isfrozen: - 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) + plugin_path = sys.extensions_location + sys.path.insert(0, plugin_path) for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc', 'fontconfig', 'calibre_poppler'] + \ @@ -74,6 +64,7 @@ if plugins is None: p = None err = str(err) plugins[plugin] = (p, err) + sys.path.remove(plugin_path) return plugins plugins = load_plugins() diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 52648a20a0..6cdb0062e9 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -25,6 +25,12 @@ _run_once = False if not _run_once: _run_once = True + ################################################################################ + # Setup resources + import calibre.utils.resources as resources + resources + + ################################################################################ # Setup translations diff --git a/src/calibre/utils/ipc/launch.py b/src/calibre/utils/ipc/launch.py index dd7cd356f2..46100b5071 100644 --- a/src/calibre/utils/ipc/launch.py +++ b/src/calibre/utils/ipc/launch.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' 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.ptempfile import PersistentTemporaryFile @@ -16,8 +16,6 @@ if iswindows: import win32process _windows_null_file = open(os.devnull, 'wb') -isnewosx = isosx and getattr(sys, 'new_app_bundle', False) - class Worker(object): ''' Platform independent object for launching child processes. All processes diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py new file mode 100644 index 0000000000..aaac62ea1e --- /dev/null +++ b/src/calibre/utils/localization.py @@ -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 ' +__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 diff --git a/src/calibre/utils/resources.py b/src/calibre/utils/resources.py new file mode 100644 index 0000000000..98f056e065 --- /dev/null +++ b/src/calibre/utils/resources.py @@ -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 ' +__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 + diff --git a/src/calibre/web/feeds/recipes/recipe_ars_technica.py b/src/calibre/web/feeds/recipes/recipe_ars_technica.py index 943889e223..d390f006ec 100644 --- a/src/calibre/web/feeds/recipes/recipe_ars_technica.py +++ b/src/calibre/web/feeds/recipes/recipe_ars_technica.py @@ -1,78 +1,87 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' -''' -arstechnica.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class ArsTechnica2(BasicNewsRecipe): - title = u'Ars Technica' - language = 'en' - - __author__ = 'Darko Miletic' - description = 'The art of technology' - publisher = 'Ars Technica' - category = 'news, IT, technology' - oldest_article = 2 - max_articles_per_feed = 100 - no_stylesheets = True - encoding = 'utf8' - remove_javascript = True - use_embedded_content = False - - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - keep_only_tags = [dict(name='div', attrs={'id':['news-item-info','news-item']})] - - remove_tags = [ - dict(name=['object','link','embed']) - ,dict(name='div', attrs={'class':'related-stories'}) - ] - - - feeds = [ - (u'Infinite Loop (Apple content)' , u'http://feeds.arstechnica.com/arstechnica/apple/' ) - ,(u'Opposable Thumbs (Gaming content)' , u'http://feeds.arstechnica.com/arstechnica/gaming/' ) - ,(u'Gear and Gadgets' , u'http://feeds.arstechnica.com/arstechnica/gadgets/' ) - ,(u'Chipster (Hardware content)' , u'http://feeds.arstechnica.com/arstechnica/hardware/' ) - ,(u'Uptime (IT content)' , u'http://feeds.arstechnica.com/arstechnica/business/' ) - ,(u'Open Ended (Open Source content)' , u'http://feeds.arstechnica.com/arstechnica/open-source/') - ,(u'One Microsoft Way' , u'http://feeds.arstechnica.com/arstechnica/microsoft/' ) - ,(u'Nobel Intent (Science content)' , u'http://feeds.arstechnica.com/arstechnica/science/' ) - ,(u'Law & Disorder (Tech policy content)' , u'http://feeds.arstechnica.com/arstechnica/tech-policy/') - ] - - def append_page(self, soup, appendtag, position): - pager = soup.find('div',attrs={'id':'pager'}) - if pager: - for atag in pager.findAll('a',href=True): - str = self.tag_to_string(atag) - if str.startswith('Next'): - soup2 = self.index_to_soup(atag['href']) - texttag = soup2.find('div', attrs={'class':'news-item-text'}) - for it in texttag.findAll(style=True): - del it['style'] - newpos = len(texttag.contents) - self.append_page(soup2,texttag,newpos) - texttag.extract() - pager.extract() - appendtag.insert(position,texttag) - - - def preprocess_html(self, soup): - ftag = soup.find('div', attrs={'class':'news-item-byline'}) - if ftag: - ftag.insert(4,'

') - for item in soup.findAll(style=True): - del item['style'] - self.append_page(soup, soup.body, 3) - return soup +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008-2009, Darko Miletic ' +''' +arstechnica.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class ArsTechnica2(BasicNewsRecipe): + title = u'Ars Technica' + language = _('English') + __author__ = 'Darko Miletic and Sujata Raman' + description = 'The art of technology' + publisher = 'Ars Technica' + category = 'news, IT, technology' + oldest_article = 2 + max_articles_per_feed = 100 + no_stylesheets = True + encoding = 'utf8' + remove_javascript = True + use_embedded_content = False + + extra_css = ''' + .news-item-title{font-size: medium ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;} + .news-item-teaser{font-size: small ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;} + .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;} + .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']})] + + remove_tags = [ + dict(name=['object','link','embed']) + ,dict(name='div', attrs={'class':'related-stories'}) + ] + + + feeds = [ + (u'Infinite Loop (Apple content)' , u'http://feeds.arstechnica.com/arstechnica/apple/' ) + ,(u'Opposable Thumbs (Gaming content)' , u'http://feeds.arstechnica.com/arstechnica/gaming/' ) + ,(u'Gear and Gadgets' , u'http://feeds.arstechnica.com/arstechnica/gadgets/' ) + ,(u'Chipster (Hardware content)' , u'http://feeds.arstechnica.com/arstechnica/hardware/' ) + ,(u'Uptime (IT content)' , u'http://feeds.arstechnica.com/arstechnica/business/' ) + ,(u'Open Ended (Open Source content)' , u'http://feeds.arstechnica.com/arstechnica/open-source/') + ,(u'One Microsoft Way' , u'http://feeds.arstechnica.com/arstechnica/microsoft/' ) + ,(u'Nobel Intent (Science content)' , u'http://feeds.arstechnica.com/arstechnica/science/' ) + ,(u'Law & Disorder (Tech policy content)' , u'http://feeds.arstechnica.com/arstechnica/tech-policy/') + ] + + def append_page(self, soup, appendtag, position): + pager = soup.find('div',attrs={'id':'pager'}) + if pager: + for atag in pager.findAll('a',href=True): + str = self.tag_to_string(atag) + if str.startswith('Next'): + soup2 = self.index_to_soup(atag['href']) + + texttag = soup2.find('div', attrs={'class':'news-item-text'}) + for it in texttag.findAll(style=True): + del it['style'] + + newpos = len(texttag.contents) + self.append_page(soup2,texttag,newpos) + texttag.extract() + pager.extract() + appendtag.insert(position,texttag) + + + def preprocess_html(self, soup): + + ftag = soup.find('div', attrs={'class':'news-item-byline'}) + if ftag: + ftag.insert(4,'

') + + for item in soup.findAll(style=True): + del item['style'] + + self.append_page(soup, soup.body, 3) + + return soup + + + diff --git a/upload.py b/upload.py index 492c80e68d..6f3c5eea9e 100644 --- a/upload.py +++ b/upload.py @@ -6,10 +6,14 @@ __docformat__ = 'restructuredtext en' import shutil, os, glob, re, cStringIO, sys, tempfile, time, textwrap, socket, \ struct, subprocess, platform 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 subprocess import check_call, call, Popen from distutils.command.build import build as _build +from distutils import log raw = open(os.path.join('src', 'calibre', 'constants.py'), 'rb').read() __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/' 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): import fcntl @@ -66,6 +73,59 @@ class OptionlessCommand(Command): for cmd_name in self.get_sub_commands(): 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): @@ -77,39 +137,6 @@ class sdist(OptionlessCommand): self.distribution.dist_files.append(('sdist', '', 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): description='''Build the User Manual ''' @@ -249,99 +276,6 @@ class resources(OptionlessCommand): if os.path.exists(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): description='''Compile all GUI forms and images''' PATH = os.path.join('src', __appname__, 'gui2') @@ -439,29 +373,6 @@ class gui(OptionlessCommand): if os.path.exists(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): def find_data_files(self, package, src_dir): From ad72afa9f971fb2642c6c9b9a7c8f60d06870914 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 00:23:38 -0600 Subject: [PATCH 02/22] Translation framework for languages now works --- setup/translations.py | 15 ++- src/calibre/ebooks/html/input.py | 2 +- src/calibre/ebooks/oeb/reader.py | 2 +- src/calibre/gui2/__init__.py | 12 +- src/calibre/gui2/dialogs/config/__init__.py | 15 +-- src/calibre/gui2/dialogs/scheduler.py | 5 +- src/calibre/startup.py | 45 +------- src/calibre/utils/localization.py | 108 +++++++++++++++++- .../web/feeds/recipes/recipe_ars_technica.py | 2 +- 9 files changed, 138 insertions(+), 68 deletions(-) diff --git a/setup/translations.py b/setup/translations.py index fb38c52504..eb70a5812b 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -11,6 +11,7 @@ from distutils import sysconfig from setup import Command, __appname__ from setup.pygettext import main as pygettext +from setup.build_environment import pyqt class POT(Command): @@ -77,6 +78,17 @@ class Translations(POT): else: self.warn('No ISO 639 translations for locale:', locale) + base = os.path.join(pyqt.qt_data_dir, 'translations') + qt_translations = glob.glob(os.path.join(base, 'qt_*.qm')) + if not qt_translations: + raise Exception('Could not find qt translations') + for f in qt_translations: + locale = self.s(self.b(f))[0][3:] + dest = self.j(self.DEST, locale, 'LC_MESSAGES', 'qt.qm') + if self.e(self.d(dest)) and self.newer(dest, f): + self.info('\tCopying Qt translation for locale:', locale) + shutil.copy2(f, dest) + self.write_stats() @property @@ -113,7 +125,8 @@ class Translations(POT): 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): + j = self.j(self.d(d), 'qt.qm') + for x in (i, j, d): if os.path.exists(x): os.remove(x) diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 7b7bfdf3aa..ba468bd55a 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -22,7 +22,7 @@ from calibre.ebooks.chardet import xml_to_unicode from calibre.customize.conversion import OptionRecommendation from calibre.constants import islinux from calibre import unicode_path -from calibre.startup import get_lang +from calibre.utils.localization import get_lang class Link(object): ''' diff --git a/src/calibre/ebooks/oeb/reader.py b/src/calibre/ebooks/oeb/reader.py index 03c878b9d2..87587e3ef5 100644 --- a/src/calibre/ebooks/oeb/reader.py +++ b/src/calibre/ebooks/oeb/reader.py @@ -27,7 +27,7 @@ from calibre.ebooks.oeb.base import namespace, barename, XPath, xpath, \ OEBError, OEBBook, DirContainer from calibre.ebooks.oeb.writer import OEBWriter from calibre.ebooks.oeb.entitydefs import ENTITYDEFS -from calibre.startup import get_lang +from calibre.utils.localization import get_lang from calibre.ptempfile import TemporaryDirectory from calibre.constants import __appname__, __version__ diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 8564fda328..a9f8e3d9f1 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -10,9 +10,8 @@ from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' from calibre import islinux, iswindows, isosx -from calibre.startup import get_lang from calibre.utils.config import Config, ConfigProxy, dynamic -import calibre.resources as resources +from calibre.utils.localization import set_qt_translator from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.ebooks.metadata import MetaInformation @@ -541,12 +540,9 @@ class Application(QApplication): global gui_thread gui_thread = QThread.currentThread() self.translator = QTranslator(self) - lang = get_lang() - if lang: - data = getattr(resources, 'qt_'+lang, None) - if data: - self.translator.loadFromData(data) - self.installTranslator(self.translator) + if set_qt_translator(self.translator): + print 1111111 + self.installTranslator(self.translator) def is_ok_to_use_qt(): global gui_thread diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 74ef932f75..4f2b7e14f9 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -390,19 +390,16 @@ class ConfigDialog(QDialog, Ui_Dialog): self.cover_browse.setValue(config['cover_flow_queue_length']) self.systray_notifications.setChecked(not config['disable_tray_notification']) - from calibre.translations.compiled import translations - from calibre.translations import language_codes - from calibre.startup import get_lang + from calibre.utils.localization import available_translations, \ + get_language, get_lang lang = get_lang() - if lang is not None and language_codes.has_key(lang): - self.language.addItem(language_codes[lang], QVariant(lang)) - else: + if lang is None or lang not in available_translations(): lang = 'en' - self.language.addItem('English', QVariant('en')) - items = [(l, language_codes[l]) for l in translations.keys() \ + self.language.addItem(get_language(lang), QVariant(lang)) + items = [(l, get_language(l)) for l in available_translations() \ if l != lang] if lang != 'en': - items.append(('en', 'English')) + items.append(('en', get_language('en'))) items.sort(cmp=lambda x, y: cmp(x[1], y[1])) for item in items: self.language.addItem(item[1], QVariant(item[0])) diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index c4e040231e..70c6a64c17 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -19,6 +19,7 @@ from calibre.gui2.search_box import SearchBox2 from calibre.web.feeds.recipes import recipes, recipe_modules, compile_recipe from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.pyparsing import ParseException +from calibre.utils.localization import get_language from calibre.gui2 import NONE, error_dialog, config as gconf from calibre.utils.config import DynamicConfig from calibre.ptempfile import PersistentTemporaryFile @@ -32,7 +33,7 @@ class Recipe(object): self.id = id self.title = getattr(recipe_class, 'title', None) self.description = getattr(recipe_class, 'description', None) - self.language = getattr(recipe_class, 'language', _('Unknown')) + self.language = getattr(recipe_class, 'language', 'und') self.last_downloaded = datetime.fromordinal(1) self.downloading = False self.builtin = builtin @@ -121,7 +122,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): self.category_map = {} for r in self.recipes: - category = getattr(r, 'language', _('Unknown')) + category = get_language(getattr(r, 'language', 'und')) if not r.builtin: category = _('Custom') if r.schedule is not None: diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 6cdb0062e9..bc9399be4f 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -6,8 +6,7 @@ __docformat__ = 'restructuredtext en' Perform various initialization tasks. ''' -import locale, sys, os, re, cStringIO -from gettext import GNUTranslations +import locale, sys, os # Default translation is NOOP import __builtin__ @@ -18,8 +17,6 @@ __builtin__.__dict__['_'] = lambda s: s __builtin__.__dict__['__'] = lambda s: s from calibre.constants import iswindows, preferred_encoding, plugins -from calibre.utils.config import prefs -from calibre.translations.msgfmt import make _run_once = False if not _run_once: @@ -33,45 +30,9 @@ if not _run_once: ################################################################################ # Setup translations + from calibre.utils.localization import set_translators - def get_lang(): - lang = prefs['language'] - if lang is not None: - return lang - lang = locale.getdefaultlocale(['LANGUAGE', 'LC_ALL', 'LC_CTYPE', - 'LC_MESSAGES', 'LANG'])[0] - if lang is None and os.environ.has_key('LANG'): # Needed for OS X - try: - lang = os.environ['LANG'] - except: - pass - if lang: - match = re.match('[a-z]{2,3}', lang) - if match: - lang = match.group() - return lang - - def set_translator(): - # To test different translations invoke as - # LC_ALL=de_DE.utf8 program - try: - from calibre.translations.compiled import translations - except: - return - lang = get_lang() - if lang: - buf = None - if os.access(lang+'.po', os.R_OK): - buf = cStringIO.StringIO() - make(lang+'.po', buf) - buf = cStringIO.StringIO(buf.getvalue()) - elif translations.has_key(lang): - buf = cStringIO.StringIO(translations[lang]) - if buf is not None: - t = GNUTranslations(buf) - t.install(unicode=True) - - set_translator() + set_translators() ################################################################################ # Initialize locale diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index aaac62ea1e..e307af332f 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -6,13 +6,115 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, locale, re, cStringIO, cPickle +from gettext import GNUTranslations _available_translations = None def available_translations(): global _available_translations if _available_translations is None: - base = P('resources/localization/locales') - _available_translations = os.listdir(base) + stats = P('localization/stats.pickle') + stats = cPickle.load(open(stats, 'rb')) + _available_translations = [x for x in stats if stats[x] > 0.1] return _available_translations + +def get_lang(): + 'Try to figure out what language to display the interface in' + from calibre.utils.config import prefs + lang = prefs['language'] + lang = os.environ.get('CALIBRE_OVERRIDE_LANG', lang) + if lang is not None: + return lang + lang = locale.getdefaultlocale(['LANGUAGE', 'LC_ALL', 'LC_CTYPE', + 'LC_MESSAGES', 'LANG'])[0] + if lang is None and os.environ.has_key('LANG'): # Needed for OS X + try: + lang = os.environ['LANG'] + except: + pass + if lang: + match = re.match('[a-z]{2,3}(_[A-Z]{2}){0,1}', lang) + if match: + lang = match.group() + if lang == 'zh': + lang = 'zh_CN' + return lang + +def messages_path(lang): + return P('localization/locales/%s/LC_MESSAGES'%lang) + +def set_translators(): + # To test different translations invoke as + # CALIBRE_OVERRIDE_LANG=de_DE.utf8 program + lang = get_lang() + if lang: + translations = available_translations() + buf = iso639 = None + if os.access(lang+'.po', os.R_OK): + from calibre.translations.msgfmt import make + buf = cStringIO.StringIO() + make(lang+'.po', buf) + buf = cStringIO.StringIO(buf.getvalue()) + + hlang = None + if lang in available_translations(): + hlang = lang + else: + xlang = lang.split('_')[0] + if xlang in available_translations(): + hlang = xlang + if hlang is not None: + if buf is None: + buf = open(os.path.join(messages_path(hlang), + 'messages.mo'), 'rb') + iso639 = open(os.path.join(messages_path(hlang), + 'iso639.mo'), 'rb') + + if buf is not None: + t = GNUTranslations(buf) + if iso639 is not None: + iso639 = GNUTranslations(iso639) + t.add_fallback(iso639) + t.install(unicode=True) + +_iso639 = None +_extra_lang_codes = { + 'pt_BR' : _('Brazilian Portuguese'), + 'en_GB' : _('English (UK)'), + 'zh_CN' : _('Simplified Chinese'), + 'zh_HK' : _('Chinese (HK)'), + 'zh_TW' : _('Traditional Chinese'), + 'en' : _('English (US)'), + 'und' : _('Unknown') + } + +def get_language(lang): + global _iso639 + if lang in _extra_lang_codes: + return _extra_lang_codes[lang] + if _iso639 is None: + _iso639 = cPickle.load(open(P('localization/iso639.pickle'), 'rb')) + ans = lang + lang = lang.split('_')[0].lower() + if len(lang) == 2: + ans = _iso639['by_2'].get(lang, ans) + elif len(lang) == 3: + if lang in _iso639['by_3b']: + ans = _iso639['by_3b'][lang] + else: + ans = _iso639['by_3t'].get(lang, ans) + return _(ans) + + +def set_qt_translator(translator): + lang = get_lang() + if lang is not None: + if lang == 'nds': + lang = 'de' + for x in (lang, lang.split('_')[0]): + p = os.path.join(messages_path(x), 'qt.qm') + if os.path.exists(p): + return translator.loadFromData(open(p, 'rb').read()) + return False + diff --git a/src/calibre/web/feeds/recipes/recipe_ars_technica.py b/src/calibre/web/feeds/recipes/recipe_ars_technica.py index d390f006ec..e5b54edc03 100644 --- a/src/calibre/web/feeds/recipes/recipe_ars_technica.py +++ b/src/calibre/web/feeds/recipes/recipe_ars_technica.py @@ -10,7 +10,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class ArsTechnica2(BasicNewsRecipe): title = u'Ars Technica' - language = _('English') + language = 'en' __author__ = 'Darko Miletic and Sujata Raman' description = 'The art of technology' publisher = 'Ars Technica' From f60b270cce9999136ade5dab645d23e6ce4cfabf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 10:41:39 -0600 Subject: [PATCH 03/22] Qt translations now work again --- setup/install.py | 8 ++++++-- setup/translations.py | 3 ++- src/calibre/gui2/__init__.py | 8 ++++---- src/calibre/gui2/main.py | 6 +++--- src/calibre/utils/localization.py | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/setup/install.py b/setup/install.py index 3d82cb3183..540de730e6 100644 --- a/setup/install.py +++ b/setup/install.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys, os, textwrap +import sys, os, textwrap, subprocess from setup import Command, islinux, basenames, modules, functions @@ -33,7 +33,7 @@ class Develop(Command): description = 'Setup a development environment' MODE = 0755 - sub_commands = ['build'] + sub_commands = ['build', 'translations'] def add_options(self, parser): parser.set_usage(textwrap.dedent('''\ @@ -64,8 +64,12 @@ class Develop(Command): self.regain_privileges() self.find_locations(opts) self.write_templates(opts) + self.run_postinstall() self.success() + def run_postinstall(self): + subprocess.check_call(['calibre_postinstall']) + def success(self): self.info('\nDevelopment environment successfully setup') diff --git a/setup/translations.py b/setup/translations.py index eb70a5812b..5ba84c4a5e 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -76,7 +76,8 @@ class Translations(POT): self.info('\tCopying ISO 639 translations') shutil.copy2(iso639, dest) else: - self.warn('No ISO 639 translations for locale:', locale) + self.warn('No ISO 639 translations for locale:', locale, + '\nDo you have pycountry installed?') base = os.path.join(pyqt.qt_data_dir, 'translations') qt_translations = glob.glob(os.path.join(base, 'qt_*.qm')) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index a9f8e3d9f1..bda0a7ffb8 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -532,6 +532,7 @@ class ResizableDialog(QDialog): gui_thread = None + class Application(QApplication): def __init__(self, args): @@ -539,10 +540,9 @@ class Application(QApplication): QApplication.__init__(self, qargs) global gui_thread gui_thread = QThread.currentThread() - self.translator = QTranslator(self) - if set_qt_translator(self.translator): - print 1111111 - self.installTranslator(self.translator) + self._translator = QTranslator(self) + if set_qt_translator(self._translator): + self.installTranslator(self._translator) def is_ok_to_use_qt(): global gui_thread diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 5667a3ca9b..d023851f15 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -1821,12 +1821,12 @@ def init_qt(args): opts, args = parser.parse_args(args) if opts.with_library is not None and os.path.isdir(opts.with_library): prefs.set('library_path', os.path.abspath(opts.with_library)) - print 'Using library at', prefs['library_path'] + prints('Using library at', prefs['library_path']) + QCoreApplication.setOrganizationName(ORG_NAME) + QCoreApplication.setApplicationName(APP_UID) app = Application(args) actions = tuple(Main.create_application_menubar()) app.setWindowIcon(QIcon(':/library')) - QCoreApplication.setOrganizationName(ORG_NAME) - QCoreApplication.setApplicationName(APP_UID) return app, opts, args, actions def run_gui(opts, args, actions, listener, app): diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index e307af332f..cd3b1c75ef 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -115,6 +115,6 @@ def set_qt_translator(translator): for x in (lang, lang.split('_')[0]): p = os.path.join(messages_path(x), 'qt.qm') if os.path.exists(p): - return translator.loadFromData(open(p, 'rb').read()) + return translator.load(p) return False From 8a1de85fc3aff8f7c5214509212e678bd9e07f5b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 13:30:01 -0600 Subject: [PATCH 04/22] Change references toimages.qrc --- .bzrignore | 20 +--- .../gui2 => resources}/images/add_book.svg | 0 .../gui2 => resources}/images/arrow-down.svg | 0 .../gui2 => resources}/images/arrow-up.svg | 0 .../images/auto_author_sort.svg | 0 .../gui2 => resources}/images/back.svg | 0 .../gui2 => resources}/images/book.svg | 0 .../gui2 => resources}/images/bookmarks.svg | 0 .../images/books_in_series.svg | 0 .../gui2 => resources}/images/chapters.svg | 0 .../gui2 => resources}/images/clear_left.svg | 0 .../gui2 => resources}/images/config.svg | 0 .../gui2 => resources}/images/convert.svg | 0 .../gui2 => resources}/images/cover_flow.svg | 0 .../gui2 => resources}/images/debug.svg | 0 .../images/dialog_error.svg | 0 .../images/dialog_information.svg | 0 .../images/dialog_warning.svg | 0 .../images/document_open.svg | 0 .../gui2 => resources}/images/donate.svg | 0 .../gui2 => resources}/images/edit_input.svg | 0 .../gui2 => resources}/images/eject.svg | 0 .../gui2 => resources}/images/exec.svg | 0 .../images/font_size_larger.svg | 0 .../images/font_size_smaller.svg | 0 .../gui2 => resources}/images/forward.svg | 0 .../gui2 => resources}/images/gmail_logo.png | Bin .../images/jobs-animated.mng | Bin .../gui2 => resources}/images/jobs.svg | 0 .../gui2 => resources}/images/library.png | Bin .../gui2 => resources}/images/list_remove.svg | 0 .../gui2 => resources}/images/lookfeel.svg | 0 .../gui2 => resources}/images/mail.svg | 0 .../gui2 => resources}/images/metadata.svg | 0 .../images/mimetypes/bmp.svg | 0 .../images/mimetypes/computer.svg | 0 .../images/mimetypes/dir.svg | 0 .../images/mimetypes/epub.svg | 0 .../images/mimetypes/fb2.svg | 0 .../images/mimetypes/gif.svg | 0 .../images/mimetypes/html.svg | 0 .../images/mimetypes/jpeg.svg | 0 .../images/mimetypes/lit.svg | 0 .../images/mimetypes/lrf.svg | 0 .../images/mimetypes/lrx.svg | 0 .../images/mimetypes/mobi.svg | 0 .../images/mimetypes/pdf.svg | 0 .../images/mimetypes/png.svg | 0 .../images/mimetypes/rar.svg | 0 .../images/mimetypes/rtf.svg | 0 .../images/mimetypes/svg.svg | 0 .../images/mimetypes/txt.svg | 0 .../images/mimetypes/unknown.svg | 0 .../images/mimetypes/zero.svg | 0 .../images/mimetypes/zip.svg | 0 .../gui2 => resources}/images/minus.svg | 0 .../images/network-server.svg | 0 .../gui2 => resources}/images/news.svg | 0 .../gui2 => resources}/images/news/24sata.png | Bin .../images/news/24sata_rs.png | Bin .../images/news/adventuregamers.png | Bin .../gui2 => resources}/images/news/ambito.png | Bin .../gui2 => resources}/images/news/amspec.png | Bin .../images/news/ars_technica.png | Bin .../images/news/atlantic.png | Bin .../images/news/axxon_news.png | Bin .../images/news/azstarnet.png | Bin .../gui2 => resources}/images/news/b92.png | Bin .../images/news/barrons.png | Bin .../gui2 => resources}/images/news/bbc.png | Bin .../gui2 => resources}/images/news/beta.png | Bin .../images/news/beta_en.png | Bin .../gui2 => resources}/images/news/blic.png | Bin .../gui2 => resources}/images/news/borba.png | Bin .../gui2 => resources}/images/news/carta.png | Bin .../images/news/chicago_breaking_news.png | Bin .../images/news/chr_mon.png | Bin .../gui2 => resources}/images/news/clarin.png | Bin .../gui2 => resources}/images/news/cnn.png | Bin .../images/news/coding_horror.png | Bin .../images/news/corriere_della_sera_en.png | Bin .../images/news/corriere_della_sera_it.png | Bin .../images/news/criticadigital.png | Bin .../images/news/cubadebate.png | Bin .../gui2 => resources}/images/news/danas.png | Bin .../images/news/darknet.png | Bin .../images/news/der_standard.png | Bin .../images/news/diepresse.png | Bin .../images/news/dnevni_avaz.png | Bin .../images/news/e_novine.png | Bin .../images/news/economist.png | Bin .../images/news/el_mercurio_chile.png | Bin .../images/news/el_universal.png | Bin .../images/news/elargentino.png | Bin .../images/news/elcronista.png | Bin .../images/news/elektrolese.png | Bin .../images/news/elmundo.png | Bin .../images/news/elperiodico_catalan.png | Bin .../images/news/elperiodico_spanish.png | Bin .../images/news/eltiempo_hn.png | Bin .../images/news/endgadget.png | Bin .../gui2 => resources}/images/news/espn.png | Bin .../images/news/esquire.png | Bin .../images/news/estadao.png | Bin .../images/news/expansion_spanish.png | Bin .../images/news/fastcompany.png | Bin .../gui2 => resources}/images/news/faznet.png | Bin .../images/news/freakonomics.png | Bin .../gui2 => resources}/images/news/ftd.png | Bin .../images/news/fudzilla.png | Bin .../images/news/glas_srpske.png | Bin .../images/news/glasjavnosti.png | Bin .../gui2 => resources}/images/news/granma.png | Bin .../gui2 => resources}/images/news/gva_be.png | Bin .../gui2 => resources}/images/news/heise.png | Bin .../gui2 => resources}/images/news/hln.png | Bin .../gui2 => resources}/images/news/hln_be.png | Bin .../gui2 => resources}/images/news/hna.png | Bin .../gui2 => resources}/images/news/hrt.png | Bin .../images/news/infobae.png | Bin .../images/news/inquirer_net.png | Bin .../images/news/instapaper.png | Bin .../images/news/jb_online.png | Bin .../images/news/joelonsoftware.png | Bin .../gui2 => resources}/images/news/jpost.png | Bin .../images/news/jutarnji.png | Bin .../images/news/krstarica.png | Bin .../images/news/krstarica_en.png | Bin .../images/news/la_cuarta.png | Bin .../images/news/la_tercera.png | Bin .../images/news/lanacion.png | Bin .../images/news/lanacion_chile.png | Bin .../images/news/laprensa_hn.png | Bin .../images/news/laprensa_ni.png | Bin .../images/news/latribuna.png | Bin .../images/news/le_monde.png | Bin .../images/news/liberation.png | Bin .../images/news/linux_magazine.png | Bin .../images/news/linuxdevices.png | Bin .../images/news/livemint.png | Bin .../images/news/miami_herald.png | Bin .../images/news/msdnmag_en.png | Bin .../images/news/nacional_cro.png | Bin .../gui2 => resources}/images/news/nasa.png | Bin .../images/news/new_yorker.png | Bin .../images/news/newsweek.png | Bin .../gui2 => resources}/images/news/noaa.png | Bin .../images/news/novosti.png | Bin .../gui2 => resources}/images/news/nspm.png | Bin .../images/news/nspm_int.png | Bin .../images/news/nytimes.png | Bin .../images/news/nytimes_sub.png | Bin .../images/news/nzz_ger.png | Bin .../images/news/o_globo.png | Bin .../images/news/pagina12.png | Bin .../images/news/pescanik.png | Bin .../images/news/pobjeda.png | Bin .../images/news/politico.png | Bin .../images/news/politika.png | Bin .../images/news/portfolio.png | Bin .../images/news/pressonline.png | Bin .../gui2 => resources}/images/news/rts.png | Bin .../images/news/sciencedaily.png | Bin .../images/news/scott_hanselman.png | Bin .../images/news/seattle_times.png | Bin .../images/news/security_watch.png | Bin .../images/news/soldiers.png | Bin .../images/news/spiegel_int.png | Bin .../images/news/spiegelde.png | Bin .../images/news/stackoverflow.png | Bin .../images/news/starbulletin.png | Bin .../images/news/straitstimes.png | Bin .../images/news/sueddeutsche.png | Bin .../gui2 => resources}/images/news/tanjug.png | Bin .../images/news/telegraph_uk.png | Bin .../images/news/telepolis.png | Bin .../images/news/telepolis_artikel.png | Bin .../images/news/teleread.png | Bin .../gui2 => resources}/images/news/the_oz.png | Bin .../images/news/theeconomictimes_india.png | Bin .../images/news/themarketticker.png | Bin .../images/news/theonion.png | Bin .../gui2 => resources}/images/news/tijd.png | Bin .../images/news/time_magazine.png | Bin .../images/news/times_online.png | Bin .../images/news/tomshardware.png | Bin .../images/news/tomshardware_de.png | Bin .../images/news/uncrate.png | Bin .../gui2 => resources}/images/news/usnews.png | Bin .../gui2 => resources}/images/news/utne.png | Bin .../images/news/vecernji_list.png | Bin .../images/news/vijesti.png | Bin .../gui2 => resources}/images/news/vreme.png | Bin .../images/news/wikinews_en.png | Bin .../gui2 => resources}/images/news/wired.png | Bin .../gui2 => resources}/images/news/wsj.png | Bin .../gui2 => resources}/images/news/zdnet.png | Bin .../gui2 => resources}/images/news/zeitde.png | Bin .../gui2 => resources}/images/next.svg | 0 {src/calibre/gui2 => resources}/images/ok.svg | 0 .../gui2 => resources}/images/page.svg | 0 .../gui2 => resources}/images/plugins.svg | 0 .../gui2 => resources}/images/plus.svg | 0 .../gui2 => resources}/images/previous.svg | 0 .../images/print-preview.svg | 0 .../gui2 => resources}/images/print.svg | 0 .../gui2 => resources}/images/publisher.png | Bin .../gui2 => resources}/images/reader.svg | 0 .../gui2 => resources}/images/save.svg | 0 .../gui2 => resources}/images/scheduler.svg | 0 {src/calibre/gui2 => resources}/images/sd.svg | 0 .../gui2 => resources}/images/search.svg | 0 .../gui2 => resources}/images/series.svg | 0 .../gui2 => resources}/images/swap.svg | 0 .../gui2 => resources}/images/sync.svg | 0 .../gui2 => resources}/images/tags.svg | 0 .../gui2 => resources}/images/trash.svg | 0 .../images/user_profile.svg | 0 .../gui2 => resources}/images/view.svg | 0 .../gui2 => resources}/images/viewer.svg | 0 .../images/welcome_wizard.svg | 0 .../images/window-close.svg | 0 .../gui2 => resources}/images/wizard.svg | 0 setup/commands.py | 4 + setup/gui.py | 97 ++++++++++++++++++ src/calibre/gui2/convert/debug.ui | 6 +- src/calibre/gui2/convert/look_and_feel.ui | 4 +- src/calibre/gui2/convert/metadata.ui | 10 +- src/calibre/gui2/convert/single.ui | 4 +- src/calibre/gui2/convert/xpath_edit.ui | 4 +- .../gui2/device_drivers/configwidget.ui | 6 +- src/calibre/gui2/dialogs/book_info.ui | 6 +- src/calibre/gui2/dialogs/choose_format.ui | 4 +- src/calibre/gui2/dialogs/comicconf.ui | 4 +- src/calibre/gui2/dialogs/config/config.ui | 24 ++--- src/calibre/gui2/dialogs/confirm_delete.ui | 6 +- src/calibre/gui2/dialogs/conversion_error.ui | 6 +- src/calibre/gui2/dialogs/fetch_metadata.ui | 4 +- src/calibre/gui2/dialogs/job_view.ui | 4 +- src/calibre/gui2/dialogs/jobs.ui | 4 +- src/calibre/gui2/dialogs/metadata_bulk.ui | 6 +- src/calibre/gui2/dialogs/metadata_single.ui | 26 ++--- src/calibre/gui2/dialogs/password.ui | 4 +- src/calibre/gui2/dialogs/progress.ui | 4 +- src/calibre/gui2/dialogs/scheduler.ui | 4 +- src/calibre/gui2/dialogs/search.ui | 4 +- src/calibre/gui2/dialogs/tag_editor.ui | 12 +-- src/calibre/gui2/dialogs/test_email.ui | 4 +- src/calibre/gui2/dialogs/user_profiles.ui | 22 ++-- src/calibre/gui2/lrf_renderer/config.ui | 4 +- src/calibre/gui2/lrf_renderer/main.ui | 16 +-- src/calibre/gui2/main.ui | 40 ++++---- src/calibre/gui2/viewer/config.ui | 4 +- src/calibre/gui2/viewer/main.ui | 36 +++---- src/calibre/gui2/wizard/device.ui | 4 +- src/calibre/gui2/wizard/send_email.ui | 4 +- src/calibre/utils/localization.py | 7 +- 257 files changed, 254 insertions(+), 164 deletions(-) rename {src/calibre/gui2 => resources}/images/add_book.svg (100%) rename {src/calibre/gui2 => resources}/images/arrow-down.svg (100%) rename {src/calibre/gui2 => resources}/images/arrow-up.svg (100%) rename {src/calibre/gui2 => resources}/images/auto_author_sort.svg (100%) rename {src/calibre/gui2 => resources}/images/back.svg (100%) rename {src/calibre/gui2 => resources}/images/book.svg (100%) rename {src/calibre/gui2 => resources}/images/bookmarks.svg (100%) rename {src/calibre/gui2 => resources}/images/books_in_series.svg (100%) rename {src/calibre/gui2 => resources}/images/chapters.svg (100%) rename {src/calibre/gui2 => resources}/images/clear_left.svg (100%) rename {src/calibre/gui2 => resources}/images/config.svg (100%) rename {src/calibre/gui2 => resources}/images/convert.svg (100%) rename {src/calibre/gui2 => resources}/images/cover_flow.svg (100%) rename {src/calibre/gui2 => resources}/images/debug.svg (100%) rename {src/calibre/gui2 => resources}/images/dialog_error.svg (100%) rename {src/calibre/gui2 => resources}/images/dialog_information.svg (100%) rename {src/calibre/gui2 => resources}/images/dialog_warning.svg (100%) rename {src/calibre/gui2 => resources}/images/document_open.svg (100%) rename {src/calibre/gui2 => resources}/images/donate.svg (100%) rename {src/calibre/gui2 => resources}/images/edit_input.svg (100%) rename {src/calibre/gui2 => resources}/images/eject.svg (100%) rename {src/calibre/gui2 => resources}/images/exec.svg (100%) rename {src/calibre/gui2 => resources}/images/font_size_larger.svg (100%) rename {src/calibre/gui2 => resources}/images/font_size_smaller.svg (100%) rename {src/calibre/gui2 => resources}/images/forward.svg (100%) rename {src/calibre/gui2 => resources}/images/gmail_logo.png (100%) rename {src/calibre/gui2 => resources}/images/jobs-animated.mng (100%) rename {src/calibre/gui2 => resources}/images/jobs.svg (100%) rename {src/calibre/gui2 => resources}/images/library.png (100%) rename {src/calibre/gui2 => resources}/images/list_remove.svg (100%) rename {src/calibre/gui2 => resources}/images/lookfeel.svg (100%) rename {src/calibre/gui2 => resources}/images/mail.svg (100%) rename {src/calibre/gui2 => resources}/images/metadata.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/bmp.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/computer.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/dir.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/epub.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/fb2.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/gif.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/html.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/jpeg.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/lit.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/lrf.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/lrx.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/mobi.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/pdf.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/png.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/rar.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/rtf.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/svg.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/txt.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/unknown.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/zero.svg (100%) rename {src/calibre/gui2 => resources}/images/mimetypes/zip.svg (100%) rename {src/calibre/gui2 => resources}/images/minus.svg (100%) rename {src/calibre/gui2 => resources}/images/network-server.svg (100%) rename {src/calibre/gui2 => resources}/images/news.svg (100%) rename {src/calibre/gui2 => resources}/images/news/24sata.png (100%) rename {src/calibre/gui2 => resources}/images/news/24sata_rs.png (100%) rename {src/calibre/gui2 => resources}/images/news/adventuregamers.png (100%) rename {src/calibre/gui2 => resources}/images/news/ambito.png (100%) rename {src/calibre/gui2 => resources}/images/news/amspec.png (100%) rename {src/calibre/gui2 => resources}/images/news/ars_technica.png (100%) rename {src/calibre/gui2 => resources}/images/news/atlantic.png (100%) rename {src/calibre/gui2 => resources}/images/news/axxon_news.png (100%) rename {src/calibre/gui2 => resources}/images/news/azstarnet.png (100%) rename {src/calibre/gui2 => resources}/images/news/b92.png (100%) rename {src/calibre/gui2 => resources}/images/news/barrons.png (100%) rename {src/calibre/gui2 => resources}/images/news/bbc.png (100%) rename {src/calibre/gui2 => resources}/images/news/beta.png (100%) rename {src/calibre/gui2 => resources}/images/news/beta_en.png (100%) rename {src/calibre/gui2 => resources}/images/news/blic.png (100%) rename {src/calibre/gui2 => resources}/images/news/borba.png (100%) rename {src/calibre/gui2 => resources}/images/news/carta.png (100%) rename {src/calibre/gui2 => resources}/images/news/chicago_breaking_news.png (100%) rename {src/calibre/gui2 => resources}/images/news/chr_mon.png (100%) rename {src/calibre/gui2 => resources}/images/news/clarin.png (100%) rename {src/calibre/gui2 => resources}/images/news/cnn.png (100%) rename {src/calibre/gui2 => resources}/images/news/coding_horror.png (100%) rename {src/calibre/gui2 => resources}/images/news/corriere_della_sera_en.png (100%) rename {src/calibre/gui2 => resources}/images/news/corriere_della_sera_it.png (100%) rename {src/calibre/gui2 => resources}/images/news/criticadigital.png (100%) rename {src/calibre/gui2 => resources}/images/news/cubadebate.png (100%) rename {src/calibre/gui2 => resources}/images/news/danas.png (100%) rename {src/calibre/gui2 => resources}/images/news/darknet.png (100%) rename {src/calibre/gui2 => resources}/images/news/der_standard.png (100%) rename {src/calibre/gui2 => resources}/images/news/diepresse.png (100%) rename {src/calibre/gui2 => resources}/images/news/dnevni_avaz.png (100%) rename {src/calibre/gui2 => resources}/images/news/e_novine.png (100%) rename {src/calibre/gui2 => resources}/images/news/economist.png (100%) rename {src/calibre/gui2 => resources}/images/news/el_mercurio_chile.png (100%) rename {src/calibre/gui2 => resources}/images/news/el_universal.png (100%) rename {src/calibre/gui2 => resources}/images/news/elargentino.png (100%) rename {src/calibre/gui2 => resources}/images/news/elcronista.png (100%) rename {src/calibre/gui2 => resources}/images/news/elektrolese.png (100%) rename {src/calibre/gui2 => resources}/images/news/elmundo.png (100%) rename {src/calibre/gui2 => resources}/images/news/elperiodico_catalan.png (100%) rename {src/calibre/gui2 => resources}/images/news/elperiodico_spanish.png (100%) rename {src/calibre/gui2 => resources}/images/news/eltiempo_hn.png (100%) rename {src/calibre/gui2 => resources}/images/news/endgadget.png (100%) rename {src/calibre/gui2 => resources}/images/news/espn.png (100%) rename {src/calibre/gui2 => resources}/images/news/esquire.png (100%) rename {src/calibre/gui2 => resources}/images/news/estadao.png (100%) rename {src/calibre/gui2 => resources}/images/news/expansion_spanish.png (100%) rename {src/calibre/gui2 => resources}/images/news/fastcompany.png (100%) rename {src/calibre/gui2 => resources}/images/news/faznet.png (100%) rename {src/calibre/gui2 => resources}/images/news/freakonomics.png (100%) rename {src/calibre/gui2 => resources}/images/news/ftd.png (100%) rename {src/calibre/gui2 => resources}/images/news/fudzilla.png (100%) rename {src/calibre/gui2 => resources}/images/news/glas_srpske.png (100%) rename {src/calibre/gui2 => resources}/images/news/glasjavnosti.png (100%) rename {src/calibre/gui2 => resources}/images/news/granma.png (100%) rename {src/calibre/gui2 => resources}/images/news/gva_be.png (100%) rename {src/calibre/gui2 => resources}/images/news/heise.png (100%) rename {src/calibre/gui2 => resources}/images/news/hln.png (100%) rename {src/calibre/gui2 => resources}/images/news/hln_be.png (100%) rename {src/calibre/gui2 => resources}/images/news/hna.png (100%) rename {src/calibre/gui2 => resources}/images/news/hrt.png (100%) rename {src/calibre/gui2 => resources}/images/news/infobae.png (100%) rename {src/calibre/gui2 => resources}/images/news/inquirer_net.png (100%) rename {src/calibre/gui2 => resources}/images/news/instapaper.png (100%) rename {src/calibre/gui2 => resources}/images/news/jb_online.png (100%) rename {src/calibre/gui2 => resources}/images/news/joelonsoftware.png (100%) rename {src/calibre/gui2 => resources}/images/news/jpost.png (100%) rename {src/calibre/gui2 => resources}/images/news/jutarnji.png (100%) rename {src/calibre/gui2 => resources}/images/news/krstarica.png (100%) rename {src/calibre/gui2 => resources}/images/news/krstarica_en.png (100%) rename {src/calibre/gui2 => resources}/images/news/la_cuarta.png (100%) rename {src/calibre/gui2 => resources}/images/news/la_tercera.png (100%) rename {src/calibre/gui2 => resources}/images/news/lanacion.png (100%) rename {src/calibre/gui2 => resources}/images/news/lanacion_chile.png (100%) rename {src/calibre/gui2 => resources}/images/news/laprensa_hn.png (100%) rename {src/calibre/gui2 => resources}/images/news/laprensa_ni.png (100%) rename {src/calibre/gui2 => resources}/images/news/latribuna.png (100%) rename {src/calibre/gui2 => resources}/images/news/le_monde.png (100%) rename {src/calibre/gui2 => resources}/images/news/liberation.png (100%) rename {src/calibre/gui2 => resources}/images/news/linux_magazine.png (100%) rename {src/calibre/gui2 => resources}/images/news/linuxdevices.png (100%) rename {src/calibre/gui2 => resources}/images/news/livemint.png (100%) rename {src/calibre/gui2 => resources}/images/news/miami_herald.png (100%) rename {src/calibre/gui2 => resources}/images/news/msdnmag_en.png (100%) rename {src/calibre/gui2 => resources}/images/news/nacional_cro.png (100%) rename {src/calibre/gui2 => resources}/images/news/nasa.png (100%) rename {src/calibre/gui2 => resources}/images/news/new_yorker.png (100%) rename {src/calibre/gui2 => resources}/images/news/newsweek.png (100%) rename {src/calibre/gui2 => resources}/images/news/noaa.png (100%) rename {src/calibre/gui2 => resources}/images/news/novosti.png (100%) rename {src/calibre/gui2 => resources}/images/news/nspm.png (100%) rename {src/calibre/gui2 => resources}/images/news/nspm_int.png (100%) rename {src/calibre/gui2 => resources}/images/news/nytimes.png (100%) rename {src/calibre/gui2 => resources}/images/news/nytimes_sub.png (100%) rename {src/calibre/gui2 => resources}/images/news/nzz_ger.png (100%) rename {src/calibre/gui2 => resources}/images/news/o_globo.png (100%) rename {src/calibre/gui2 => resources}/images/news/pagina12.png (100%) rename {src/calibre/gui2 => resources}/images/news/pescanik.png (100%) rename {src/calibre/gui2 => resources}/images/news/pobjeda.png (100%) rename {src/calibre/gui2 => resources}/images/news/politico.png (100%) rename {src/calibre/gui2 => resources}/images/news/politika.png (100%) rename {src/calibre/gui2 => resources}/images/news/portfolio.png (100%) rename {src/calibre/gui2 => resources}/images/news/pressonline.png (100%) rename {src/calibre/gui2 => resources}/images/news/rts.png (100%) rename {src/calibre/gui2 => resources}/images/news/sciencedaily.png (100%) rename {src/calibre/gui2 => resources}/images/news/scott_hanselman.png (100%) rename {src/calibre/gui2 => resources}/images/news/seattle_times.png (100%) rename {src/calibre/gui2 => resources}/images/news/security_watch.png (100%) rename {src/calibre/gui2 => resources}/images/news/soldiers.png (100%) rename {src/calibre/gui2 => resources}/images/news/spiegel_int.png (100%) rename {src/calibre/gui2 => resources}/images/news/spiegelde.png (100%) rename {src/calibre/gui2 => resources}/images/news/stackoverflow.png (100%) rename {src/calibre/gui2 => resources}/images/news/starbulletin.png (100%) rename {src/calibre/gui2 => resources}/images/news/straitstimes.png (100%) rename {src/calibre/gui2 => resources}/images/news/sueddeutsche.png (100%) rename {src/calibre/gui2 => resources}/images/news/tanjug.png (100%) rename {src/calibre/gui2 => resources}/images/news/telegraph_uk.png (100%) rename {src/calibre/gui2 => resources}/images/news/telepolis.png (100%) rename {src/calibre/gui2 => resources}/images/news/telepolis_artikel.png (100%) rename {src/calibre/gui2 => resources}/images/news/teleread.png (100%) rename {src/calibre/gui2 => resources}/images/news/the_oz.png (100%) rename {src/calibre/gui2 => resources}/images/news/theeconomictimes_india.png (100%) rename {src/calibre/gui2 => resources}/images/news/themarketticker.png (100%) rename {src/calibre/gui2 => resources}/images/news/theonion.png (100%) rename {src/calibre/gui2 => resources}/images/news/tijd.png (100%) rename {src/calibre/gui2 => resources}/images/news/time_magazine.png (100%) rename {src/calibre/gui2 => resources}/images/news/times_online.png (100%) rename {src/calibre/gui2 => resources}/images/news/tomshardware.png (100%) rename {src/calibre/gui2 => resources}/images/news/tomshardware_de.png (100%) rename {src/calibre/gui2 => resources}/images/news/uncrate.png (100%) rename {src/calibre/gui2 => resources}/images/news/usnews.png (100%) rename {src/calibre/gui2 => resources}/images/news/utne.png (100%) rename {src/calibre/gui2 => resources}/images/news/vecernji_list.png (100%) rename {src/calibre/gui2 => resources}/images/news/vijesti.png (100%) rename {src/calibre/gui2 => resources}/images/news/vreme.png (100%) rename {src/calibre/gui2 => resources}/images/news/wikinews_en.png (100%) rename {src/calibre/gui2 => resources}/images/news/wired.png (100%) rename {src/calibre/gui2 => resources}/images/news/wsj.png (100%) rename {src/calibre/gui2 => resources}/images/news/zdnet.png (100%) rename {src/calibre/gui2 => resources}/images/news/zeitde.png (100%) rename {src/calibre/gui2 => resources}/images/next.svg (100%) rename {src/calibre/gui2 => resources}/images/ok.svg (100%) rename {src/calibre/gui2 => resources}/images/page.svg (100%) rename {src/calibre/gui2 => resources}/images/plugins.svg (100%) rename {src/calibre/gui2 => resources}/images/plus.svg (100%) rename {src/calibre/gui2 => resources}/images/previous.svg (100%) rename {src/calibre/gui2 => resources}/images/print-preview.svg (100%) rename {src/calibre/gui2 => resources}/images/print.svg (100%) rename {src/calibre/gui2 => resources}/images/publisher.png (100%) rename {src/calibre/gui2 => resources}/images/reader.svg (100%) rename {src/calibre/gui2 => resources}/images/save.svg (100%) rename {src/calibre/gui2 => resources}/images/scheduler.svg (100%) rename {src/calibre/gui2 => resources}/images/sd.svg (100%) rename {src/calibre/gui2 => resources}/images/search.svg (100%) rename {src/calibre/gui2 => resources}/images/series.svg (100%) rename {src/calibre/gui2 => resources}/images/swap.svg (100%) rename {src/calibre/gui2 => resources}/images/sync.svg (100%) rename {src/calibre/gui2 => resources}/images/tags.svg (100%) rename {src/calibre/gui2 => resources}/images/trash.svg (100%) rename {src/calibre/gui2 => resources}/images/user_profile.svg (100%) rename {src/calibre/gui2 => resources}/images/view.svg (100%) rename {src/calibre/gui2 => resources}/images/viewer.svg (100%) rename {src/calibre/gui2 => resources}/images/welcome_wizard.svg (100%) rename {src/calibre/gui2 => resources}/images/window-close.svg (100%) rename {src/calibre/gui2 => resources}/images/wizard.svg (100%) create mode 100644 setup/gui.py diff --git a/.bzrignore b/.bzrignore index 081ed9f5ca..5ae1ec3117 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,30 +1,16 @@ *_ui.py moc_*.cpp src/calibre/plugins -src/calibre/gui2/pictureflow/*.so* -src/calibre/gui2/pictureflow/PyQt/.build/ -src/calibre/gui2/pictureflow/Makefile -src/calibre.egg-info/ -src/calibre/resources.py -src/calibre/gui2/images.qrc -src/calibre/gui2/images_rc.py +resources/images.qrc src/calibre/manual/.build/ src/calibre/manual/cli/ build dist docs -resources -nbproject/ -src/calibre/gui2/pictureflow/Makefile.Debug -src/calibre/gui2/pictureflow/Makefile.Release -src/calibre/gui2/pictureflow/debug/ -src/calibre/gui2/pictureflow/pictureflow_resource.rc -src/calibre/gui2/pictureflow/release/ -src/calibre/translations/compiled.py +resources/localization +resources/images.qrc installer/windows/calibre/build.log src/calibre/translations/.errors -src/calibre/plugins/* -src/calibre/gui2/pictureflow/.build src/cssutils/.svn/ src/cssutils/_todo/ src/cssutils/scripts/ diff --git a/src/calibre/gui2/images/add_book.svg b/resources/images/add_book.svg similarity index 100% rename from src/calibre/gui2/images/add_book.svg rename to resources/images/add_book.svg diff --git a/src/calibre/gui2/images/arrow-down.svg b/resources/images/arrow-down.svg similarity index 100% rename from src/calibre/gui2/images/arrow-down.svg rename to resources/images/arrow-down.svg diff --git a/src/calibre/gui2/images/arrow-up.svg b/resources/images/arrow-up.svg similarity index 100% rename from src/calibre/gui2/images/arrow-up.svg rename to resources/images/arrow-up.svg diff --git a/src/calibre/gui2/images/auto_author_sort.svg b/resources/images/auto_author_sort.svg similarity index 100% rename from src/calibre/gui2/images/auto_author_sort.svg rename to resources/images/auto_author_sort.svg diff --git a/src/calibre/gui2/images/back.svg b/resources/images/back.svg similarity index 100% rename from src/calibre/gui2/images/back.svg rename to resources/images/back.svg diff --git a/src/calibre/gui2/images/book.svg b/resources/images/book.svg similarity index 100% rename from src/calibre/gui2/images/book.svg rename to resources/images/book.svg diff --git a/src/calibre/gui2/images/bookmarks.svg b/resources/images/bookmarks.svg similarity index 100% rename from src/calibre/gui2/images/bookmarks.svg rename to resources/images/bookmarks.svg diff --git a/src/calibre/gui2/images/books_in_series.svg b/resources/images/books_in_series.svg similarity index 100% rename from src/calibre/gui2/images/books_in_series.svg rename to resources/images/books_in_series.svg diff --git a/src/calibre/gui2/images/chapters.svg b/resources/images/chapters.svg similarity index 100% rename from src/calibre/gui2/images/chapters.svg rename to resources/images/chapters.svg diff --git a/src/calibre/gui2/images/clear_left.svg b/resources/images/clear_left.svg similarity index 100% rename from src/calibre/gui2/images/clear_left.svg rename to resources/images/clear_left.svg diff --git a/src/calibre/gui2/images/config.svg b/resources/images/config.svg similarity index 100% rename from src/calibre/gui2/images/config.svg rename to resources/images/config.svg diff --git a/src/calibre/gui2/images/convert.svg b/resources/images/convert.svg similarity index 100% rename from src/calibre/gui2/images/convert.svg rename to resources/images/convert.svg diff --git a/src/calibre/gui2/images/cover_flow.svg b/resources/images/cover_flow.svg similarity index 100% rename from src/calibre/gui2/images/cover_flow.svg rename to resources/images/cover_flow.svg diff --git a/src/calibre/gui2/images/debug.svg b/resources/images/debug.svg similarity index 100% rename from src/calibre/gui2/images/debug.svg rename to resources/images/debug.svg diff --git a/src/calibre/gui2/images/dialog_error.svg b/resources/images/dialog_error.svg similarity index 100% rename from src/calibre/gui2/images/dialog_error.svg rename to resources/images/dialog_error.svg diff --git a/src/calibre/gui2/images/dialog_information.svg b/resources/images/dialog_information.svg similarity index 100% rename from src/calibre/gui2/images/dialog_information.svg rename to resources/images/dialog_information.svg diff --git a/src/calibre/gui2/images/dialog_warning.svg b/resources/images/dialog_warning.svg similarity index 100% rename from src/calibre/gui2/images/dialog_warning.svg rename to resources/images/dialog_warning.svg diff --git a/src/calibre/gui2/images/document_open.svg b/resources/images/document_open.svg similarity index 100% rename from src/calibre/gui2/images/document_open.svg rename to resources/images/document_open.svg diff --git a/src/calibre/gui2/images/donate.svg b/resources/images/donate.svg similarity index 100% rename from src/calibre/gui2/images/donate.svg rename to resources/images/donate.svg diff --git a/src/calibre/gui2/images/edit_input.svg b/resources/images/edit_input.svg similarity index 100% rename from src/calibre/gui2/images/edit_input.svg rename to resources/images/edit_input.svg diff --git a/src/calibre/gui2/images/eject.svg b/resources/images/eject.svg similarity index 100% rename from src/calibre/gui2/images/eject.svg rename to resources/images/eject.svg diff --git a/src/calibre/gui2/images/exec.svg b/resources/images/exec.svg similarity index 100% rename from src/calibre/gui2/images/exec.svg rename to resources/images/exec.svg diff --git a/src/calibre/gui2/images/font_size_larger.svg b/resources/images/font_size_larger.svg similarity index 100% rename from src/calibre/gui2/images/font_size_larger.svg rename to resources/images/font_size_larger.svg diff --git a/src/calibre/gui2/images/font_size_smaller.svg b/resources/images/font_size_smaller.svg similarity index 100% rename from src/calibre/gui2/images/font_size_smaller.svg rename to resources/images/font_size_smaller.svg diff --git a/src/calibre/gui2/images/forward.svg b/resources/images/forward.svg similarity index 100% rename from src/calibre/gui2/images/forward.svg rename to resources/images/forward.svg diff --git a/src/calibre/gui2/images/gmail_logo.png b/resources/images/gmail_logo.png similarity index 100% rename from src/calibre/gui2/images/gmail_logo.png rename to resources/images/gmail_logo.png diff --git a/src/calibre/gui2/images/jobs-animated.mng b/resources/images/jobs-animated.mng similarity index 100% rename from src/calibre/gui2/images/jobs-animated.mng rename to resources/images/jobs-animated.mng diff --git a/src/calibre/gui2/images/jobs.svg b/resources/images/jobs.svg similarity index 100% rename from src/calibre/gui2/images/jobs.svg rename to resources/images/jobs.svg diff --git a/src/calibre/gui2/images/library.png b/resources/images/library.png similarity index 100% rename from src/calibre/gui2/images/library.png rename to resources/images/library.png diff --git a/src/calibre/gui2/images/list_remove.svg b/resources/images/list_remove.svg similarity index 100% rename from src/calibre/gui2/images/list_remove.svg rename to resources/images/list_remove.svg diff --git a/src/calibre/gui2/images/lookfeel.svg b/resources/images/lookfeel.svg similarity index 100% rename from src/calibre/gui2/images/lookfeel.svg rename to resources/images/lookfeel.svg diff --git a/src/calibre/gui2/images/mail.svg b/resources/images/mail.svg similarity index 100% rename from src/calibre/gui2/images/mail.svg rename to resources/images/mail.svg diff --git a/src/calibre/gui2/images/metadata.svg b/resources/images/metadata.svg similarity index 100% rename from src/calibre/gui2/images/metadata.svg rename to resources/images/metadata.svg diff --git a/src/calibre/gui2/images/mimetypes/bmp.svg b/resources/images/mimetypes/bmp.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/bmp.svg rename to resources/images/mimetypes/bmp.svg diff --git a/src/calibre/gui2/images/mimetypes/computer.svg b/resources/images/mimetypes/computer.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/computer.svg rename to resources/images/mimetypes/computer.svg diff --git a/src/calibre/gui2/images/mimetypes/dir.svg b/resources/images/mimetypes/dir.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/dir.svg rename to resources/images/mimetypes/dir.svg diff --git a/src/calibre/gui2/images/mimetypes/epub.svg b/resources/images/mimetypes/epub.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/epub.svg rename to resources/images/mimetypes/epub.svg diff --git a/src/calibre/gui2/images/mimetypes/fb2.svg b/resources/images/mimetypes/fb2.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/fb2.svg rename to resources/images/mimetypes/fb2.svg diff --git a/src/calibre/gui2/images/mimetypes/gif.svg b/resources/images/mimetypes/gif.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/gif.svg rename to resources/images/mimetypes/gif.svg diff --git a/src/calibre/gui2/images/mimetypes/html.svg b/resources/images/mimetypes/html.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/html.svg rename to resources/images/mimetypes/html.svg diff --git a/src/calibre/gui2/images/mimetypes/jpeg.svg b/resources/images/mimetypes/jpeg.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/jpeg.svg rename to resources/images/mimetypes/jpeg.svg diff --git a/src/calibre/gui2/images/mimetypes/lit.svg b/resources/images/mimetypes/lit.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/lit.svg rename to resources/images/mimetypes/lit.svg diff --git a/src/calibre/gui2/images/mimetypes/lrf.svg b/resources/images/mimetypes/lrf.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/lrf.svg rename to resources/images/mimetypes/lrf.svg diff --git a/src/calibre/gui2/images/mimetypes/lrx.svg b/resources/images/mimetypes/lrx.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/lrx.svg rename to resources/images/mimetypes/lrx.svg diff --git a/src/calibre/gui2/images/mimetypes/mobi.svg b/resources/images/mimetypes/mobi.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/mobi.svg rename to resources/images/mimetypes/mobi.svg diff --git a/src/calibre/gui2/images/mimetypes/pdf.svg b/resources/images/mimetypes/pdf.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/pdf.svg rename to resources/images/mimetypes/pdf.svg diff --git a/src/calibre/gui2/images/mimetypes/png.svg b/resources/images/mimetypes/png.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/png.svg rename to resources/images/mimetypes/png.svg diff --git a/src/calibre/gui2/images/mimetypes/rar.svg b/resources/images/mimetypes/rar.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/rar.svg rename to resources/images/mimetypes/rar.svg diff --git a/src/calibre/gui2/images/mimetypes/rtf.svg b/resources/images/mimetypes/rtf.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/rtf.svg rename to resources/images/mimetypes/rtf.svg diff --git a/src/calibre/gui2/images/mimetypes/svg.svg b/resources/images/mimetypes/svg.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/svg.svg rename to resources/images/mimetypes/svg.svg diff --git a/src/calibre/gui2/images/mimetypes/txt.svg b/resources/images/mimetypes/txt.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/txt.svg rename to resources/images/mimetypes/txt.svg diff --git a/src/calibre/gui2/images/mimetypes/unknown.svg b/resources/images/mimetypes/unknown.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/unknown.svg rename to resources/images/mimetypes/unknown.svg diff --git a/src/calibre/gui2/images/mimetypes/zero.svg b/resources/images/mimetypes/zero.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/zero.svg rename to resources/images/mimetypes/zero.svg diff --git a/src/calibre/gui2/images/mimetypes/zip.svg b/resources/images/mimetypes/zip.svg similarity index 100% rename from src/calibre/gui2/images/mimetypes/zip.svg rename to resources/images/mimetypes/zip.svg diff --git a/src/calibre/gui2/images/minus.svg b/resources/images/minus.svg similarity index 100% rename from src/calibre/gui2/images/minus.svg rename to resources/images/minus.svg diff --git a/src/calibre/gui2/images/network-server.svg b/resources/images/network-server.svg similarity index 100% rename from src/calibre/gui2/images/network-server.svg rename to resources/images/network-server.svg diff --git a/src/calibre/gui2/images/news.svg b/resources/images/news.svg similarity index 100% rename from src/calibre/gui2/images/news.svg rename to resources/images/news.svg diff --git a/src/calibre/gui2/images/news/24sata.png b/resources/images/news/24sata.png similarity index 100% rename from src/calibre/gui2/images/news/24sata.png rename to resources/images/news/24sata.png diff --git a/src/calibre/gui2/images/news/24sata_rs.png b/resources/images/news/24sata_rs.png similarity index 100% rename from src/calibre/gui2/images/news/24sata_rs.png rename to resources/images/news/24sata_rs.png diff --git a/src/calibre/gui2/images/news/adventuregamers.png b/resources/images/news/adventuregamers.png similarity index 100% rename from src/calibre/gui2/images/news/adventuregamers.png rename to resources/images/news/adventuregamers.png diff --git a/src/calibre/gui2/images/news/ambito.png b/resources/images/news/ambito.png similarity index 100% rename from src/calibre/gui2/images/news/ambito.png rename to resources/images/news/ambito.png diff --git a/src/calibre/gui2/images/news/amspec.png b/resources/images/news/amspec.png similarity index 100% rename from src/calibre/gui2/images/news/amspec.png rename to resources/images/news/amspec.png diff --git a/src/calibre/gui2/images/news/ars_technica.png b/resources/images/news/ars_technica.png similarity index 100% rename from src/calibre/gui2/images/news/ars_technica.png rename to resources/images/news/ars_technica.png diff --git a/src/calibre/gui2/images/news/atlantic.png b/resources/images/news/atlantic.png similarity index 100% rename from src/calibre/gui2/images/news/atlantic.png rename to resources/images/news/atlantic.png diff --git a/src/calibre/gui2/images/news/axxon_news.png b/resources/images/news/axxon_news.png similarity index 100% rename from src/calibre/gui2/images/news/axxon_news.png rename to resources/images/news/axxon_news.png diff --git a/src/calibre/gui2/images/news/azstarnet.png b/resources/images/news/azstarnet.png similarity index 100% rename from src/calibre/gui2/images/news/azstarnet.png rename to resources/images/news/azstarnet.png diff --git a/src/calibre/gui2/images/news/b92.png b/resources/images/news/b92.png similarity index 100% rename from src/calibre/gui2/images/news/b92.png rename to resources/images/news/b92.png diff --git a/src/calibre/gui2/images/news/barrons.png b/resources/images/news/barrons.png similarity index 100% rename from src/calibre/gui2/images/news/barrons.png rename to resources/images/news/barrons.png diff --git a/src/calibre/gui2/images/news/bbc.png b/resources/images/news/bbc.png similarity index 100% rename from src/calibre/gui2/images/news/bbc.png rename to resources/images/news/bbc.png diff --git a/src/calibre/gui2/images/news/beta.png b/resources/images/news/beta.png similarity index 100% rename from src/calibre/gui2/images/news/beta.png rename to resources/images/news/beta.png diff --git a/src/calibre/gui2/images/news/beta_en.png b/resources/images/news/beta_en.png similarity index 100% rename from src/calibre/gui2/images/news/beta_en.png rename to resources/images/news/beta_en.png diff --git a/src/calibre/gui2/images/news/blic.png b/resources/images/news/blic.png similarity index 100% rename from src/calibre/gui2/images/news/blic.png rename to resources/images/news/blic.png diff --git a/src/calibre/gui2/images/news/borba.png b/resources/images/news/borba.png similarity index 100% rename from src/calibre/gui2/images/news/borba.png rename to resources/images/news/borba.png diff --git a/src/calibre/gui2/images/news/carta.png b/resources/images/news/carta.png similarity index 100% rename from src/calibre/gui2/images/news/carta.png rename to resources/images/news/carta.png diff --git a/src/calibre/gui2/images/news/chicago_breaking_news.png b/resources/images/news/chicago_breaking_news.png similarity index 100% rename from src/calibre/gui2/images/news/chicago_breaking_news.png rename to resources/images/news/chicago_breaking_news.png diff --git a/src/calibre/gui2/images/news/chr_mon.png b/resources/images/news/chr_mon.png similarity index 100% rename from src/calibre/gui2/images/news/chr_mon.png rename to resources/images/news/chr_mon.png diff --git a/src/calibre/gui2/images/news/clarin.png b/resources/images/news/clarin.png similarity index 100% rename from src/calibre/gui2/images/news/clarin.png rename to resources/images/news/clarin.png diff --git a/src/calibre/gui2/images/news/cnn.png b/resources/images/news/cnn.png similarity index 100% rename from src/calibre/gui2/images/news/cnn.png rename to resources/images/news/cnn.png diff --git a/src/calibre/gui2/images/news/coding_horror.png b/resources/images/news/coding_horror.png similarity index 100% rename from src/calibre/gui2/images/news/coding_horror.png rename to resources/images/news/coding_horror.png diff --git a/src/calibre/gui2/images/news/corriere_della_sera_en.png b/resources/images/news/corriere_della_sera_en.png similarity index 100% rename from src/calibre/gui2/images/news/corriere_della_sera_en.png rename to resources/images/news/corriere_della_sera_en.png diff --git a/src/calibre/gui2/images/news/corriere_della_sera_it.png b/resources/images/news/corriere_della_sera_it.png similarity index 100% rename from src/calibre/gui2/images/news/corriere_della_sera_it.png rename to resources/images/news/corriere_della_sera_it.png diff --git a/src/calibre/gui2/images/news/criticadigital.png b/resources/images/news/criticadigital.png similarity index 100% rename from src/calibre/gui2/images/news/criticadigital.png rename to resources/images/news/criticadigital.png diff --git a/src/calibre/gui2/images/news/cubadebate.png b/resources/images/news/cubadebate.png similarity index 100% rename from src/calibre/gui2/images/news/cubadebate.png rename to resources/images/news/cubadebate.png diff --git a/src/calibre/gui2/images/news/danas.png b/resources/images/news/danas.png similarity index 100% rename from src/calibre/gui2/images/news/danas.png rename to resources/images/news/danas.png diff --git a/src/calibre/gui2/images/news/darknet.png b/resources/images/news/darknet.png similarity index 100% rename from src/calibre/gui2/images/news/darknet.png rename to resources/images/news/darknet.png diff --git a/src/calibre/gui2/images/news/der_standard.png b/resources/images/news/der_standard.png similarity index 100% rename from src/calibre/gui2/images/news/der_standard.png rename to resources/images/news/der_standard.png diff --git a/src/calibre/gui2/images/news/diepresse.png b/resources/images/news/diepresse.png similarity index 100% rename from src/calibre/gui2/images/news/diepresse.png rename to resources/images/news/diepresse.png diff --git a/src/calibre/gui2/images/news/dnevni_avaz.png b/resources/images/news/dnevni_avaz.png similarity index 100% rename from src/calibre/gui2/images/news/dnevni_avaz.png rename to resources/images/news/dnevni_avaz.png diff --git a/src/calibre/gui2/images/news/e_novine.png b/resources/images/news/e_novine.png similarity index 100% rename from src/calibre/gui2/images/news/e_novine.png rename to resources/images/news/e_novine.png diff --git a/src/calibre/gui2/images/news/economist.png b/resources/images/news/economist.png similarity index 100% rename from src/calibre/gui2/images/news/economist.png rename to resources/images/news/economist.png diff --git a/src/calibre/gui2/images/news/el_mercurio_chile.png b/resources/images/news/el_mercurio_chile.png similarity index 100% rename from src/calibre/gui2/images/news/el_mercurio_chile.png rename to resources/images/news/el_mercurio_chile.png diff --git a/src/calibre/gui2/images/news/el_universal.png b/resources/images/news/el_universal.png similarity index 100% rename from src/calibre/gui2/images/news/el_universal.png rename to resources/images/news/el_universal.png diff --git a/src/calibre/gui2/images/news/elargentino.png b/resources/images/news/elargentino.png similarity index 100% rename from src/calibre/gui2/images/news/elargentino.png rename to resources/images/news/elargentino.png diff --git a/src/calibre/gui2/images/news/elcronista.png b/resources/images/news/elcronista.png similarity index 100% rename from src/calibre/gui2/images/news/elcronista.png rename to resources/images/news/elcronista.png diff --git a/src/calibre/gui2/images/news/elektrolese.png b/resources/images/news/elektrolese.png similarity index 100% rename from src/calibre/gui2/images/news/elektrolese.png rename to resources/images/news/elektrolese.png diff --git a/src/calibre/gui2/images/news/elmundo.png b/resources/images/news/elmundo.png similarity index 100% rename from src/calibre/gui2/images/news/elmundo.png rename to resources/images/news/elmundo.png diff --git a/src/calibre/gui2/images/news/elperiodico_catalan.png b/resources/images/news/elperiodico_catalan.png similarity index 100% rename from src/calibre/gui2/images/news/elperiodico_catalan.png rename to resources/images/news/elperiodico_catalan.png diff --git a/src/calibre/gui2/images/news/elperiodico_spanish.png b/resources/images/news/elperiodico_spanish.png similarity index 100% rename from src/calibre/gui2/images/news/elperiodico_spanish.png rename to resources/images/news/elperiodico_spanish.png diff --git a/src/calibre/gui2/images/news/eltiempo_hn.png b/resources/images/news/eltiempo_hn.png similarity index 100% rename from src/calibre/gui2/images/news/eltiempo_hn.png rename to resources/images/news/eltiempo_hn.png diff --git a/src/calibre/gui2/images/news/endgadget.png b/resources/images/news/endgadget.png similarity index 100% rename from src/calibre/gui2/images/news/endgadget.png rename to resources/images/news/endgadget.png diff --git a/src/calibre/gui2/images/news/espn.png b/resources/images/news/espn.png similarity index 100% rename from src/calibre/gui2/images/news/espn.png rename to resources/images/news/espn.png diff --git a/src/calibre/gui2/images/news/esquire.png b/resources/images/news/esquire.png similarity index 100% rename from src/calibre/gui2/images/news/esquire.png rename to resources/images/news/esquire.png diff --git a/src/calibre/gui2/images/news/estadao.png b/resources/images/news/estadao.png similarity index 100% rename from src/calibre/gui2/images/news/estadao.png rename to resources/images/news/estadao.png diff --git a/src/calibre/gui2/images/news/expansion_spanish.png b/resources/images/news/expansion_spanish.png similarity index 100% rename from src/calibre/gui2/images/news/expansion_spanish.png rename to resources/images/news/expansion_spanish.png diff --git a/src/calibre/gui2/images/news/fastcompany.png b/resources/images/news/fastcompany.png similarity index 100% rename from src/calibre/gui2/images/news/fastcompany.png rename to resources/images/news/fastcompany.png diff --git a/src/calibre/gui2/images/news/faznet.png b/resources/images/news/faznet.png similarity index 100% rename from src/calibre/gui2/images/news/faznet.png rename to resources/images/news/faznet.png diff --git a/src/calibre/gui2/images/news/freakonomics.png b/resources/images/news/freakonomics.png similarity index 100% rename from src/calibre/gui2/images/news/freakonomics.png rename to resources/images/news/freakonomics.png diff --git a/src/calibre/gui2/images/news/ftd.png b/resources/images/news/ftd.png similarity index 100% rename from src/calibre/gui2/images/news/ftd.png rename to resources/images/news/ftd.png diff --git a/src/calibre/gui2/images/news/fudzilla.png b/resources/images/news/fudzilla.png similarity index 100% rename from src/calibre/gui2/images/news/fudzilla.png rename to resources/images/news/fudzilla.png diff --git a/src/calibre/gui2/images/news/glas_srpske.png b/resources/images/news/glas_srpske.png similarity index 100% rename from src/calibre/gui2/images/news/glas_srpske.png rename to resources/images/news/glas_srpske.png diff --git a/src/calibre/gui2/images/news/glasjavnosti.png b/resources/images/news/glasjavnosti.png similarity index 100% rename from src/calibre/gui2/images/news/glasjavnosti.png rename to resources/images/news/glasjavnosti.png diff --git a/src/calibre/gui2/images/news/granma.png b/resources/images/news/granma.png similarity index 100% rename from src/calibre/gui2/images/news/granma.png rename to resources/images/news/granma.png diff --git a/src/calibre/gui2/images/news/gva_be.png b/resources/images/news/gva_be.png similarity index 100% rename from src/calibre/gui2/images/news/gva_be.png rename to resources/images/news/gva_be.png diff --git a/src/calibre/gui2/images/news/heise.png b/resources/images/news/heise.png similarity index 100% rename from src/calibre/gui2/images/news/heise.png rename to resources/images/news/heise.png diff --git a/src/calibre/gui2/images/news/hln.png b/resources/images/news/hln.png similarity index 100% rename from src/calibre/gui2/images/news/hln.png rename to resources/images/news/hln.png diff --git a/src/calibre/gui2/images/news/hln_be.png b/resources/images/news/hln_be.png similarity index 100% rename from src/calibre/gui2/images/news/hln_be.png rename to resources/images/news/hln_be.png diff --git a/src/calibre/gui2/images/news/hna.png b/resources/images/news/hna.png similarity index 100% rename from src/calibre/gui2/images/news/hna.png rename to resources/images/news/hna.png diff --git a/src/calibre/gui2/images/news/hrt.png b/resources/images/news/hrt.png similarity index 100% rename from src/calibre/gui2/images/news/hrt.png rename to resources/images/news/hrt.png diff --git a/src/calibre/gui2/images/news/infobae.png b/resources/images/news/infobae.png similarity index 100% rename from src/calibre/gui2/images/news/infobae.png rename to resources/images/news/infobae.png diff --git a/src/calibre/gui2/images/news/inquirer_net.png b/resources/images/news/inquirer_net.png similarity index 100% rename from src/calibre/gui2/images/news/inquirer_net.png rename to resources/images/news/inquirer_net.png diff --git a/src/calibre/gui2/images/news/instapaper.png b/resources/images/news/instapaper.png similarity index 100% rename from src/calibre/gui2/images/news/instapaper.png rename to resources/images/news/instapaper.png diff --git a/src/calibre/gui2/images/news/jb_online.png b/resources/images/news/jb_online.png similarity index 100% rename from src/calibre/gui2/images/news/jb_online.png rename to resources/images/news/jb_online.png diff --git a/src/calibre/gui2/images/news/joelonsoftware.png b/resources/images/news/joelonsoftware.png similarity index 100% rename from src/calibre/gui2/images/news/joelonsoftware.png rename to resources/images/news/joelonsoftware.png diff --git a/src/calibre/gui2/images/news/jpost.png b/resources/images/news/jpost.png similarity index 100% rename from src/calibre/gui2/images/news/jpost.png rename to resources/images/news/jpost.png diff --git a/src/calibre/gui2/images/news/jutarnji.png b/resources/images/news/jutarnji.png similarity index 100% rename from src/calibre/gui2/images/news/jutarnji.png rename to resources/images/news/jutarnji.png diff --git a/src/calibre/gui2/images/news/krstarica.png b/resources/images/news/krstarica.png similarity index 100% rename from src/calibre/gui2/images/news/krstarica.png rename to resources/images/news/krstarica.png diff --git a/src/calibre/gui2/images/news/krstarica_en.png b/resources/images/news/krstarica_en.png similarity index 100% rename from src/calibre/gui2/images/news/krstarica_en.png rename to resources/images/news/krstarica_en.png diff --git a/src/calibre/gui2/images/news/la_cuarta.png b/resources/images/news/la_cuarta.png similarity index 100% rename from src/calibre/gui2/images/news/la_cuarta.png rename to resources/images/news/la_cuarta.png diff --git a/src/calibre/gui2/images/news/la_tercera.png b/resources/images/news/la_tercera.png similarity index 100% rename from src/calibre/gui2/images/news/la_tercera.png rename to resources/images/news/la_tercera.png diff --git a/src/calibre/gui2/images/news/lanacion.png b/resources/images/news/lanacion.png similarity index 100% rename from src/calibre/gui2/images/news/lanacion.png rename to resources/images/news/lanacion.png diff --git a/src/calibre/gui2/images/news/lanacion_chile.png b/resources/images/news/lanacion_chile.png similarity index 100% rename from src/calibre/gui2/images/news/lanacion_chile.png rename to resources/images/news/lanacion_chile.png diff --git a/src/calibre/gui2/images/news/laprensa_hn.png b/resources/images/news/laprensa_hn.png similarity index 100% rename from src/calibre/gui2/images/news/laprensa_hn.png rename to resources/images/news/laprensa_hn.png diff --git a/src/calibre/gui2/images/news/laprensa_ni.png b/resources/images/news/laprensa_ni.png similarity index 100% rename from src/calibre/gui2/images/news/laprensa_ni.png rename to resources/images/news/laprensa_ni.png diff --git a/src/calibre/gui2/images/news/latribuna.png b/resources/images/news/latribuna.png similarity index 100% rename from src/calibre/gui2/images/news/latribuna.png rename to resources/images/news/latribuna.png diff --git a/src/calibre/gui2/images/news/le_monde.png b/resources/images/news/le_monde.png similarity index 100% rename from src/calibre/gui2/images/news/le_monde.png rename to resources/images/news/le_monde.png diff --git a/src/calibre/gui2/images/news/liberation.png b/resources/images/news/liberation.png similarity index 100% rename from src/calibre/gui2/images/news/liberation.png rename to resources/images/news/liberation.png diff --git a/src/calibre/gui2/images/news/linux_magazine.png b/resources/images/news/linux_magazine.png similarity index 100% rename from src/calibre/gui2/images/news/linux_magazine.png rename to resources/images/news/linux_magazine.png diff --git a/src/calibre/gui2/images/news/linuxdevices.png b/resources/images/news/linuxdevices.png similarity index 100% rename from src/calibre/gui2/images/news/linuxdevices.png rename to resources/images/news/linuxdevices.png diff --git a/src/calibre/gui2/images/news/livemint.png b/resources/images/news/livemint.png similarity index 100% rename from src/calibre/gui2/images/news/livemint.png rename to resources/images/news/livemint.png diff --git a/src/calibre/gui2/images/news/miami_herald.png b/resources/images/news/miami_herald.png similarity index 100% rename from src/calibre/gui2/images/news/miami_herald.png rename to resources/images/news/miami_herald.png diff --git a/src/calibre/gui2/images/news/msdnmag_en.png b/resources/images/news/msdnmag_en.png similarity index 100% rename from src/calibre/gui2/images/news/msdnmag_en.png rename to resources/images/news/msdnmag_en.png diff --git a/src/calibre/gui2/images/news/nacional_cro.png b/resources/images/news/nacional_cro.png similarity index 100% rename from src/calibre/gui2/images/news/nacional_cro.png rename to resources/images/news/nacional_cro.png diff --git a/src/calibre/gui2/images/news/nasa.png b/resources/images/news/nasa.png similarity index 100% rename from src/calibre/gui2/images/news/nasa.png rename to resources/images/news/nasa.png diff --git a/src/calibre/gui2/images/news/new_yorker.png b/resources/images/news/new_yorker.png similarity index 100% rename from src/calibre/gui2/images/news/new_yorker.png rename to resources/images/news/new_yorker.png diff --git a/src/calibre/gui2/images/news/newsweek.png b/resources/images/news/newsweek.png similarity index 100% rename from src/calibre/gui2/images/news/newsweek.png rename to resources/images/news/newsweek.png diff --git a/src/calibre/gui2/images/news/noaa.png b/resources/images/news/noaa.png similarity index 100% rename from src/calibre/gui2/images/news/noaa.png rename to resources/images/news/noaa.png diff --git a/src/calibre/gui2/images/news/novosti.png b/resources/images/news/novosti.png similarity index 100% rename from src/calibre/gui2/images/news/novosti.png rename to resources/images/news/novosti.png diff --git a/src/calibre/gui2/images/news/nspm.png b/resources/images/news/nspm.png similarity index 100% rename from src/calibre/gui2/images/news/nspm.png rename to resources/images/news/nspm.png diff --git a/src/calibre/gui2/images/news/nspm_int.png b/resources/images/news/nspm_int.png similarity index 100% rename from src/calibre/gui2/images/news/nspm_int.png rename to resources/images/news/nspm_int.png diff --git a/src/calibre/gui2/images/news/nytimes.png b/resources/images/news/nytimes.png similarity index 100% rename from src/calibre/gui2/images/news/nytimes.png rename to resources/images/news/nytimes.png diff --git a/src/calibre/gui2/images/news/nytimes_sub.png b/resources/images/news/nytimes_sub.png similarity index 100% rename from src/calibre/gui2/images/news/nytimes_sub.png rename to resources/images/news/nytimes_sub.png diff --git a/src/calibre/gui2/images/news/nzz_ger.png b/resources/images/news/nzz_ger.png similarity index 100% rename from src/calibre/gui2/images/news/nzz_ger.png rename to resources/images/news/nzz_ger.png diff --git a/src/calibre/gui2/images/news/o_globo.png b/resources/images/news/o_globo.png similarity index 100% rename from src/calibre/gui2/images/news/o_globo.png rename to resources/images/news/o_globo.png diff --git a/src/calibre/gui2/images/news/pagina12.png b/resources/images/news/pagina12.png similarity index 100% rename from src/calibre/gui2/images/news/pagina12.png rename to resources/images/news/pagina12.png diff --git a/src/calibre/gui2/images/news/pescanik.png b/resources/images/news/pescanik.png similarity index 100% rename from src/calibre/gui2/images/news/pescanik.png rename to resources/images/news/pescanik.png diff --git a/src/calibre/gui2/images/news/pobjeda.png b/resources/images/news/pobjeda.png similarity index 100% rename from src/calibre/gui2/images/news/pobjeda.png rename to resources/images/news/pobjeda.png diff --git a/src/calibre/gui2/images/news/politico.png b/resources/images/news/politico.png similarity index 100% rename from src/calibre/gui2/images/news/politico.png rename to resources/images/news/politico.png diff --git a/src/calibre/gui2/images/news/politika.png b/resources/images/news/politika.png similarity index 100% rename from src/calibre/gui2/images/news/politika.png rename to resources/images/news/politika.png diff --git a/src/calibre/gui2/images/news/portfolio.png b/resources/images/news/portfolio.png similarity index 100% rename from src/calibre/gui2/images/news/portfolio.png rename to resources/images/news/portfolio.png diff --git a/src/calibre/gui2/images/news/pressonline.png b/resources/images/news/pressonline.png similarity index 100% rename from src/calibre/gui2/images/news/pressonline.png rename to resources/images/news/pressonline.png diff --git a/src/calibre/gui2/images/news/rts.png b/resources/images/news/rts.png similarity index 100% rename from src/calibre/gui2/images/news/rts.png rename to resources/images/news/rts.png diff --git a/src/calibre/gui2/images/news/sciencedaily.png b/resources/images/news/sciencedaily.png similarity index 100% rename from src/calibre/gui2/images/news/sciencedaily.png rename to resources/images/news/sciencedaily.png diff --git a/src/calibre/gui2/images/news/scott_hanselman.png b/resources/images/news/scott_hanselman.png similarity index 100% rename from src/calibre/gui2/images/news/scott_hanselman.png rename to resources/images/news/scott_hanselman.png diff --git a/src/calibre/gui2/images/news/seattle_times.png b/resources/images/news/seattle_times.png similarity index 100% rename from src/calibre/gui2/images/news/seattle_times.png rename to resources/images/news/seattle_times.png diff --git a/src/calibre/gui2/images/news/security_watch.png b/resources/images/news/security_watch.png similarity index 100% rename from src/calibre/gui2/images/news/security_watch.png rename to resources/images/news/security_watch.png diff --git a/src/calibre/gui2/images/news/soldiers.png b/resources/images/news/soldiers.png similarity index 100% rename from src/calibre/gui2/images/news/soldiers.png rename to resources/images/news/soldiers.png diff --git a/src/calibre/gui2/images/news/spiegel_int.png b/resources/images/news/spiegel_int.png similarity index 100% rename from src/calibre/gui2/images/news/spiegel_int.png rename to resources/images/news/spiegel_int.png diff --git a/src/calibre/gui2/images/news/spiegelde.png b/resources/images/news/spiegelde.png similarity index 100% rename from src/calibre/gui2/images/news/spiegelde.png rename to resources/images/news/spiegelde.png diff --git a/src/calibre/gui2/images/news/stackoverflow.png b/resources/images/news/stackoverflow.png similarity index 100% rename from src/calibre/gui2/images/news/stackoverflow.png rename to resources/images/news/stackoverflow.png diff --git a/src/calibre/gui2/images/news/starbulletin.png b/resources/images/news/starbulletin.png similarity index 100% rename from src/calibre/gui2/images/news/starbulletin.png rename to resources/images/news/starbulletin.png diff --git a/src/calibre/gui2/images/news/straitstimes.png b/resources/images/news/straitstimes.png similarity index 100% rename from src/calibre/gui2/images/news/straitstimes.png rename to resources/images/news/straitstimes.png diff --git a/src/calibre/gui2/images/news/sueddeutsche.png b/resources/images/news/sueddeutsche.png similarity index 100% rename from src/calibre/gui2/images/news/sueddeutsche.png rename to resources/images/news/sueddeutsche.png diff --git a/src/calibre/gui2/images/news/tanjug.png b/resources/images/news/tanjug.png similarity index 100% rename from src/calibre/gui2/images/news/tanjug.png rename to resources/images/news/tanjug.png diff --git a/src/calibre/gui2/images/news/telegraph_uk.png b/resources/images/news/telegraph_uk.png similarity index 100% rename from src/calibre/gui2/images/news/telegraph_uk.png rename to resources/images/news/telegraph_uk.png diff --git a/src/calibre/gui2/images/news/telepolis.png b/resources/images/news/telepolis.png similarity index 100% rename from src/calibre/gui2/images/news/telepolis.png rename to resources/images/news/telepolis.png diff --git a/src/calibre/gui2/images/news/telepolis_artikel.png b/resources/images/news/telepolis_artikel.png similarity index 100% rename from src/calibre/gui2/images/news/telepolis_artikel.png rename to resources/images/news/telepolis_artikel.png diff --git a/src/calibre/gui2/images/news/teleread.png b/resources/images/news/teleread.png similarity index 100% rename from src/calibre/gui2/images/news/teleread.png rename to resources/images/news/teleread.png diff --git a/src/calibre/gui2/images/news/the_oz.png b/resources/images/news/the_oz.png similarity index 100% rename from src/calibre/gui2/images/news/the_oz.png rename to resources/images/news/the_oz.png diff --git a/src/calibre/gui2/images/news/theeconomictimes_india.png b/resources/images/news/theeconomictimes_india.png similarity index 100% rename from src/calibre/gui2/images/news/theeconomictimes_india.png rename to resources/images/news/theeconomictimes_india.png diff --git a/src/calibre/gui2/images/news/themarketticker.png b/resources/images/news/themarketticker.png similarity index 100% rename from src/calibre/gui2/images/news/themarketticker.png rename to resources/images/news/themarketticker.png diff --git a/src/calibre/gui2/images/news/theonion.png b/resources/images/news/theonion.png similarity index 100% rename from src/calibre/gui2/images/news/theonion.png rename to resources/images/news/theonion.png diff --git a/src/calibre/gui2/images/news/tijd.png b/resources/images/news/tijd.png similarity index 100% rename from src/calibre/gui2/images/news/tijd.png rename to resources/images/news/tijd.png diff --git a/src/calibre/gui2/images/news/time_magazine.png b/resources/images/news/time_magazine.png similarity index 100% rename from src/calibre/gui2/images/news/time_magazine.png rename to resources/images/news/time_magazine.png diff --git a/src/calibre/gui2/images/news/times_online.png b/resources/images/news/times_online.png similarity index 100% rename from src/calibre/gui2/images/news/times_online.png rename to resources/images/news/times_online.png diff --git a/src/calibre/gui2/images/news/tomshardware.png b/resources/images/news/tomshardware.png similarity index 100% rename from src/calibre/gui2/images/news/tomshardware.png rename to resources/images/news/tomshardware.png diff --git a/src/calibre/gui2/images/news/tomshardware_de.png b/resources/images/news/tomshardware_de.png similarity index 100% rename from src/calibre/gui2/images/news/tomshardware_de.png rename to resources/images/news/tomshardware_de.png diff --git a/src/calibre/gui2/images/news/uncrate.png b/resources/images/news/uncrate.png similarity index 100% rename from src/calibre/gui2/images/news/uncrate.png rename to resources/images/news/uncrate.png diff --git a/src/calibre/gui2/images/news/usnews.png b/resources/images/news/usnews.png similarity index 100% rename from src/calibre/gui2/images/news/usnews.png rename to resources/images/news/usnews.png diff --git a/src/calibre/gui2/images/news/utne.png b/resources/images/news/utne.png similarity index 100% rename from src/calibre/gui2/images/news/utne.png rename to resources/images/news/utne.png diff --git a/src/calibre/gui2/images/news/vecernji_list.png b/resources/images/news/vecernji_list.png similarity index 100% rename from src/calibre/gui2/images/news/vecernji_list.png rename to resources/images/news/vecernji_list.png diff --git a/src/calibre/gui2/images/news/vijesti.png b/resources/images/news/vijesti.png similarity index 100% rename from src/calibre/gui2/images/news/vijesti.png rename to resources/images/news/vijesti.png diff --git a/src/calibre/gui2/images/news/vreme.png b/resources/images/news/vreme.png similarity index 100% rename from src/calibre/gui2/images/news/vreme.png rename to resources/images/news/vreme.png diff --git a/src/calibre/gui2/images/news/wikinews_en.png b/resources/images/news/wikinews_en.png similarity index 100% rename from src/calibre/gui2/images/news/wikinews_en.png rename to resources/images/news/wikinews_en.png diff --git a/src/calibre/gui2/images/news/wired.png b/resources/images/news/wired.png similarity index 100% rename from src/calibre/gui2/images/news/wired.png rename to resources/images/news/wired.png diff --git a/src/calibre/gui2/images/news/wsj.png b/resources/images/news/wsj.png similarity index 100% rename from src/calibre/gui2/images/news/wsj.png rename to resources/images/news/wsj.png diff --git a/src/calibre/gui2/images/news/zdnet.png b/resources/images/news/zdnet.png similarity index 100% rename from src/calibre/gui2/images/news/zdnet.png rename to resources/images/news/zdnet.png diff --git a/src/calibre/gui2/images/news/zeitde.png b/resources/images/news/zeitde.png similarity index 100% rename from src/calibre/gui2/images/news/zeitde.png rename to resources/images/news/zeitde.png diff --git a/src/calibre/gui2/images/next.svg b/resources/images/next.svg similarity index 100% rename from src/calibre/gui2/images/next.svg rename to resources/images/next.svg diff --git a/src/calibre/gui2/images/ok.svg b/resources/images/ok.svg similarity index 100% rename from src/calibre/gui2/images/ok.svg rename to resources/images/ok.svg diff --git a/src/calibre/gui2/images/page.svg b/resources/images/page.svg similarity index 100% rename from src/calibre/gui2/images/page.svg rename to resources/images/page.svg diff --git a/src/calibre/gui2/images/plugins.svg b/resources/images/plugins.svg similarity index 100% rename from src/calibre/gui2/images/plugins.svg rename to resources/images/plugins.svg diff --git a/src/calibre/gui2/images/plus.svg b/resources/images/plus.svg similarity index 100% rename from src/calibre/gui2/images/plus.svg rename to resources/images/plus.svg diff --git a/src/calibre/gui2/images/previous.svg b/resources/images/previous.svg similarity index 100% rename from src/calibre/gui2/images/previous.svg rename to resources/images/previous.svg diff --git a/src/calibre/gui2/images/print-preview.svg b/resources/images/print-preview.svg similarity index 100% rename from src/calibre/gui2/images/print-preview.svg rename to resources/images/print-preview.svg diff --git a/src/calibre/gui2/images/print.svg b/resources/images/print.svg similarity index 100% rename from src/calibre/gui2/images/print.svg rename to resources/images/print.svg diff --git a/src/calibre/gui2/images/publisher.png b/resources/images/publisher.png similarity index 100% rename from src/calibre/gui2/images/publisher.png rename to resources/images/publisher.png diff --git a/src/calibre/gui2/images/reader.svg b/resources/images/reader.svg similarity index 100% rename from src/calibre/gui2/images/reader.svg rename to resources/images/reader.svg diff --git a/src/calibre/gui2/images/save.svg b/resources/images/save.svg similarity index 100% rename from src/calibre/gui2/images/save.svg rename to resources/images/save.svg diff --git a/src/calibre/gui2/images/scheduler.svg b/resources/images/scheduler.svg similarity index 100% rename from src/calibre/gui2/images/scheduler.svg rename to resources/images/scheduler.svg diff --git a/src/calibre/gui2/images/sd.svg b/resources/images/sd.svg similarity index 100% rename from src/calibre/gui2/images/sd.svg rename to resources/images/sd.svg diff --git a/src/calibre/gui2/images/search.svg b/resources/images/search.svg similarity index 100% rename from src/calibre/gui2/images/search.svg rename to resources/images/search.svg diff --git a/src/calibre/gui2/images/series.svg b/resources/images/series.svg similarity index 100% rename from src/calibre/gui2/images/series.svg rename to resources/images/series.svg diff --git a/src/calibre/gui2/images/swap.svg b/resources/images/swap.svg similarity index 100% rename from src/calibre/gui2/images/swap.svg rename to resources/images/swap.svg diff --git a/src/calibre/gui2/images/sync.svg b/resources/images/sync.svg similarity index 100% rename from src/calibre/gui2/images/sync.svg rename to resources/images/sync.svg diff --git a/src/calibre/gui2/images/tags.svg b/resources/images/tags.svg similarity index 100% rename from src/calibre/gui2/images/tags.svg rename to resources/images/tags.svg diff --git a/src/calibre/gui2/images/trash.svg b/resources/images/trash.svg similarity index 100% rename from src/calibre/gui2/images/trash.svg rename to resources/images/trash.svg diff --git a/src/calibre/gui2/images/user_profile.svg b/resources/images/user_profile.svg similarity index 100% rename from src/calibre/gui2/images/user_profile.svg rename to resources/images/user_profile.svg diff --git a/src/calibre/gui2/images/view.svg b/resources/images/view.svg similarity index 100% rename from src/calibre/gui2/images/view.svg rename to resources/images/view.svg diff --git a/src/calibre/gui2/images/viewer.svg b/resources/images/viewer.svg similarity index 100% rename from src/calibre/gui2/images/viewer.svg rename to resources/images/viewer.svg diff --git a/src/calibre/gui2/images/welcome_wizard.svg b/resources/images/welcome_wizard.svg similarity index 100% rename from src/calibre/gui2/images/welcome_wizard.svg rename to resources/images/welcome_wizard.svg diff --git a/src/calibre/gui2/images/window-close.svg b/resources/images/window-close.svg similarity index 100% rename from src/calibre/gui2/images/window-close.svg rename to resources/images/window-close.svg diff --git a/src/calibre/gui2/images/wizard.svg b/resources/images/wizard.svg similarity index 100% rename from src/calibre/gui2/images/wizard.svg rename to resources/images/wizard.svg diff --git a/setup/commands.py b/setup/commands.py index 721caa165c..a05d158e01 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en' __all__ = [ 'pot', 'translations', 'get_translations', 'iso639', 'build', + 'gui', 'develop', 'clean' ] @@ -28,6 +29,9 @@ build = Build() from setup.install import Develop develop = Develop() +from setup.gui import GUI +gui = GUI() + class Clean(Command): description='''Delete all computer generated files in the source tree''' diff --git a/setup/gui.py b/setup/gui.py new file mode 100644 index 0000000000..5dd53e4cdd --- /dev/null +++ b/setup/gui.py @@ -0,0 +1,97 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import os, cStringIO, re + +from setup import Command, __appname__ + +class GUI(Command): + description = 'Compile all GUI forms' + PATH = os.path.join(Command.SRC, __appname__, 'gui2') + QRC = os.path.join(Command.RESOURCES, 'images.qrc') + + @classmethod + def find_forms(cls): + forms = [] + for root, _, files in os.walk(cls.PATH): + for name in files: + if name.endswith('.ui'): + forms.append(os.path.abspath(os.path.join(root, name))) + + return forms + + @classmethod + def form_to_compiled_form(cls, form): + return form.rpartition('.')[0]+'_ui.py' + + def run(self, opts): + self.build_forms() + self.build_images() + + def build_images(self): + cwd = os.getcwd() + try: + os.chdir(self.RESOURCES) + sources, files = [], [] + for root, _, files in os.walk('images'): + for name in files: + sources.append(os.path.join(root, name)) + if self.newer(self.QRC, sources): + self.info('Creating images.qrc') + for s in sources: + files.append('%s'%s) + manifest = '\n\n%s\n\n'%'\n'.join(files) + with open('images.qrc', 'wb') as f: + f.write(manifest) + finally: + os.chdir(cwd) + + + def build_forms(self): + from PyQt4.uic import compileUi + forms = self.find_forms() + for form in forms: + compiled_form = self.form_to_compiled_form(form) + if not os.path.exists(compiled_form) or os.stat(form).st_mtime > os.stat(compiled_form).st_mtime: + print 'Compiling form', form + buf = cStringIO.StringIO() + compileUi(form, buf) + dat = buf.getvalue() + dat = dat.replace('__appname__', __appname__) + dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc') + dat = dat.replace('from library import', 'from calibre.gui2.library import') + dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import') + dat = dat.replace('from convert.xpath_wizard import', + 'from calibre.gui2.convert.xpath_wizard import') + dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?... - + :/images/document_open.svg:/images/document_open.svg @@ -64,7 +64,7 @@ ... - + :/images/clear_left.svg:/images/clear_left.svg @@ -72,7 +72,7 @@ - + diff --git a/src/calibre/gui2/convert/look_and_feel.ui b/src/calibre/gui2/convert/look_and_feel.ui index 9378265d7d..de0001bb3e 100644 --- a/src/calibre/gui2/convert/look_and_feel.ui +++ b/src/calibre/gui2/convert/look_and_feel.ui @@ -153,8 +153,8 @@ - - + + diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui index b1424b49dc..5d3cc432d7 100644 --- a/src/calibre/gui2/convert/metadata.ui +++ b/src/calibre/gui2/convert/metadata.ui @@ -28,7 +28,7 @@ - :/images/book.svg + :/images/book.svg true @@ -92,7 +92,7 @@ ... - + :/images/document_open.svg:/images/document_open.svg @@ -343,9 +343,9 @@ opt_prefer_metadata_cover - - - + + + diff --git a/src/calibre/gui2/convert/single.ui b/src/calibre/gui2/convert/single.ui index 63c9e5084d..f6da306b2d 100644 --- a/src/calibre/gui2/convert/single.ui +++ b/src/calibre/gui2/convert/single.ui @@ -14,7 +14,7 @@ Dialog - + :/images/convert.svg:/images/convert.svg @@ -161,7 +161,7 @@ - + diff --git a/src/calibre/gui2/convert/xpath_edit.ui b/src/calibre/gui2/convert/xpath_edit.ui index 00f0782c47..0b11e9c071 100644 --- a/src/calibre/gui2/convert/xpath_edit.ui +++ b/src/calibre/gui2/convert/xpath_edit.ui @@ -43,7 +43,7 @@ ... - + :/images/wizard.svg:/images/wizard.svg @@ -57,7 +57,7 @@ - + diff --git a/src/calibre/gui2/device_drivers/configwidget.ui b/src/calibre/gui2/device_drivers/configwidget.ui index 660e4f9925..1a42a5e386 100644 --- a/src/calibre/gui2/device_drivers/configwidget.ui +++ b/src/calibre/gui2/device_drivers/configwidget.ui @@ -40,7 +40,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -64,7 +64,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -93,7 +93,7 @@ - + diff --git a/src/calibre/gui2/dialogs/book_info.ui b/src/calibre/gui2/dialogs/book_info.ui index 28b27b99b4..27e15f96a1 100644 --- a/src/calibre/gui2/dialogs/book_info.ui +++ b/src/calibre/gui2/dialogs/book_info.ui @@ -61,7 +61,7 @@ &Previous - + :/images/previous.svg:/images/previous.svg @@ -72,7 +72,7 @@ &Next - + :/images/next.svg:/images/next.svg @@ -84,7 +84,7 @@ - + diff --git a/src/calibre/gui2/dialogs/choose_format.ui b/src/calibre/gui2/dialogs/choose_format.ui index 2a4073800f..0ae0fa8b94 100644 --- a/src/calibre/gui2/dialogs/choose_format.ui +++ b/src/calibre/gui2/dialogs/choose_format.ui @@ -13,7 +13,7 @@ Choose Format - :/images/mimetypes/unknown.svg + :/images/mimetypes/unknown.svg @@ -46,7 +46,7 @@ - + diff --git a/src/calibre/gui2/dialogs/comicconf.ui b/src/calibre/gui2/dialogs/comicconf.ui index fd50df8e87..03911b2171 100644 --- a/src/calibre/gui2/dialogs/comicconf.ui +++ b/src/calibre/gui2/dialogs/comicconf.ui @@ -14,7 +14,7 @@ Dialog - + :/images/convert.svg:/images/convert.svg @@ -163,7 +163,7 @@ - + diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index 6e655c3468..dc180edba7 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -15,7 +15,7 @@ Preferences - + :/images/config.svg:/images/config.svg @@ -115,7 +115,7 @@ ... - + :/images/mimetypes/dir.svg:/images/mimetypes/dir.svg @@ -245,7 +245,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -269,7 +269,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -326,7 +326,7 @@ ... - + :/images/plus.svg:/images/plus.svg @@ -353,7 +353,7 @@ ... - + :/images/list_remove.svg:/images/list_remove.svg @@ -530,7 +530,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -554,7 +554,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -614,7 +614,7 @@ &Add email - + :/images/plus.svg:/images/plus.svg @@ -641,7 +641,7 @@ &Remove email - + :/images/minus.svg:/images/minus.svg @@ -996,7 +996,7 @@ ... - + :/images/document_open.svg:/images/document_open.svg @@ -1063,7 +1063,7 @@ - + diff --git a/src/calibre/gui2/dialogs/confirm_delete.ui b/src/calibre/gui2/dialogs/confirm_delete.ui index 1ee4cb79d9..eee2c9fdfd 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.ui +++ b/src/calibre/gui2/dialogs/confirm_delete.ui @@ -13,7 +13,7 @@ Are you sure? - + :/images/dialog_warning.svg:/images/dialog_warning.svg @@ -22,7 +22,7 @@ - :/images/dialog_warning.svg + :/images/dialog_warning.svg @@ -61,7 +61,7 @@ - + diff --git a/src/calibre/gui2/dialogs/conversion_error.ui b/src/calibre/gui2/dialogs/conversion_error.ui index d3963df860..28272b7c6e 100644 --- a/src/calibre/gui2/dialogs/conversion_error.ui +++ b/src/calibre/gui2/dialogs/conversion_error.ui @@ -13,7 +13,7 @@ ERROR - + :/library:/library @@ -23,7 +23,7 @@ - :/images/dialog_error.svg + :/images/dialog_error.svg @@ -53,7 +53,7 @@ - + diff --git a/src/calibre/gui2/dialogs/fetch_metadata.ui b/src/calibre/gui2/dialogs/fetch_metadata.ui index 653c7005b8..edf4207b45 100644 --- a/src/calibre/gui2/dialogs/fetch_metadata.ui +++ b/src/calibre/gui2/dialogs/fetch_metadata.ui @@ -16,7 +16,7 @@ Fetch metadata - + :/images/metadata.svg:/images/metadata.svg @@ -118,7 +118,7 @@ - + diff --git a/src/calibre/gui2/dialogs/job_view.ui b/src/calibre/gui2/dialogs/job_view.ui index f4b0086497..a66fdd482d 100644 --- a/src/calibre/gui2/dialogs/job_view.ui +++ b/src/calibre/gui2/dialogs/job_view.ui @@ -13,7 +13,7 @@ Details of job - + :/images/view.svg:/images/view.svg @@ -40,7 +40,7 @@ - + diff --git a/src/calibre/gui2/dialogs/jobs.ui b/src/calibre/gui2/dialogs/jobs.ui index 3716c9fbb9..de2d78db73 100644 --- a/src/calibre/gui2/dialogs/jobs.ui +++ b/src/calibre/gui2/dialogs/jobs.ui @@ -14,7 +14,7 @@ Active Jobs - + :/images/jobs.svg:/images/jobs.svg @@ -67,7 +67,7 @@ - + diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index 59fdc5d27a..ca6237c41b 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -14,7 +14,7 @@ Edit Meta information - + :/images/edit_input.svg:/images/edit_input.svg @@ -153,7 +153,7 @@ Open Tag Editor - + :/images/chapters.svg:/images/chapters.svg @@ -278,7 +278,7 @@ button_box - + diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index a20348885d..0108039217 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -20,7 +20,7 @@ Edit Meta Information - + :/images/edit_input.svg:/images/edit_input.svg @@ -102,7 +102,7 @@ ... - + :/images/swap.svg:/images/swap.svg @@ -157,7 +157,7 @@ ... - + :/images/auto_author_sort.svg:/images/auto_author_sort.svg @@ -240,7 +240,7 @@ Open Tag Editor - + :/images/chapters.svg:/images/chapters.svg @@ -302,7 +302,7 @@ ... - + :/images/trash.svg:/images/trash.svg @@ -446,7 +446,7 @@ ... - + :/images/add_book.svg:/images/add_book.svg @@ -466,7 +466,7 @@ ... - + :/images/trash.svg:/images/trash.svg @@ -486,7 +486,7 @@ ... - + :/images/book.svg:/images/book.svg @@ -506,7 +506,7 @@ - + :/images/edit_input.svg:/images/edit_input.svg @@ -547,7 +547,7 @@ - :/images/book.svg + :/images/book.svg true @@ -599,7 +599,7 @@ ... - + :/images/document_open.svg:/images/document_open.svg @@ -613,7 +613,7 @@ ... - + :/images/trash.svg:/images/trash.svg @@ -709,7 +709,7 @@ button_box - + diff --git a/src/calibre/gui2/dialogs/password.ui b/src/calibre/gui2/dialogs/password.ui index 3fc982371e..811e042153 100644 --- a/src/calibre/gui2/dialogs/password.ui +++ b/src/calibre/gui2/dialogs/password.ui @@ -14,7 +14,7 @@ Password needed - + :/images/mimetypes/unknown.svg:/images/mimetypes/unknown.svg @@ -81,7 +81,7 @@ - + diff --git a/src/calibre/gui2/dialogs/progress.ui b/src/calibre/gui2/dialogs/progress.ui index 60488be62d..451b9dfb59 100644 --- a/src/calibre/gui2/dialogs/progress.ui +++ b/src/calibre/gui2/dialogs/progress.ui @@ -13,7 +13,7 @@ Dialog - + :/images/jobs.svg:/images/jobs.svg @@ -66,7 +66,7 @@ - + diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index 497b1215dc..b8769ff47f 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -14,7 +14,7 @@ Schedule news download - + :/images/scheduler.svg:/images/scheduler.svg @@ -314,7 +314,7 @@ - + diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index b35ca84aca..5c28807bac 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -13,7 +13,7 @@ Advanced Search - + :/images/search.svg:/images/search.svg @@ -132,7 +132,7 @@ - + diff --git a/src/calibre/gui2/dialogs/tag_editor.ui b/src/calibre/gui2/dialogs/tag_editor.ui index 713ffc7fdf..1db1abb1d3 100644 --- a/src/calibre/gui2/dialogs/tag_editor.ui +++ b/src/calibre/gui2/dialogs/tag_editor.ui @@ -14,7 +14,7 @@ Tag Editor - + :/images/chapters.svg:/images/chapters.svg @@ -58,7 +58,7 @@ ... - + :/images/trash.svg:/images/trash.svg @@ -104,7 +104,7 @@ ... - + :/images/forward.svg:/images/forward.svg @@ -189,7 +189,7 @@ ... - + :/images/list_remove.svg:/images/list_remove.svg @@ -250,7 +250,7 @@ ... - + :/images/plus.svg:/images/plus.svg @@ -290,7 +290,7 @@ - + diff --git a/src/calibre/gui2/dialogs/test_email.ui b/src/calibre/gui2/dialogs/test_email.ui index f1d5568c03..241468793f 100644 --- a/src/calibre/gui2/dialogs/test_email.ui +++ b/src/calibre/gui2/dialogs/test_email.ui @@ -14,7 +14,7 @@ Test email settings - + :/images/config.svg:/images/config.svg @@ -64,7 +64,7 @@ - + diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui index a913275c61..64b6d10123 100644 --- a/src/calibre/gui2/dialogs/user_profiles.ui +++ b/src/calibre/gui2/dialogs/user_profiles.ui @@ -14,7 +14,7 @@ Add custom news source - + :/images/user_profile.svg:/images/user_profile.svg @@ -82,7 +82,7 @@ Add/Update &recipe - + :/images/plus.svg:/images/plus.svg @@ -93,7 +93,7 @@ &Remove recipe - + :/images/list_remove.svg:/images/list_remove.svg @@ -104,7 +104,7 @@ &Share recipe - + :/images/forward.svg:/images/forward.svg @@ -115,7 +115,7 @@ Customize &builtin recipe - + :/images/news.svg:/images/news.svg @@ -126,7 +126,7 @@ &Load recipe from file - + :/images/chapters.svg:/images/chapters.svg @@ -285,7 +285,7 @@ p, li { white-space: pre-wrap; } ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -299,7 +299,7 @@ p, li { white-space: pre-wrap; } ... - + :/images/list_remove.svg:/images/list_remove.svg @@ -310,7 +310,7 @@ p, li { white-space: pre-wrap; } ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -361,7 +361,7 @@ p, li { white-space: pre-wrap; } &Add feed - + :/images/plus.svg:/images/plus.svg @@ -455,7 +455,7 @@ p, li { white-space: pre-wrap; } - + diff --git a/src/calibre/gui2/lrf_renderer/config.ui b/src/calibre/gui2/lrf_renderer/config.ui index cad538090a..448ee2f9a0 100644 --- a/src/calibre/gui2/lrf_renderer/config.ui +++ b/src/calibre/gui2/lrf_renderer/config.ui @@ -14,7 +14,7 @@ Configure Viewer - + :/images/config.svg:/images/config.svg @@ -67,7 +67,7 @@ - + diff --git a/src/calibre/gui2/lrf_renderer/main.ui b/src/calibre/gui2/lrf_renderer/main.ui index 02e0f60493..2eeb400cc4 100644 --- a/src/calibre/gui2/lrf_renderer/main.ui +++ b/src/calibre/gui2/lrf_renderer/main.ui @@ -19,7 +19,7 @@ LRF Viewer - + :/images/viewer.svg:/images/viewer.svg @@ -173,7 +173,7 @@ - + :/images/next.svg:/images/next.svg @@ -182,7 +182,7 @@ - + :/images/previous.svg:/images/previous.svg @@ -191,7 +191,7 @@ - + :/images/back.svg:/images/back.svg @@ -200,7 +200,7 @@ - + :/images/forward.svg:/images/forward.svg @@ -214,7 +214,7 @@ - + :/images/document_open.svg:/images/document_open.svg @@ -223,7 +223,7 @@ - + :/images/config.svg:/images/config.svg @@ -239,7 +239,7 @@ - + diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 0ad69a2f59..6273d275fd 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -24,7 +24,7 @@ __appname__ - + :/library:/library @@ -104,7 +104,7 @@ ... - + :/images/donate.svg:/images/donate.svg @@ -166,7 +166,7 @@ ... - + :/images/search.svg:/images/search.svg @@ -209,7 +209,7 @@ ... - + :/images/clear_left.svg:/images/clear_left.svg @@ -484,7 +484,7 @@ - + :/images/add_book.svg:/images/add_book.svg @@ -499,7 +499,7 @@ - + :/images/trash.svg:/images/trash.svg @@ -514,7 +514,7 @@ - + :/images/edit_input.svg:/images/edit_input.svg @@ -532,7 +532,7 @@ false - + :/images/sync.svg:/images/sync.svg @@ -541,7 +541,7 @@ - + :/images/save.svg:/images/save.svg @@ -553,7 +553,7 @@ - + :/images/news.svg:/images/news.svg @@ -565,7 +565,7 @@ - + :/images/convert.svg:/images/convert.svg @@ -577,7 +577,7 @@ - + :/images/view.svg:/images/view.svg @@ -589,7 +589,7 @@ - + :/images/document_open.svg:/images/document_open.svg @@ -598,7 +598,7 @@ - + :/images/dialog_information.svg:/images/dialog_information.svg @@ -607,7 +607,7 @@ - + :/images/user_profile.svg:/images/user_profile.svg @@ -616,7 +616,7 @@ - + :/images/books_in_series.svg:/images/books_in_series.svg @@ -625,7 +625,7 @@ - + :/images/publisher.png:/images/publisher.png @@ -634,7 +634,7 @@ - + :/images/tags.svg:/images/tags.svg @@ -643,7 +643,7 @@ - + :/images/config.svg:/images/config.svg @@ -685,7 +685,7 @@ - + diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui index 7b286f194c..45a6f539c2 100644 --- a/src/calibre/gui2/viewer/config.ui +++ b/src/calibre/gui2/viewer/config.ui @@ -14,7 +14,7 @@ Configure Ebook viewer - + :/images/config.svg:/images/config.svg @@ -240,7 +240,7 @@ buttonBox - + diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui index 9161e76491..7028ea3a65 100644 --- a/src/calibre/gui2/viewer/main.ui +++ b/src/calibre/gui2/viewer/main.ui @@ -14,7 +14,7 @@ Ebook Viewer - + :/images/viewer.svg:/images/viewer.svg @@ -104,7 +104,7 @@ - + :/images/back.svg:/images/back.svg @@ -113,7 +113,7 @@ - + :/images/forward.svg:/images/forward.svg @@ -122,7 +122,7 @@ - + :/images/next.svg:/images/next.svg @@ -131,7 +131,7 @@ - + :/images/previous.svg:/images/previous.svg @@ -140,7 +140,7 @@ - + :/images/font_size_larger.svg:/images/font_size_larger.svg @@ -149,7 +149,7 @@ - + :/images/font_size_smaller.svg:/images/font_size_smaller.svg @@ -158,7 +158,7 @@ - + :/images/chapters.svg:/images/chapters.svg @@ -167,7 +167,7 @@ - + :/images/dialog_information.svg:/images/dialog_information.svg @@ -176,7 +176,7 @@ - + :/images/document_open.svg:/images/document_open.svg @@ -185,7 +185,7 @@ - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -194,7 +194,7 @@ - + :/images/convert.svg:/images/convert.svg @@ -203,7 +203,7 @@ - + :/images/config.svg:/images/config.svg @@ -212,7 +212,7 @@ - + :/images/lookfeel.svg:/images/lookfeel.svg @@ -221,7 +221,7 @@ - + :/images/bookmarks.svg:/images/bookmarks.svg @@ -230,7 +230,7 @@ - + :/images/page.svg:/images/page.svg @@ -239,7 +239,7 @@ - + :/images/print.svg:/images/print.svg @@ -255,7 +255,7 @@ - + diff --git a/src/calibre/gui2/wizard/device.ui b/src/calibre/gui2/wizard/device.ui index 27af13b3ed..a422140bb1 100644 --- a/src/calibre/gui2/wizard/device.ui +++ b/src/calibre/gui2/wizard/device.ui @@ -14,7 +14,7 @@ Welcome to calibre - + :/images/wizard.svg:/images/wizard.svg @@ -69,7 +69,7 @@ - + diff --git a/src/calibre/gui2/wizard/send_email.ui b/src/calibre/gui2/wizard/send_email.ui index 3802d7f451..f248b8df89 100644 --- a/src/calibre/gui2/wizard/send_email.ui +++ b/src/calibre/gui2/wizard/send_email.ui @@ -202,7 +202,7 @@ Use Gmail - + :/images/gmail_logo.png:/images/gmail_logo.png @@ -228,7 +228,7 @@ - + diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index cd3b1c75ef..4090605341 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -68,8 +68,11 @@ def set_translators(): if buf is None: buf = open(os.path.join(messages_path(hlang), 'messages.mo'), 'rb') - iso639 = open(os.path.join(messages_path(hlang), - 'iso639.mo'), 'rb') + if hlang == 'nds': + hlang = 'de' + isof = os.path.join(messages_path(hlang), 'iso639.mo') + if os.path.exists(isof): + iso639 = open(isof, 'rb') if buf is not None: t = GNUTranslations(buf) From 039aab161ca65ad4455948c99df6ae43c68249e8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 13:33:01 -0600 Subject: [PATCH 05/22] Remove use of library image alias --- src/calibre/gui2/dialogs/conversion_error.ui | 2 +- src/calibre/gui2/main.py | 4 ++-- src/calibre/gui2/main.ui | 2 +- src/calibre/gui2/widgets.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/dialogs/conversion_error.ui b/src/calibre/gui2/dialogs/conversion_error.ui index 28272b7c6e..6521cb8c59 100644 --- a/src/calibre/gui2/dialogs/conversion_error.ui +++ b/src/calibre/gui2/dialogs/conversion_error.ui @@ -14,7 +14,7 @@ - :/library:/library + :/images/library.png:/images/library.png diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index d023851f15..4db6f4c1fb 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -146,7 +146,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.device_connected = False self.viewers = collections.deque() self.content_server = None - self.system_tray_icon = QSystemTrayIcon(QIcon(':/library'), self) + self.system_tray_icon = QSystemTrayIcon(QIcon(':/images/library.png'), self) self.system_tray_icon.setToolTip('calibre') if not config['systray_icon']: self.system_tray_icon.hide() @@ -1826,7 +1826,7 @@ def init_qt(args): QCoreApplication.setApplicationName(APP_UID) app = Application(args) actions = tuple(Main.create_application_menubar()) - app.setWindowIcon(QIcon(':/library')) + app.setWindowIcon(QIcon(':/images/library.png')) return app, opts, args, actions def run_gui(opts, args, actions, listener, app): diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 6273d275fd..0be1df12df 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -25,7 +25,7 @@ - :/library:/library + :/images/library.png:/images/library.png diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 9b5780d0b8..d2a24ddebc 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -159,7 +159,7 @@ class LocationModel(QAbstractListModel): def __init__(self, parent): QAbstractListModel.__init__(self, parent) - self.icons = [QVariant(QIcon(':/library')), + self.icons = [QVariant(QIcon(':/images/library.png')), QVariant(QIcon(':/images/reader.svg')), QVariant(QIcon(':/images/sd.svg')), QVariant(QIcon(':/images/sd.svg'))] From 1ce7847d8da3a70d7d90d0a05478300993ab2768 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 13:48:26 -0600 Subject: [PATCH 06/22] Fix setup commands help messages --- setup.py | 5 ++--- setup/commands.py | 25 +++++++++++++++++-------- setup/extensions.py | 10 +++++----- setup/install.py | 17 +++++++---------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/setup.py b/setup.py index 76ddd8b43a..516a3ad198 100644 --- a/setup.py +++ b/setup.py @@ -42,9 +42,8 @@ def main(args=sys.argv): 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)) + parser.set_usage('Usage: python setup.py %s [options]\n\n'%args[1]+\ + command.description) opts, args = parser.parse_args(args) command.run_all(opts) diff --git a/setup/commands.py b/setup/commands.py index a05d158e01..1d71d4753e 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -11,7 +11,7 @@ __all__ = [ 'build', 'gui', 'develop', - 'clean' + 'clean', 'clean_backups', ] import os, shutil @@ -32,6 +32,22 @@ develop = Develop() from setup.gui import GUI gui = GUI() +class CleanBackups(Command): + + description='Delete all backup files in the calibre source tree' + + def clean(self): + return self.run(None) + + def run(self, opts=None): + 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)) + +clean_backups = CleanBackups() + class Clean(Command): description='''Delete all computer generated files in the source tree''' @@ -55,13 +71,6 @@ class Clean(Command): 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) diff --git a/setup/extensions.py b/setup/extensions.py index 25b4979022..1be58a5839 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -157,9 +157,7 @@ if iswindows: class Build(Command): - def add_options(self, parser): - parser.set_usage(parser.usage + textwrap.dedent(''' - + description = 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: @@ -179,9 +177,11 @@ class Build(Command): PODOFO_LIB_DIR - podofo library files QMAKE - Path to qmake - VS90COMNTOOLS - Location of Microsoft Visual Studio 9 Tools + VS90COMNTOOLS - Location of Microsoft Visual Studio 9 Tools (windows only) - ''')) + ''') + + def add_options(self, parser): choices = [e.name for e in extensions]+['all'] parser.add_option('-1', '--only', choices=choices, default='all', help=('Build only the named extension. Available: '+ diff --git a/setup/install.py b/setup/install.py index 540de730e6..bf726899c0 100644 --- a/setup/install.py +++ b/setup/install.py @@ -30,21 +30,18 @@ sys.exit({func!s}()) class Develop(Command): - description = 'Setup a development environment' + description = textwrap.dedent('''\ + Setup a development environment for calibre. + This allows you to run calibre directly from the source tree. + Binaries will be installed in /bin where is + the prefix of your python installation. This can be controlled + via the --prefix option. + ''') MODE = 0755 sub_commands = ['build', 'translations'] 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 /bin where 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 /bin') From 792c6b0b22d2f3241c9500c1f3e321a4bd8fb299 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 16:03:44 -0600 Subject: [PATCH 07/22] Refactored image access for new resources framework --- session.vim | 2 +- setup.py | 30 ++++++++++++ setup/commands.py | 47 ------------------- setup/gui.py | 18 +++---- src/calibre/ebooks/epub/output.py | 11 +---- src/calibre/gui2/__init__.py | 10 ++-- src/calibre/gui2/convert/__init__.py | 2 +- src/calibre/gui2/convert/bulk.py | 2 +- src/calibre/gui2/convert/debug.py | 2 +- src/calibre/gui2/convert/look_and_feel.py | 2 +- src/calibre/gui2/convert/metadata.py | 2 +- src/calibre/gui2/convert/single.py | 13 +---- .../gui2/convert/structure_detection.py | 2 +- src/calibre/gui2/convert/toc.py | 2 +- src/calibre/gui2/device.py | 24 +++++----- src/calibre/gui2/dialogs/config/__init__.py | 14 +++--- src/calibre/gui2/dialogs/metadata_single.py | 2 +- src/calibre/gui2/dialogs/scheduler.py | 12 ++--- src/calibre/gui2/jobs.py | 8 ++-- src/calibre/gui2/library.py | 2 +- src/calibre/gui2/lrf_renderer/main.py | 2 +- src/calibre/gui2/main.py | 12 ++--- src/calibre/gui2/main_window.py | 4 +- src/calibre/gui2/status.py | 8 ++-- src/calibre/gui2/tag_view.py | 10 ++-- src/calibre/gui2/viewer/main.py | 6 +-- src/calibre/gui2/widgets.py | 12 ++--- src/calibre/gui2/wizard/__init__.py | 4 +- src/calibre/linux.py | 7 ++- src/calibre/translations/dynamic.py | 16 ++++--- src/calibre/utils/localization.py | 36 ++++++++------ src/calibre/utils/resources.py | 5 +- src/calibre/web/feeds/news.py | 10 +--- 33 files changed, 154 insertions(+), 185 deletions(-) diff --git a/session.vim b/session.vim index 50c2d5285e..56705f9528 100644 --- a/session.vim +++ b/session.vim @@ -1,5 +1,5 @@ " Project wide builtins -let g:pyflakes_builtins += ["dynamic_property", "__", "P"] +let g:pyflakes_builtins += ["dynamic_property", "__", "P", "I"] python << EOFPY import os diff --git a/setup.py b/setup.py index 516a3ad198..d5cf8a406a 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,23 @@ def check_version_info(): def option_parser(): parser = optparse.OptionParser() + parser.add_option('-c', '--clean', default=False, action='store_true', + help=('Instead of running the command delete all files generated ' + 'by the command')) + parser.add_option('--clean-backups', default=False, action='store_true', + help='Delete all backup files from the source tree') + parser.add_option('--clean-all', default=False, action='store_true', + help='Delete all machine generated files from the source tree') return parser +def clean_backups(): + for root, _, files in os.walk('.'): + for name in files: + for t in ('.pyc', '.pyo', '~', '.swp', '.swo'): + if name.endswith(t): + os.remove(os.path.join(root, name)) + + def main(args=sys.argv): if len(args) == 1 or args[1] in ('-h', '--help'): print 'Usage: python', args[0], 'command', '[options]' @@ -46,6 +61,21 @@ def main(args=sys.argv): command.description) opts, args = parser.parse_args(args) + + if opts.clean_backups: + clean_backups() + + if opts.clean: + prints('Cleaning', args[1]) + command.clean() + return 0 + + if opts.clean_all(): + for cmd in commands.__all__: + prints('Cleaning', cmd) + getattr(commands, cmd).clean() + return 0 + command.run_all(opts) warnings = get_warnings() diff --git a/setup/commands.py b/setup/commands.py index 1d71d4753e..de44c75538 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -11,13 +11,10 @@ __all__ = [ 'build', 'gui', 'develop', - 'clean', 'clean_backups', ] -import os, shutil from setup.translations import POT, GetTranslations, Translations, ISO639 -from setup import Command pot = POT() translations = Translations() get_translations = GetTranslations() @@ -32,50 +29,6 @@ develop = Develop() from setup.gui import GUI gui = GUI() -class CleanBackups(Command): - - description='Delete all backup files in the calibre source tree' - - def clean(self): - return self.run(None) - - def run(self, opts=None): - 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)) - -clean_backups = CleanBackups() - -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 dir in ('dist', os.path.join('src', 'calibre.egg-info')): - shutil.rmtree(dir, ignore_errors=True) - -clean = Clean() - commands = {} for x in __all__: diff --git a/setup/gui.py b/setup/gui.py index 5dd53e4cdd..d3c4071ffd 100644 --- a/setup/gui.py +++ b/setup/gui.py @@ -55,30 +55,30 @@ class GUI(Command): def build_forms(self): from PyQt4.uic import compileUi forms = self.find_forms() + pat = re.compile(r'''(['"]):/images/([^'"]+)\1''') + def sub(match): + ans = 'I(%s%s%s)'%(match.group(1), match.group(2), match.group(1)) + return ans + for form in forms: compiled_form = self.form_to_compiled_form(form) if not os.path.exists(compiled_form) or os.stat(form).st_mtime > os.stat(compiled_form).st_mtime: - print 'Compiling form', form + self.info('\tCompiling form', form) buf = cStringIO.StringIO() compileUi(form, buf) dat = buf.getvalue() dat = dat.replace('__appname__', __appname__) - dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc') + dat = dat.replace('import images_rc', '') dat = dat.replace('from library import', 'from calibre.gui2.library import') dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import') dat = dat.replace('from convert.xpath_wizard import', 'from calibre.gui2.convert.xpath_wizard import') dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(? 1 else None, diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index d2a24ddebc..c0f8b13e44 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -27,7 +27,7 @@ class ProgressIndicator(QWidget): def __init__(self, *args): QWidget.__init__(self, *args) self.setGeometry(0, 0, 300, 350) - self.movie = QMovie(':/images/jobs-animated.mng') + self.movie = QMovie(I('jobs-animated.mng')) self.ml = QLabel(self) self.ml.setMovie(self.movie) self.movie.start() @@ -159,10 +159,10 @@ class LocationModel(QAbstractListModel): def __init__(self, parent): QAbstractListModel.__init__(self, parent) - self.icons = [QVariant(QIcon(':/images/library.png')), - QVariant(QIcon(':/images/reader.svg')), - QVariant(QIcon(':/images/sd.svg')), - QVariant(QIcon(':/images/sd.svg'))] + self.icons = [QVariant(QIcon(I('library.png'))), + QVariant(QIcon(I('reader.svg'))), + QVariant(QIcon(I('sd.svg'))), + QVariant(QIcon(I('sd.svg')))] self.text = [_('Library\n%d\nbooks'), _('Reader\n%s\navailable'), _('Card A\n%s\navailable'), @@ -313,7 +313,7 @@ class EjectButton(QAbstractButton): def paintEvent(self, event): painter = QPainter(self) painter.setClipRect(event.rect()) - image = QPixmap(':/images/eject').scaledToHeight(event.rect().height(), + image = QPixmap(I('eject')).scaledToHeight(event.rect().height(), Qt.SmoothTransformation) if not self.mouse_over: diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index f4aea4268f..df7cdc7fd6 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -532,8 +532,8 @@ class Wizard(QWizard): self.setPixmap(self.LogoPixmap, p.scaledToHeight(80, Qt.SmoothTransformation)) self.setPixmap(self.WatermarkPixmap, - QPixmap(':/images/welcome_wizard.svg')) - self.setPixmap(self.BackgroundPixmap, QPixmap(':/images/wizard.svg')) + QPixmap(I('welcome_wizard.svg'))) + self.setPixmap(self.BackgroundPixmap, QPixmap(I('wizard.svg'))) self.device_page = DevicePage() self.library_page = LibraryPage() self.finish_page = FinishPage() diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 53e15dcfd6..73fbbc6e00 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -428,7 +428,6 @@ def render_svg(image, dest): def setup_desktop_integration(fatal_errors): try: from PyQt4.QtCore import QFile - from calibre.gui2 import images_rc # Load images from tempfile import mkdtemp print 'Setting up desktop integration...' @@ -438,12 +437,12 @@ def setup_desktop_integration(fatal_errors): cwd = os.getcwdu() try: os.chdir(tdir) - render_svg(QFile(':/images/mimetypes/lrf.svg'), os.path.join(tdir, 'calibre-lrf.png')) + render_svg(QFile(I('mimetypes/lrf.svg')), os.path.join(tdir, 'calibre-lrf.png')) check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) - QFile(':library').copy(os.path.join(tdir, 'calibre-gui.png')) + QFile(I('library.png')).copy(os.path.join(tdir, 'calibre-gui.png')) check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True) - render_svg(QFile(':/images/viewer.svg'), os.path.join(tdir, 'calibre-viewer.png')) + render_svg(QFile(I('viewer.svg')), os.path.join(tdir, 'calibre-viewer.png')) check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) f = open('calibre-lrfviewer.desktop', 'wb') diff --git a/src/calibre/translations/dynamic.py b/src/calibre/translations/dynamic.py index 6131a84c8f..c1f368ff5a 100644 --- a/src/calibre/translations/dynamic.py +++ b/src/calibre/translations/dynamic.py @@ -5,9 +5,10 @@ Dynamic language lookup of translations for user-visible strings. __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -from cStringIO import StringIO +import os + from gettext import GNUTranslations -from calibre.translations.compiled import translations +from calibre.utils.localization import get_lc_messages_path __all__ = ['translate'] @@ -17,10 +18,13 @@ def translate(lang, text): trans = None if lang in _CACHE: trans = _CACHE[lang] - elif lang in translations: - buf = StringIO(translations[lang]) - trans = GNUTranslations(buf) - _CACHE[lang] = trans + else: + mpath = get_lc_messages_path(lang) + if mpath is not None: + p = os.path.join(mpath, 'messages.mo') + if os.path.exists(p): + trans = GNUTranslations(open(p, 'rb')) + _CACHE[lang] = trans if trans is None: return getattr(__builtins__, '_', lambda x: x)(text) return trans.ugettext(text) diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index 4090605341..b4323e0a65 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -44,6 +44,19 @@ def get_lang(): def messages_path(lang): return P('localization/locales/%s/LC_MESSAGES'%lang) +def get_lc_messages_path(lang): + hlang = None + if lang in available_translations(): + hlang = lang + else: + xlang = lang.split('_')[0] + if xlang in available_translations(): + hlang = xlang + if hlang is not None: + return messages_path(hlang) + return None + + def set_translators(): # To test different translations invoke as # CALIBRE_OVERRIDE_LANG=de_DE.utf8 program @@ -57,20 +70,12 @@ def set_translators(): make(lang+'.po', buf) buf = cStringIO.StringIO(buf.getvalue()) - hlang = None - if lang in available_translations(): - hlang = lang - else: - xlang = lang.split('_')[0] - if xlang in available_translations(): - hlang = xlang - if hlang is not None: + mpath = get_lc_messages_path(lang) + if mpath is not None: if buf is None: - buf = open(os.path.join(messages_path(hlang), - 'messages.mo'), 'rb') - if hlang == 'nds': - hlang = 'de' - isof = os.path.join(messages_path(hlang), 'iso639.mo') + buf = open(os.path.join(mpath, 'messages.mo'), 'rb') + mpath = mpath.replace(os.sep+'nds'+os.sep, os.sep+'de'+os.sep) + isof = os.path.join(mpath, 'iso639.mo') if os.path.exists(isof): iso639 = open(isof, 'rb') @@ -115,8 +120,9 @@ def set_qt_translator(translator): if lang is not None: if lang == 'nds': lang = 'de' - for x in (lang, lang.split('_')[0]): - p = os.path.join(messages_path(x), 'qt.qm') + mpath = get_lc_messages_path(lang) + if mpath is not None: + p = os.path.join(mpath, 'qt.qm') if os.path.exists(p): return translator.load(p) return False diff --git a/src/calibre/utils/resources.py b/src/calibre/utils/resources.py index 98f056e065..0baaac014b 100644 --- a/src/calibre/utils/resources.py +++ b/src/calibre/utils/resources.py @@ -13,5 +13,8 @@ def get_path(path): path = path.replace(os.sep, '/') return os.path.join(sys.resources_location, *path.split('/')) -__builtin__.__dict__['P'] = get_path +def get_image_path(path): + return get_path('images/'+path) +__builtin__.__dict__['P'] = get_path +__builtin__.__dict__['I'] = get_image_path diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 4e81f15d89..f28a41a7cb 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -13,8 +13,6 @@ from functools import partial from contextlib import nested, closing from datetime import datetime -from PyQt4.Qt import QApplication, QFile, QIODevice - from calibre import browser, __appname__, iswindows, \ strftime, __version__, preferred_encoding @@ -812,17 +810,11 @@ class BasicNewsRecipe(Recipe): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): return False - from calibre.gui2 import images_rc # Needed for access to logo - images_rc - if QApplication.instance() is None: QApplication([]) - f = QFile(':/library') - f.open(QIODevice.ReadOnly) - img_data = str(f.readAll()) + img_data = open(I('library.png'), 'rb').read() tdir = PersistentTemporaryDirectory('_default_cover') img = os.path.join(tdir, 'logo.png') with open(img, 'wb') as g: g.write(img_data) - f.close() img = os.path.basename(img) html= u'''\ From f9ff180347431e17d1a398031e9c1c567fac63bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Sep 2009 19:03:52 -0600 Subject: [PATCH 08/22] Implement a check setup command that uses PyFlakes to check for various errors --- .bzrignore | 2 +- setup.py | 3 +- setup/check.py | 75 + setup/commands.py | 3 + setup/gui.py | 3 +- src/calibre/__init__.py | 5 + .../ebooks/chardet/codingstatemachine.py | 6 +- src/calibre/ebooks/chardet/escprober.py | 8 +- src/calibre/ebooks/chardet/sbcsgroupprober.py | 6 +- src/calibre/ebooks/chardet/utf8prober.py | 6 +- src/calibre/ebooks/epub/pages.py | 9 +- src/calibre/ebooks/lit/lzx.py | 1 - src/calibre/ebooks/lit/maps/__init__.py | 2 + src/calibre/ebooks/lrf/fonts/__init__.py | 14 +- src/calibre/ebooks/lrf/html/color_map.py | 6 +- src/calibre/ebooks/lrf/html/convert_to.py | 30 +- src/calibre/ebooks/lrf/html/table.py | 138 +- src/calibre/ebooks/lrf/pylrs/elements.py | 162 +- src/calibre/ebooks/lrf/pylrs/pylrf.py | 1568 ++--- src/calibre/ebooks/lrf/pylrs/pylrfopt.py | 86 +- src/calibre/ebooks/lrf/pylrs/pylrs.py | 5187 +++++++++-------- src/calibre/ebooks/markdown/__init__.py | 4 +- src/calibre/ebooks/markdown/mdx_toc.py | 28 +- src/calibre/ebooks/metadata/imp.py | 6 +- src/calibre/ebooks/metadata/lrx.py | 8 +- src/calibre/ebooks/metadata/odt.py | 10 +- src/calibre/ebooks/metadata/zip.py | 8 +- src/calibre/ebooks/pdb/palmdoc/writer.py | 1 - src/calibre/ebooks/pdb/ztxt/__init__.py | 1 - src/calibre/ebooks/pdf/manipulate/decrypt.py | 24 +- src/calibre/ebooks/pdf/manipulate/encrypt.py | 14 +- src/calibre/ebooks/pdf/verify.py | 8 +- src/calibre/ebooks/rb/writer.py | 1 - src/calibre/ebooks/rtf2xml/copy.py | 6 +- src/calibre/ebooks/rtf2xml/options_trem.py | 1 - src/calibre/ebooks/rtf2xml/output.py | 1 - src/calibre/ebooks/rtf2xml/override_table.py | 2 - src/calibre/gui2/dialogs/choose_format.py | 8 +- src/calibre/gui2/dialogs/conversion_error.py | 6 +- src/calibre/gui2/lrf_renderer/bookview.py | 14 +- src/calibre/gui2/viewer/documentview.py | 2 +- src/calibre/gui2/viewer/printing.py | 41 +- src/calibre/manual/conf.py | 2 +- src/calibre/path.py | 970 --- src/calibre/translations/automatic.py | 121 - src/calibre/utils/localization.py | 5 +- src/calibre/utils/pyparsing.py | 20 +- src/calibre/utils/rss_gen.py | 29 +- src/calibre/web/feeds/recipes/__init__.py | 13 +- .../web/feeds/recipes/recipe_24sata.py | 120 +- .../web/feeds/recipes/recipe_24sata_rs.py | 134 +- src/calibre/web/feeds/recipes/recipe_7dias.py | 142 +- .../feeds/recipes/recipe_accountancyage.py | 116 +- .../feeds/recipes/recipe_adventuregamers.py | 150 +- .../web/feeds/recipes/recipe_ambito.py | 115 +- .../web/feeds/recipes/recipe_amspec.py | 108 +- .../web/feeds/recipes/recipe_axxon_news.py | 122 +- .../web/feeds/recipes/recipe_azstarnet.py | 128 +- src/calibre/web/feeds/recipes/recipe_b92.py | 136 +- .../web/feeds/recipes/recipe_barrons.py | 184 +- .../web/feeds/recipes/recipe_bbcvietnamese.py | 68 +- src/calibre/web/feeds/recipes/recipe_beta.py | 100 +- .../web/feeds/recipes/recipe_beta_en.py | 73 +- src/calibre/web/feeds/recipes/recipe_blic.py | 129 +- src/calibre/web/feeds/recipes/recipe_borba.py | 188 +- .../recipes/recipe_buenosaireseconomico.py | 142 +- .../recipes/recipe_chicago_breaking_news.py | 90 +- .../feeds/recipes/recipe_chicago_tribune.py | 4 - .../web/feeds/recipes/recipe_clarin.py | 144 +- .../feeds/recipes/recipe_climate_progress.py | 90 +- .../web/feeds/recipes/recipe_coding_horror.py | 80 +- .../recipes/recipe_corriere_della_sera_en.py | 90 +- .../recipes/recipe_corriere_della_sera_it.py | 110 +- .../recipes/recipe_courrierinternational.py | 9 +- .../feeds/recipes/recipe_criticadigital.py | 122 +- .../web/feeds/recipes/recipe_cubadebate.py | 87 +- .../web/feeds/recipes/recipe_daily_mail.py | 66 +- src/calibre/web/feeds/recipes/recipe_danas.py | 122 +- .../web/feeds/recipes/recipe_degentenaar.py | 150 +- .../web/feeds/recipes/recipe_der_standard.py | 134 +- .../web/feeds/recipes/recipe_diagonales.py | 142 +- .../web/feeds/recipes/recipe_diepresse.py | 142 +- .../web/feeds/recipes/recipe_dnevni_avaz.py | 136 +- .../web/feeds/recipes/recipe_dnevnik_cro.py | 148 +- .../web/feeds/recipes/recipe_e_novine.py | 116 +- .../web/feeds/recipes/recipe_ecogeek.py | 62 +- .../feeds/recipes/recipe_el_mercurio_chile.py | 115 +- .../web/feeds/recipes/recipe_el_universal.py | 130 +- .../web/feeds/recipes/recipe_elargentino.py | 122 +- .../web/feeds/recipes/recipe_elcronista.py | 142 +- .../web/feeds/recipes/recipe_elmundo.py | 113 +- .../recipes/recipe_elperiodico_catalan.py | 110 +- .../recipes/recipe_elperiodico_spanish.py | 110 +- .../web/feeds/recipes/recipe_eltiempo_hn.py | 104 +- .../web/feeds/recipes/recipe_endgadget.py | 61 +- .../web/feeds/recipes/recipe_esquire.py | 124 +- .../web/feeds/recipes/recipe_exiled.py | 114 +- .../feeds/recipes/recipe_expansion_spanish.py | 116 +- .../web/feeds/recipes/recipe_fastcompany.py | 108 +- .../web/feeds/recipes/recipe_faznet.py | 100 +- .../web/feeds/recipes/recipe_fudzilla.py | 53 +- .../web/feeds/recipes/recipe_glas_srpske.py | 192 +- .../feeds/recipes/recipe_glasgow_herald.py | 68 +- .../web/feeds/recipes/recipe_glasjavnosti.py | 156 +- .../feeds/recipes/recipe_globe_and_mail.py | 138 +- .../web/feeds/recipes/recipe_granma.py | 107 +- .../web/feeds/recipes/recipe_greader.py | 74 +- .../web/feeds/recipes/recipe_gva_be.py | 126 +- .../web/feeds/recipes/recipe_harpers.py | 96 +- .../web/feeds/recipes/recipe_harpers_full.py | 162 +- src/calibre/web/feeds/recipes/recipe_hln.py | 104 +- .../web/feeds/recipes/recipe_hln_be.py | 70 +- .../recipes/recipe_honoluluadvertiser.py | 118 +- src/calibre/web/feeds/recipes/recipe_hrt.py | 132 +- .../web/feeds/recipes/recipe_infobae.py | 116 +- .../web/feeds/recipes/recipe_inquirer_net.py | 122 +- .../web/feeds/recipes/recipe_instapaper.py | 154 +- .../web/feeds/recipes/recipe_intelligencer.py | 90 +- .../web/feeds/recipes/recipe_irish_times.py | 76 +- .../feeds/recipes/recipe_joelonsoftware.py | 54 +- .../web/feeds/recipes/recipe_jutarnji.py | 164 +- .../feeds/recipes/recipe_juventudrebelde.py | 108 +- .../recipes/recipe_juventudrebelde_english.py | 81 +- .../web/feeds/recipes/recipe_krstarica.py | 130 +- .../web/feeds/recipes/recipe_krstarica_en.py | 114 +- .../web/feeds/recipes/recipe_la_cuarta.py | 99 +- .../web/feeds/recipes/recipe_la_segunda.py | 120 +- .../web/feeds/recipes/recipe_la_tercera.py | 121 +- .../feeds/recipes/recipe_lamujerdemivida.py | 152 +- .../web/feeds/recipes/recipe_lanacion.py | 113 +- .../feeds/recipes/recipe_lanacion_chile.py | 101 +- .../web/feeds/recipes/recipe_laprensa.py | 113 +- .../web/feeds/recipes/recipe_laprensa_hn.py | 108 +- .../web/feeds/recipes/recipe_laprensa_ni.py | 158 +- .../web/feeds/recipes/recipe_latribuna.py | 130 +- .../web/feeds/recipes/recipe_lavanguardia.py | 138 +- .../web/feeds/recipes/recipe_liberation.py | 78 +- .../feeds/recipes/recipe_linux_magazine.py | 74 +- .../web/feeds/recipes/recipe_livemint.py | 80 +- src/calibre/web/feeds/recipes/recipe_marca.py | 110 +- .../web/feeds/recipes/recipe_mediapart.py | 11 +- .../web/feeds/recipes/recipe_miami_herald.py | 106 +- .../web/feeds/recipes/recipe_miradasalsur.py | 142 +- .../web/feeds/recipes/recipe_mondedurable.py | 90 +- .../web/feeds/recipes/recipe_moneynews.py | 100 +- .../web/feeds/recipes/recipe_monitor.py | 196 +- .../web/feeds/recipes/recipe_msdnmag_en.py | 122 +- .../web/feeds/recipes/recipe_nacional_cro.py | 120 +- src/calibre/web/feeds/recipes/recipe_nasa.py | 174 +- .../web/feeds/recipes/recipe_new_scientist.py | 138 +- .../web/feeds/recipes/recipe_new_yorker.py | 114 +- .../recipes/recipe_newsweek_argentina.py | 142 +- src/calibre/web/feeds/recipes/recipe_nin.py | 182 +- src/calibre/web/feeds/recipes/recipe_noaa.py | 82 +- .../web/feeds/recipes/recipe_novosti.py | 114 +- src/calibre/web/feeds/recipes/recipe_nspm.py | 133 +- .../web/feeds/recipes/recipe_nspm_int.py | 76 +- .../web/feeds/recipes/recipe_nzz_ger.py | 132 +- .../web/feeds/recipes/recipe_ourdailybread.py | 70 +- .../web/feeds/recipes/recipe_outlook_india.py | 2 - .../web/feeds/recipes/recipe_pagina12.py | 106 +- .../web/feeds/recipes/recipe_pescanik.py | 130 +- .../web/feeds/recipes/recipe_physics_today.py | 76 +- .../web/feeds/recipes/recipe_physics_world.py | 8 +- .../web/feeds/recipes/recipe_pobjeda.py | 202 +- .../web/feeds/recipes/recipe_politico.py | 132 +- .../web/feeds/recipes/recipe_politika.py | 140 +- .../web/feeds/recipes/recipe_pressonline.py | 132 +- .../web/feeds/recipes/recipe_republika.py | 160 +- src/calibre/web/feeds/recipes/recipe_rts.py | 120 +- .../web/feeds/recipes/recipe_sciencedaily.py | 66 +- .../feeds/recipes/recipe_scott_hanselman.py | 80 +- .../web/feeds/recipes/recipe_seattle_times.py | 100 +- .../web/feeds/recipes/recipe_shacknews.py | 54 +- .../web/feeds/recipes/recipe_slashdot.py | 72 +- src/calibre/web/feeds/recipes/recipe_slate.py | 115 +- .../web/feeds/recipes/recipe_soldiers.py | 114 +- .../web/feeds/recipes/recipe_spiegel_int.py | 78 +- .../web/feeds/recipes/recipe_spiegelde.py | 128 +- .../recipes/recipe_st_petersburg_times.py | 96 +- .../web/feeds/recipes/recipe_stackoverflow.py | 66 +- .../web/feeds/recipes/recipe_starbulletin.py | 118 +- .../web/feeds/recipes/recipe_straitstimes.py | 112 +- .../web/feeds/recipes/recipe_tanjug.py | 90 +- .../feeds/recipes/recipe_telepolis_artikel.py | 2 +- .../recipes/recipe_the_budget_fashionista.py | 86 +- .../feeds/recipes/recipe_thedgesingapore.py | 128 +- .../recipes/recipe_theeconomictimes_india.py | 112 +- .../feeds/recipes/recipe_themarketticker.py | 48 +- .../web/feeds/recipes/recipe_theoldfoodie.py | 58 +- .../web/feeds/recipes/recipe_theonion.py | 90 +- src/calibre/web/feeds/recipes/recipe_tijd.py | 140 +- .../web/feeds/recipes/recipe_times_online.py | 130 +- src/calibre/web/feeds/recipes/recipe_tnxm.py | 56 +- .../web/feeds/recipes/recipe_tomshardware.py | 157 +- .../web/feeds/recipes/recipe_twitchfilms.py | 84 +- .../web/feeds/recipes/recipe_uncrate.py | 94 +- .../web/feeds/recipes/recipe_usnews.py | 120 +- src/calibre/web/feeds/recipes/recipe_utne.py | 100 +- .../web/feeds/recipes/recipe_vecernji_list.py | 124 +- .../web/feeds/recipes/recipe_veintitres.py | 142 +- .../web/feeds/recipes/recipe_vijesti.py | 114 +- .../web/feeds/recipes/recipe_vnexpress.py | 72 +- src/calibre/web/feeds/recipes/recipe_vreme.py | 224 +- .../web/feeds/recipes/recipe_wikinews_en.py | 140 +- .../web/feeds/recipes/recipe_winsupersite.py | 54 +- 206 files changed, 12460 insertions(+), 13498 deletions(-) create mode 100644 setup/check.py delete mode 100644 src/calibre/path.py delete mode 100644 src/calibre/translations/automatic.py diff --git a/.bzrignore b/.bzrignore index 5ae1ec3117..bd265eb830 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,5 +1,5 @@ *_ui.py -moc_*.cpp +.check-cache.pickle src/calibre/plugins resources/images.qrc src/calibre/manual/.build/ diff --git a/setup.py b/setup.py index d5cf8a406a..b0acff3963 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' - import sys, os, optparse sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) @@ -70,7 +69,7 @@ def main(args=sys.argv): command.clean() return 0 - if opts.clean_all(): + if opts.clean_all: for cmd in commands.__all__: prints('Cleaning', cmd) getattr(commands, cmd).clean() diff --git a/setup/check.py b/setup/check.py new file mode 100644 index 0000000000..75a6d82530 --- /dev/null +++ b/setup/check.py @@ -0,0 +1,75 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import sys, os, cPickle, subprocess +from operator import attrgetter +from setup import Command + +def check_for_python_errors(filename, builtins): + from pyflakes import checker, ast + + contents = open(filename, 'rb').read() + + try: + tree = ast.parse(contents, filename) + except: + import traceback + traceback.print_exc() + try: + value = sys.exc_info()[1] + lineno, offset, line = value[1][1:] + except IndexError: + lineno, offset, line = 1, 0, '' + if line.endswith("\n"): + line = line[:-1] + + return [SyntaxError(filename, lineno, offset, str(value))] + else: + w = checker.Checker(tree, filename, builtins = builtins) + w.messages.sort(key = attrgetter('lineno')) + return w.messages + + +class Check(Command): + + BUILTINS = ['_', '__', 'dynamic_property', 'I', 'P'] + CACHE = '.check-cache.pickle' + + def run(self, opts): + cache = {} + if os.path.exists(self.CACHE): + cache = cPickle.load(open(self.CACHE, 'rb')) + for x in os.walk(self.j(self.SRC, 'calibre')): + for f in x[-1]: + f = self.j(x[0], f) + mtime = os.stat(f).st_mtime + if f.endswith('.py') and cache.get(f, 0) != mtime and \ + self.b(f) not in ('ptempfile.py', 'feedparser.py', + 'pyparsing.py', 'markdown.py') and 'genshi' not in f and \ + 'prs500/driver.py' not in f: + self.info('\tChecking', f) + w = check_for_python_errors(f, self.BUILTINS) + if w: + self.report_errors(w) + cPickle.dump(cache, open(self.CACHE, 'wb'), -1) + subprocess.call(['gvim', '-f', f]) + raise SystemExit(1) + cache[f] = mtime + cPickle.dump(cache, open(self.CACHE, 'wb'), -1) + + + def report_errors(self, errors): + for err in errors: + if isinstance(err, SyntaxError): + print '\t\tSyntax Error' + else: + col = getattr(err, 'col', 0) if getattr(err, 'col', 0) else 0 + lineno = err.lineno if err.lineno else 0 + self.info('\t\t%d:%d:'%(lineno, col), + err.message%err.message_args) + diff --git a/setup/commands.py b/setup/commands.py index de44c75538..57ef2c63bf 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -11,6 +11,7 @@ __all__ = [ 'build', 'gui', 'develop', + 'check', ] @@ -29,6 +30,8 @@ develop = Develop() from setup.gui import GUI gui = GUI() +from setup.check import Check +check = Check() commands = {} for x in __all__: diff --git a/setup/gui.py b/setup/gui.py index d3c4071ffd..dd0bdfd204 100644 --- a/setup/gui.py +++ b/setup/gui.py @@ -78,9 +78,10 @@ class GUI(Command): dat = pat.sub(sub, dat) if form.endswith('viewer%smain.ui'%os.sep): - self.inf('\t\tPromoting WebView') + self.info('\t\tPromoting WebView') dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(') dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView' + dat += '\nQtWebKit' open(compiled_form, 'wb').write(dat) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index bfe101ffa7..1f14e3868e 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -21,6 +21,11 @@ from calibre.constants import iswindows, isosx, islinux, isfrozen, \ filesystem_encoding import mechanize +if False: + winutil, winutilerror, __appname__, islinux, __version__ + fcntl, win32event, isfrozen, __author__, terminal_controller + winerror, win32api + mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') mimetypes.add_type('application/xhtml+xml', '.xhtml') diff --git a/src/calibre/ebooks/chardet/codingstatemachine.py b/src/calibre/ebooks/chardet/codingstatemachine.py index 452d3b0a06..5e759007ea 100644 --- a/src/calibre/ebooks/chardet/codingstatemachine.py +++ b/src/calibre/ebooks/chardet/codingstatemachine.py @@ -13,19 +13,19 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA ######################### END LICENSE BLOCK ######################### -from constants import eStart, eError, eItsMe +from constants import eStart class CodingStateMachine: def __init__(self, sm): diff --git a/src/calibre/ebooks/chardet/escprober.py b/src/calibre/ebooks/chardet/escprober.py index 572ed7be37..5d98b2aad6 100644 --- a/src/calibre/ebooks/chardet/escprober.py +++ b/src/calibre/ebooks/chardet/escprober.py @@ -13,19 +13,19 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA ######################### END LICENSE BLOCK ######################### -import constants, sys +import constants from escsm import HZSMModel, ISO2022CNSMModel, ISO2022JPSMModel, ISO2022KRSMModel from charsetprober import CharSetProber from codingstatemachine import CodingStateMachine @@ -75,5 +75,5 @@ class EscCharSetProber(CharSetProber): self._mState = constants.eFoundIt self._mDetectedCharset = codingSM.get_coding_state_machine() return self.get_state() - + return self.get_state() diff --git a/src/calibre/ebooks/chardet/sbcsgroupprober.py b/src/calibre/ebooks/chardet/sbcsgroupprober.py index d19160c86c..6269d4c1d8 100644 --- a/src/calibre/ebooks/chardet/sbcsgroupprober.py +++ b/src/calibre/ebooks/chardet/sbcsgroupprober.py @@ -14,19 +14,19 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA ######################### END LICENSE BLOCK ######################### -import constants, sys +import constants from charsetgroupprober import CharSetGroupProber from sbcharsetprober import SingleByteCharSetProber from langcyrillicmodel import Win1251CyrillicModel, Koi8rModel, Latin5CyrillicModel, MacCyrillicModel, Ibm866Model, Ibm855Model diff --git a/src/calibre/ebooks/chardet/utf8prober.py b/src/calibre/ebooks/chardet/utf8prober.py index c1792bb377..1a1618ecc2 100644 --- a/src/calibre/ebooks/chardet/utf8prober.py +++ b/src/calibre/ebooks/chardet/utf8prober.py @@ -13,19 +13,19 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA ######################### END LICENSE BLOCK ######################### -import constants, sys +import constants from constants import eStart, eError, eItsMe from charsetprober import CharSetProber from codingstatemachine import CodingStateMachine diff --git a/src/calibre/ebooks/epub/pages.py b/src/calibre/ebooks/epub/pages.py index 4737107a6c..6cd2b60672 100644 --- a/src/calibre/ebooks/epub/pages.py +++ b/src/calibre/ebooks/epub/pages.py @@ -8,11 +8,10 @@ __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' __docformat__ = 'restructuredtext en' -import os, re -from itertools import count, chain -from calibre.ebooks.oeb.base import XHTML, XHTML_NS +import re +from itertools import count +from calibre.ebooks.oeb.base import XHTML_NS from calibre.ebooks.oeb.base import OEBBook -from lxml import etree, html from lxml.etree import XPath NSMAP = {'h': XHTML_NS, 'html': XHTML_NS, 'xhtml': XHTML_NS} @@ -55,5 +54,5 @@ def add_page_map(opfpath, opts): id = elem.attrib['id'] = idgen.next() href = '#'.join((item.href, id)) oeb.pages.add(name, href) - writer = DirWriter(version='2.0', page_map=True) + writer = None#DirWriter(version='2.0', page_map=True) writer.dump(oeb, opfpath) diff --git a/src/calibre/ebooks/lit/lzx.py b/src/calibre/ebooks/lit/lzx.py index ee46e729c3..3f324a65a6 100644 --- a/src/calibre/ebooks/lit/lzx.py +++ b/src/calibre/ebooks/lit/lzx.py @@ -6,7 +6,6 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -import sys from calibre import plugins _lzx, _error = plugins['lzx'] diff --git a/src/calibre/ebooks/lit/maps/__init__.py b/src/calibre/ebooks/lit/maps/__init__.py index 2235c384ff..b30974ba6b 100644 --- a/src/calibre/ebooks/lit/maps/__init__.py +++ b/src/calibre/ebooks/lit/maps/__init__.py @@ -7,3 +7,5 @@ Microsoft LIT tag and attribute tables. from calibre.ebooks.lit.maps.opf import MAP as OPF_MAP from calibre.ebooks.lit.maps.html import MAP as HTML_MAP + +OPF_MAP, HTML_MAP diff --git a/src/calibre/ebooks/lrf/fonts/__init__.py b/src/calibre/ebooks/lrf/fonts/__init__.py index 1f67a50f25..7fef457bc1 100644 --- a/src/calibre/ebooks/lrf/fonts/__init__.py +++ b/src/calibre/ebooks/lrf/fonts/__init__.py @@ -1,14 +1,14 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import sys, os -from calibre import iswindows +import os from calibre.ptempfile import PersistentTemporaryFile try: from PIL import ImageFont + ImageFont except ImportError: import ImageFont - + ''' Default fonts used in the PRS500 ''' @@ -48,11 +48,11 @@ def get_font_path(name): # then, try calibre shipped ones try: try: - font_mod = __import__('calibre.ebooks.lrf.fonts.prs500', {}, {}, + font_mod = __import__('calibre.ebooks.lrf.fonts.prs500', {}, {}, [fname], -1) getattr(font_mod, fname) except (ImportError, AttributeError): - font_mod = __import__('calibre.ebooks.lrf.fonts.liberation', {}, {}, + font_mod = __import__('calibre.ebooks.lrf.fonts.liberation', {}, {}, [LIBERATION_FONT_MAP[name]], -1) p = PersistentTemporaryFile('.ttf', 'font_') p.write(getattr(font_mod, fname).font_data) @@ -61,7 +61,7 @@ def get_font_path(name): return p.name except ImportError: pass - + # finally, try system default ones if SYSTEM_FONT_MAP.has_key(name) and os.access(SYSTEM_FONT_MAP[name], os.R_OK): return SYSTEM_FONT_MAP[name] @@ -71,7 +71,7 @@ def get_font_path(name): def get_font(name, size, encoding='unic'): ''' - Get an ImageFont object by name. + Get an ImageFont object by name. @param size: Font height in pixels. To convert from pts: sz in pixels = (dpi/72) * size in pts @param encoding: Font encoding to use. E.g. 'unic', 'symbol', 'ADOB', 'ADBE', 'aprm' diff --git a/src/calibre/ebooks/lrf/html/color_map.py b/src/calibre/ebooks/lrf/html/color_map.py index 78377de657..c1b5ea3d95 100644 --- a/src/calibre/ebooks/lrf/html/color_map.py +++ b/src/calibre/ebooks/lrf/html/color_map.py @@ -94,7 +94,7 @@ NAME_MAP = { u'springgreen': u'#00FF7F', u'violet': u'#EE82EE', u'yellowgreen': u'#9ACD32' - } + } hex_pat = re.compile('#(\d{2})(\d{2})(\d{2})') rgb_pat = re.compile('rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)', re.IGNORECASE) @@ -109,5 +109,5 @@ def lrs_color(html_color): if hcol in NAME_MAP: return NAME_MAP[hcol].replace('#', '0x00') return '0x00000000' - - \ No newline at end of file + + diff --git a/src/calibre/ebooks/lrf/html/convert_to.py b/src/calibre/ebooks/lrf/html/convert_to.py index a86e4e072e..fa4fe5aae6 100644 --- a/src/calibre/ebooks/lrf/html/convert_to.py +++ b/src/calibre/ebooks/lrf/html/convert_to.py @@ -10,13 +10,13 @@ from calibre.ebooks.lrf.lrfparser import LRFDocument from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.lrf.objects import PageAttr, BlockAttr, TextAttr - +from calibre.ebooks.lrf.pylrs.pylrs import TextStyle class BlockStyle(object): - + def __init__(self, ba): self.ba = ba - + def __str__(self): ans = '.'+str(self.ba.id)+' {\n' if hasattr(self.ba, 'sidemargin'): @@ -37,10 +37,10 @@ class BlockStyle(object): ans += '\tbackground-color: %s;\n'%(self.ba.bgcolor.to_html()) #TODO: Fixed size blocks return ans + '}\n' - + class LRFConverter(object): - + def __init__(self, document, opts, logger): self.lrf = document self.opts = opts @@ -48,15 +48,15 @@ class LRFConverter(object): self.logger = logger logger.info('Parsing LRF...') self.lrf.parse() - + self.create_metadata() self.create_styles() - + def create_metadata(self): self.logger.info('Reading metadata...') mi = get_metadata(self.lrf) self.opf = OPFCreator(self.output_dir, mi) - + def create_page_styles(self): self.page_css = '' for obj in self.lrf.objects.values(): @@ -65,21 +65,21 @@ class LRFConverter(object): self.page_css = selector + ' {\n' # TODO: Headers and footers self.page_css += '}\n' - - + + def create_block_styles(self): self.block_css = '' for obj in self.lrf.objects.values(): if isinstance(obj, BlockAttr): self.block_css += str(BlockStyle(obj)) - + def create_text_styles(self): self.text_css = '' for obj in self.lrf.objects.values(): if isinstance(obj, TextAttr): self.text_css += str(TextStyle(obj)) print self.text_css - + def create_styles(self): self.logger.info('Creating CSS stylesheet...') self.create_page_styles() @@ -104,9 +104,9 @@ def process_file(lrfpath, opts, logger=None): raise ConversionError(opts.out + ' is not a directory') if not os.path.exists(opts.out): os.makedirs(opts.out) - + document = LRFDocument(open(lrfpath, 'rb')) - conv = LRFConverter(document, opts, logger) + conv = LRFConverter(document, opts, logger) def main(args=sys.argv): @@ -116,7 +116,7 @@ def main(args=sys.argv): parser.print_help() return 1 process_file(args[1], opts) - + return 0 diff --git a/src/calibre/ebooks/lrf/html/table.py b/src/calibre/ebooks/lrf/html/table.py index a3b3123293..dc246fa693 100644 --- a/src/calibre/ebooks/lrf/html/table.py +++ b/src/calibre/ebooks/lrf/html/table.py @@ -11,23 +11,23 @@ def ceil(num): return int(math.ceil(num)) def print_xml(elem): - from calibre.ebooks.lrf.pylrs.pylrs import ElementWriter + from calibre.ebooks.lrf.pylrs.pylrs import ElementWriter elem = elem.toElement('utf8') ew = ElementWriter(elem, sourceEncoding='utf8') ew.write(sys.stdout) print - + def cattrs(base, extra): new = base.copy() new.update(extra) return new - + def tokens(tb): ''' Return the next token. A token is : - 1. A string + 1. A string a block of text that has the same style - ''' + ''' def process_element(x, attrs): if isinstance(x, CR): yield 2, None @@ -49,22 +49,22 @@ def tokens(tb): for y in x.contents: for z in process_element(y, attrs): yield z - - + + for i in tb.contents: if isinstance(i, CR): yield 1, None elif isinstance(i, Paragraph): - for j in i.contents: + for j in i.contents: attrs = {} if hasattr(j, 'attrs'): attrs = j.attrs - for k in process_element(j, attrs): + for k in process_element(j, attrs): yield k - + class Cell(object): - + def __init__(self, conv, tag, css): self.conv = conv self.tag = tag @@ -89,7 +89,7 @@ class Cell(object): self.rowspan = int(tag['rowspan']) if tag.has_key('rowspan') else 1 except: pass - + pp = conv.current_page conv.book.allow_new_page = False conv.current_page = conv.book.create_page() @@ -99,7 +99,7 @@ class Cell(object): if isinstance(item, TextBlock): self.text_blocks.append(item) conv.current_page = pp - conv.book.allow_new_page = True + conv.book.allow_new_page = True if not self.text_blocks: tb = conv.book.create_text_block() tb.Paragraph(' ') @@ -107,7 +107,7 @@ class Cell(object): for tb in self.text_blocks: tb.parent = None tb.objId = 0 - # Needed as we have to eventually change this BlockStyle's width and + # Needed as we have to eventually change this BlockStyle's width and # height attributes. This blockstyle may be shared with other # elements, so doing that causes havoc. tb.blockStyle = conv.book.create_block_style() @@ -117,17 +117,17 @@ class Cell(object): if ts.attrs['align'] == 'foot': if isinstance(tb.contents[-1], Paragraph): tb.contents[-1].append(' ') - - - - + + + + def pts_to_pixels(self, pts): pts = int(pts) return ceil((float(self.conv.profile.dpi)/72.)*(pts/10.)) - + def minimum_width(self): return max([self.minimum_tb_width(tb) for tb in self.text_blocks]) - + def minimum_tb_width(self, tb): ts = tb.textStyle.attrs default_font = get_font(ts['fontfacename'], self.pts_to_pixels(ts['fontsize'])) @@ -135,7 +135,7 @@ class Cell(object): mwidth = 0 for token, attrs in tokens(tb): font = default_font - if isinstance(token, int): # Handle para and line breaks + if isinstance(token, int): # Handle para and line breaks continue if isinstance(token, Plot): return self.pts_to_pixels(token.xsize) @@ -151,24 +151,24 @@ class Cell(object): if width > mwidth: mwidth = width return parindent + mwidth + 2 - + def text_block_size(self, tb, maxwidth=sys.maxint, debug=False): ts = tb.textStyle.attrs default_font = get_font(ts['fontfacename'], self.pts_to_pixels(ts['fontsize'])) parindent = self.pts_to_pixels(ts['parindent']) top, bottom, left, right = 0, 0, parindent, parindent - - def add_word(width, height, left, right, top, bottom, ls, ws): + + def add_word(width, height, left, right, top, bottom, ls, ws): if left + width > maxwidth: left = width + ws top += ls bottom = top+ls if top+ls > bottom else bottom else: left += (width + ws) - right = left if left > right else right + right = left if left > right else right bottom = top+ls if top+ls > bottom else bottom return left, right, top, bottom - + for token, attrs in tokens(tb): if attrs == None: attrs = {} @@ -196,17 +196,17 @@ class Cell(object): width, height = font.getsize(word) left, right, top, bottom = add_word(width, height, left, right, top, bottom, ls, ws) return right+3+max(parindent, 10), bottom - + def text_block_preferred_width(self, tb, debug=False): return self.text_block_size(tb, sys.maxint, debug=debug)[0] - + def preferred_width(self, debug=False): return ceil(max([self.text_block_preferred_width(i, debug=debug) for i in self.text_blocks])) - + def height(self, width): return sum([self.text_block_size(i, width)[1] for i in self.text_blocks]) - - + + class Row(object): def __init__(self, conv, row, css, colpad): @@ -221,15 +221,15 @@ class Row(object): name = a['name'] if a.has_key('name') else a['id'] if a.has_key('id') else None if name is not None: self.targets.append(name.replace('#', '')) - - + + def number_of_cells(self): '''Number of cells in this row. Respects colspan''' ans = 0 for cell in self.cells: ans += cell.colspan return ans - + def height(self, widths): i, heights = 0, [] for cell in self.cells: @@ -239,11 +239,11 @@ class Row(object): if not heights: return 0 return max(heights) - + def cell_from_index(self, col): i = -1 - cell = None - for cell in self.cells: + cell = None + for cell in self.cells: for k in range(0, cell.colspan): if i == col: break @@ -251,30 +251,30 @@ class Row(object): if i == col: break return cell - + def minimum_width(self, col): cell = self.cell_from_index(col) if not cell: return 0 return cell.minimum_width() - + def preferred_width(self, col): cell = self.cell_from_index(col) if not cell: return 0 return 0 if cell.colspan > 1 else cell.preferred_width() - + def width_percent(self, col): cell = self.cell_from_index(col) if not cell: return -1 return -1 if cell.colspan > 1 else cell.pwidth - + def cell_iterator(self): for c in self.cells: yield c - - + + class Table(object): def __init__(self, conv, table, css, rowpad=10, colpad=10): self.rows = [] @@ -283,31 +283,31 @@ class Table(object): self.colpad = colpad rows = table.findAll('tr') conv.in_table = True - for row in rows: + for row in rows: rcss = conv.tag_css(row, css)[0] self.rows.append(Row(conv, row, rcss, colpad)) conv.in_table = False - + def number_of_columns(self): max = 0 for row in self.rows: max = row.number_of_cells() if row.number_of_cells() > max else max return max - + def number_or_rows(self): return len(self.rows) - + def height(self, maxwidth): ''' Return row heights + self.rowpad''' widths = self.get_widths(maxwidth) return sum([row.height(widths) + self.rowpad for row in self.rows]) - self.rowpad - + def minimum_width(self, col): return max([row.minimum_width(col) for row in self.rows]) - + def width_percent(self, col): return max([row.width_percent(col) for row in self.rows]) - + def get_widths(self, maxwidth): ''' Return widths of columns + self.colpad @@ -320,29 +320,29 @@ class Table(object): try: cellwidths[r] = self.rows[r].preferred_width(c) except IndexError: - continue + continue widths[c] = max(cellwidths) - + min_widths = [self.minimum_width(i)+10 for i in xrange(cols)] for i in xrange(len(widths)): wp = self.width_percent(i) if wp >= 0.: widths[i] = max(min_widths[i], ceil((wp/100.) * (maxwidth - (cols-1)*self.colpad))) - - + + itercount = 0 - + while sum(widths) > maxwidth-((len(widths)-1)*self.colpad) and itercount < 100: for i in range(cols): widths[i] = ceil((95./100.)*widths[i]) if \ ceil((95./100.)*widths[i]) >= min_widths[i] else widths[i] itercount += 1 - + return [i+self.colpad for i in widths] - - def blocks(self, maxwidth, maxheight): + + def blocks(self, maxwidth, maxheight): rows, cols = self.number_or_rows(), self.number_of_columns() - cellmatrix = [[None for c in range(cols)] for r in range(rows)] + cellmatrix = [[None for c in range(cols)] for r in range(rows)] rowpos = [0 for i in range(rows)] for r in range(rows): nc = self.rows[r].cell_iterator() @@ -358,14 +358,14 @@ class Table(object): break except StopIteration: # No more cells in this row continue - - + + widths = self.get_widths(maxwidth) heights = [row.height(widths) for row in self.rows] - + xpos = [sum(widths[:i]) for i in range(cols)] delta = maxwidth - sum(widths) - if delta < 0: + if delta < 0: delta = 0 for r in range(len(cellmatrix)): yield None, 0, heights[r], 0, self.rows[r].targets @@ -377,13 +377,13 @@ class Table(object): sypos = 0 for tb in cell.text_blocks: tb.blockStyle = self.conv.book.create_block_style( - blockwidth=width, + blockwidth=width, blockheight=cell.text_block_size(tb, width)[1], blockrule='horz-fixed') - + yield tb, xpos[c], sypos, delta, None sypos += tb.blockStyle.attrs['blockheight'] - - - - \ No newline at end of file + + + + diff --git a/src/calibre/ebooks/lrf/pylrs/elements.py b/src/calibre/ebooks/lrf/pylrs/elements.py index 0cb02dd21b..0e9ec4d7d0 100644 --- a/src/calibre/ebooks/lrf/pylrs/elements.py +++ b/src/calibre/ebooks/lrf/pylrs/elements.py @@ -1,81 +1,81 @@ -""" elements.py -- replacements and helpers for ElementTree """ - -class ElementWriter(object): - def __init__(self, e, header=False, sourceEncoding="ascii", - spaceBeforeClose=True, outputEncodingName="UTF-16"): - self.header = header - self.e = e - self.sourceEncoding=sourceEncoding - self.spaceBeforeClose = spaceBeforeClose - self.outputEncodingName = outputEncodingName - - - def _encodeCdata(self, rawText): - if type(rawText) is str: - rawText = rawText.decode(self.sourceEncoding) - - text = rawText.replace("&", "&") - text = text.replace("<", "<") - text = text.replace(">", ">") - return text - - - def _writeAttribute(self, f, name, value): - f.write(u' %s="' % unicode(name)) - if not isinstance(value, basestring): - value = unicode(value) - value = self._encodeCdata(value) - value = value.replace('"', '"') - f.write(value) - f.write(u'"') - - - def _writeText(self, f, rawText): - text = self._encodeCdata(rawText) - f.write(text) - - - def _write(self, f, e): - f.write(u'<' + unicode(e.tag)) - - attributes = e.items() - attributes.sort() - for name, value in attributes: - self._writeAttribute(f, name, value) - - if e.text is not None or len(e) > 0: - f.write(u'>') - - if e.text: - self._writeText(f, e.text) - - for e2 in e: - self._write(f, e2) - - f.write(u'' % e.tag) - else: - if self.spaceBeforeClose: - f.write(' ') - f.write(u'/>') - - if e.tail is not None: - self._writeText(f, e.tail) - - - def toString(self): - class x: - pass - buffer = [] - x.write = buffer.append - self.write(x) - return u''.join(buffer) - - - def write(self, f): - if self.header: - f.write(u'\n' % self.outputEncodingName) - - self._write(f, self.e) - - - +""" elements.py -- replacements and helpers for ElementTree """ + +class ElementWriter(object): + def __init__(self, e, header=False, sourceEncoding="ascii", + spaceBeforeClose=True, outputEncodingName="UTF-16"): + self.header = header + self.e = e + self.sourceEncoding=sourceEncoding + self.spaceBeforeClose = spaceBeforeClose + self.outputEncodingName = outputEncodingName + + + def _encodeCdata(self, rawText): + if type(rawText) is str: + rawText = rawText.decode(self.sourceEncoding) + + text = rawText.replace("&", "&") + text = text.replace("<", "<") + text = text.replace(">", ">") + return text + + + def _writeAttribute(self, f, name, value): + f.write(u' %s="' % unicode(name)) + if not isinstance(value, basestring): + value = unicode(value) + value = self._encodeCdata(value) + value = value.replace('"', '"') + f.write(value) + f.write(u'"') + + + def _writeText(self, f, rawText): + text = self._encodeCdata(rawText) + f.write(text) + + + def _write(self, f, e): + f.write(u'<' + unicode(e.tag)) + + attributes = e.items() + attributes.sort() + for name, value in attributes: + self._writeAttribute(f, name, value) + + if e.text is not None or len(e) > 0: + f.write(u'>') + + if e.text: + self._writeText(f, e.text) + + for e2 in e: + self._write(f, e2) + + f.write(u'' % e.tag) + else: + if self.spaceBeforeClose: + f.write(' ') + f.write(u'/>') + + if e.tail is not None: + self._writeText(f, e.tail) + + + def toString(self): + class x: + pass + buffer = [] + x.write = buffer.append + self.write(x) + return u''.join(buffer) + + + def write(self, f): + if self.header: + f.write(u'\n' % self.outputEncodingName) + + self._write(f, self.e) + + + diff --git a/src/calibre/ebooks/lrf/pylrs/pylrf.py b/src/calibre/ebooks/lrf/pylrs/pylrf.py index 02c575d0b0..f3db518010 100644 --- a/src/calibre/ebooks/lrf/pylrs/pylrf.py +++ b/src/calibre/ebooks/lrf/pylrs/pylrf.py @@ -1,784 +1,784 @@ -""" - pylrf.py -- very low level interface to create lrf files. See pylrs for - higher level interface that can use this module to render books to lrf. -""" - -import struct -import zlib -import StringIO -import codecs -import os - -from pylrfopt import tagListOptimizer - -PYLRF_VERSION = "1.0" - -# -# Acknowledgement: -# This software would not have been possible without the pioneering -# efforts of the author of lrf2lrs.py, Igor Skochinsky. -# -# Copyright (c) 2007 Mike Higgins (Falstaff) -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -# -# Change History: -# -# V1.0 06 Feb 2007 -# Initial Release. - -# -# Current limitations and bugs: -# Never "scrambles" any streams (even if asked to). This does not seem -# to hurt anything. -# -# Not based on any official documentation, so many assumptions had to be made. -# -# Can be used to create lrf files that can lock up an eBook reader. -# This is your only warning. -# -# Unsupported objects: Canvas, Window, PopUpWindow, Sound, Import, -# SoundStream, ObjectInfo -# -# The only button type supported is JumpButton. -# -# Unsupported tags: SoundStop, Wait, pos on BlockSpace (and those used by -# unsupported objects). -# -# Tags supporting Japanese text and Asian layout have not been tested. -# -# Tested on Python 2.4 and 2.5, Windows XP and Sony PRS-500. -# -# Commented even less than pylrs, but not very useful when called directly, -# anyway. -# - -class LrfError(Exception): - pass - -def writeByte(f, byte): - f.write(struct.pack(" 65535: - raise LrfError('Cannot encode a number greater than 65535 in a word.') - if int(word) < 0: - raise LrfError('Cannot encode a number < 0 in a word: '+str(word)) - f.write(struct.pack("I", int(color, 0))) - -def writeLineWidth(f, width): - writeWord(f, int(width)) - -def writeUnicode(f, string, encoding): - if isinstance(string, str): - string = string.decode(encoding) - string = string.encode("utf-16-le") - length = len(string) - if length > 65535: - raise LrfError('Cannot write strings longer than 65535 characters.') - writeWord(f, length) - writeString(f, string) - -def writeRaw(f, string, encoding): - if isinstance(string, str): - string = string.decode(encoding) - - string = string.encode("utf-16-le") - writeString(f, string) - -def writeRubyAA(f, rubyAA): - ralign, radjust = rubyAA - radjust = {"line-edge":0x10, "none":0}[radjust] - ralign = {"start":1, "center":2}[ralign] - writeWord(f, ralign | radjust) - -def writeBgImage(f, bgInfo): - imode, iid = bgInfo - imode = {"pfix": 0, "fix":1, "tile":2, "centering":3}[imode] - writeWord(f, imode) - writeDWord(f, iid) - -def writeEmpDots(f, dotsInfo, encoding): - refDotsFont, dotsFontName, dotsCode = dotsInfo - writeDWord(f, refDotsFont) - LrfTag("fontfacename", dotsFontName).write(f, encoding) - writeWord(f, int(dotsCode, 0)) - -def writeRuledLine(f, lineInfo): - lineLength, lineType, lineWidth, lineColor = lineInfo - writeWord(f, lineLength) - writeWord(f, LINE_TYPE_ENCODING[lineType]) - writeWord(f, lineWidth) - writeColor(f, lineColor) - - -LRF_SIGNATURE = "L\x00R\x00F\x00\x00\x00" - -#XOR_KEY = 48 -XOR_KEY = 65024 # that's what lrf2lrs says -- not used, anyway... - -LRF_VERSION = 1000 # is 999 for librie? lrf2lrs uses 1000 - -IMAGE_TYPE_ENCODING = dict(GIF=0x14, PNG=0x12, BMP=0x13, JPEG=0x11, JPG=0x11) - -OBJECT_TYPE_ENCODING = dict( - PageTree = 0x01, - Page = 0x02, - Header = 0x03, - Footer = 0x04, - PageAtr = 0x05, PageStyle=0x05, - Block = 0x06, - BlockAtr = 0x07, BlockStyle=0x07, - MiniPage = 0x08, - TextBlock = 0x0A, Text=0x0A, - TextAtr = 0x0B, TextStyle=0x0B, - ImageBlock = 0x0C, Image=0x0C, - Canvas = 0x0D, - ESound = 0x0E, - ImageStream = 0x11, - Import = 0x12, - Button = 0x13, - Window = 0x14, - PopUpWindow = 0x15, - Sound = 0x16, - SoundStream = 0x17, - Font = 0x19, - ObjectInfo = 0x1A, - BookAtr = 0x1C, BookStyle=0x1C, - SimpleTextBlock = 0x1D, - TOC=0x1E -) - -LINE_TYPE_ENCODING = { - 'none':0, 'solid':0x10, 'dashed':0x20, 'double':0x30, 'dotted':0x40 -} - -BINDING_DIRECTION_ENCODING = dict(Lr=1, Rl=16) - - -TAG_INFO = dict( - rawtext = (0, writeRaw), - ObjectStart = (0xF500, " 1: - raise LrfError("only one parameter allowed on tag %s" % name) - - if len(parameters) == 0: - self.parameter = None - else: - self.parameter = parameters[0] - - - def write(self, lrf, encoding=None): - if self.type != 0: - writeWord(lrf, self.type) - - p = self.parameter - if p is None: - return - - #print " Writing tag", self.name - for f in self.format: - if isinstance(f, dict): - p = f[p] - elif isinstance(f, str): - if isinstance(p, tuple): - writeString(lrf, struct.pack(f, *p)) - else: - writeString(lrf, struct.pack(f, p)) - else: - if f in [writeUnicode, writeRaw, writeEmpDots]: - if encoding is None: - raise LrfError, "Tag requires encoding" - f(lrf, p, encoding) - else: - f(lrf, p) - - -STREAM_SCRAMBLED = 0x200 -STREAM_COMPRESSED = 0x100 -STREAM_FORCE_COMPRESSED = 0x8100 -STREAM_TOC = 0x0051 - -class LrfStreamBase(object): - def __init__(self, streamFlags, streamData=None): - self.streamFlags = streamFlags - self.streamData = streamData - - - def setStreamData(self, streamData): - self.streamData = streamData - - - def getStreamTags(self, optimize=False): - # tags: - # StreamFlags - # StreamSize - # StreamStart - # (data) - # StreamEnd - # - # if flags & 0x200, stream is scrambled - # if flags & 0x100, stream is compressed - - - flags = self.streamFlags - streamBuffer = self.streamData - - # implement scramble? I never scramble anything... - - if flags & STREAM_FORCE_COMPRESSED == STREAM_FORCE_COMPRESSED: - optimize = False - - if flags & STREAM_COMPRESSED == STREAM_COMPRESSED: - uncompLen = len(streamBuffer) - compStreamBuffer = zlib.compress(streamBuffer) - if optimize and uncompLen <= len(compStreamBuffer) + 4: - flags &= ~STREAM_COMPRESSED - else: - streamBuffer = struct.pack(" 65535: + raise LrfError('Cannot encode a number greater than 65535 in a word.') + if int(word) < 0: + raise LrfError('Cannot encode a number < 0 in a word: '+str(word)) + f.write(struct.pack("I", int(color, 0))) + +def writeLineWidth(f, width): + writeWord(f, int(width)) + +def writeUnicode(f, string, encoding): + if isinstance(string, str): + string = string.decode(encoding) + string = string.encode("utf-16-le") + length = len(string) + if length > 65535: + raise LrfError('Cannot write strings longer than 65535 characters.') + writeWord(f, length) + writeString(f, string) + +def writeRaw(f, string, encoding): + if isinstance(string, str): + string = string.decode(encoding) + + string = string.encode("utf-16-le") + writeString(f, string) + +def writeRubyAA(f, rubyAA): + ralign, radjust = rubyAA + radjust = {"line-edge":0x10, "none":0}[radjust] + ralign = {"start":1, "center":2}[ralign] + writeWord(f, ralign | radjust) + +def writeBgImage(f, bgInfo): + imode, iid = bgInfo + imode = {"pfix": 0, "fix":1, "tile":2, "centering":3}[imode] + writeWord(f, imode) + writeDWord(f, iid) + +def writeEmpDots(f, dotsInfo, encoding): + refDotsFont, dotsFontName, dotsCode = dotsInfo + writeDWord(f, refDotsFont) + LrfTag("fontfacename", dotsFontName).write(f, encoding) + writeWord(f, int(dotsCode, 0)) + +def writeRuledLine(f, lineInfo): + lineLength, lineType, lineWidth, lineColor = lineInfo + writeWord(f, lineLength) + writeWord(f, LINE_TYPE_ENCODING[lineType]) + writeWord(f, lineWidth) + writeColor(f, lineColor) + + +LRF_SIGNATURE = "L\x00R\x00F\x00\x00\x00" + +#XOR_KEY = 48 +XOR_KEY = 65024 # that's what lrf2lrs says -- not used, anyway... + +LRF_VERSION = 1000 # is 999 for librie? lrf2lrs uses 1000 + +IMAGE_TYPE_ENCODING = dict(GIF=0x14, PNG=0x12, BMP=0x13, JPEG=0x11, JPG=0x11) + +OBJECT_TYPE_ENCODING = dict( + PageTree = 0x01, + Page = 0x02, + Header = 0x03, + Footer = 0x04, + PageAtr = 0x05, PageStyle=0x05, + Block = 0x06, + BlockAtr = 0x07, BlockStyle=0x07, + MiniPage = 0x08, + TextBlock = 0x0A, Text=0x0A, + TextAtr = 0x0B, TextStyle=0x0B, + ImageBlock = 0x0C, Image=0x0C, + Canvas = 0x0D, + ESound = 0x0E, + ImageStream = 0x11, + Import = 0x12, + Button = 0x13, + Window = 0x14, + PopUpWindow = 0x15, + Sound = 0x16, + SoundStream = 0x17, + Font = 0x19, + ObjectInfo = 0x1A, + BookAtr = 0x1C, BookStyle=0x1C, + SimpleTextBlock = 0x1D, + TOC=0x1E +) + +LINE_TYPE_ENCODING = { + 'none':0, 'solid':0x10, 'dashed':0x20, 'double':0x30, 'dotted':0x40 +} + +BINDING_DIRECTION_ENCODING = dict(Lr=1, Rl=16) + + +TAG_INFO = dict( + rawtext = (0, writeRaw), + ObjectStart = (0xF500, " 1: + raise LrfError("only one parameter allowed on tag %s" % name) + + if len(parameters) == 0: + self.parameter = None + else: + self.parameter = parameters[0] + + + def write(self, lrf, encoding=None): + if self.type != 0: + writeWord(lrf, self.type) + + p = self.parameter + if p is None: + return + + #print " Writing tag", self.name + for f in self.format: + if isinstance(f, dict): + p = f[p] + elif isinstance(f, str): + if isinstance(p, tuple): + writeString(lrf, struct.pack(f, *p)) + else: + writeString(lrf, struct.pack(f, p)) + else: + if f in [writeUnicode, writeRaw, writeEmpDots]: + if encoding is None: + raise LrfError, "Tag requires encoding" + f(lrf, p, encoding) + else: + f(lrf, p) + + +STREAM_SCRAMBLED = 0x200 +STREAM_COMPRESSED = 0x100 +STREAM_FORCE_COMPRESSED = 0x8100 +STREAM_TOC = 0x0051 + +class LrfStreamBase(object): + def __init__(self, streamFlags, streamData=None): + self.streamFlags = streamFlags + self.streamData = streamData + + + def setStreamData(self, streamData): + self.streamData = streamData + + + def getStreamTags(self, optimize=False): + # tags: + # StreamFlags + # StreamSize + # StreamStart + # (data) + # StreamEnd + # + # if flags & 0x200, stream is scrambled + # if flags & 0x100, stream is compressed + + + flags = self.streamFlags + streamBuffer = self.streamData + + # implement scramble? I never scramble anything... + + if flags & STREAM_FORCE_COMPRESSED == STREAM_FORCE_COMPRESSED: + optimize = False + + if flags & STREAM_COMPRESSED == STREAM_COMPRESSED: + uncompLen = len(streamBuffer) + compStreamBuffer = zlib.compress(streamBuffer) + if optimize and uncompLen <= len(compStreamBuffer) + 4: + flags &= ~STREAM_COMPRESSED + else: + streamBuffer = struct.pack(" 0 and tagList[-1].name == tagName: - del tagList[-1] - - -def tagListOptimizer(tagList): - # this function eliminates redundant or unnecessary tags - # it scans a list of tags, looking for text settings that are - # changed before any text is output - # for example, - # fontsize=100, fontsize=200, text, fontsize=100, fontsize=200 - # should be: - # fontsize=200 text - oldSize = len(tagList) - _optimize(tagList, "fontsize", int) - _optimize(tagList, "fontweight", int) - return oldSize - len(tagList) - - +def _optimize(tagList, tagName, conversion): + # copy the tag of interest plus any text + newTagList = [] + for tag in tagList: + if tag.name == tagName or tag.name == "rawtext": + newTagList.append(tag) + + # now, eliminate any duplicates (leaving the last one) + for i, newTag in enumerate(newTagList[:-1]): + if newTag.name == tagName and newTagList[i+1].name == tagName: + tagList.remove(newTag) + + # eliminate redundant settings to same value across text strings + newTagList = [] + for tag in tagList: + if tag.name == tagName: + newTagList.append(tag) + + for i, newTag in enumerate(newTagList[:-1]): + value = conversion(newTag.parameter) + nextValue = conversion(newTagList[i+1].parameter) + if value == nextValue: + tagList.remove(newTagList[i+1]) + + # eliminate any setting that don't have text after them + while len(tagList) > 0 and tagList[-1].name == tagName: + del tagList[-1] + + +def tagListOptimizer(tagList): + # this function eliminates redundant or unnecessary tags + # it scans a list of tags, looking for text settings that are + # changed before any text is output + # for example, + # fontsize=100, fontsize=200, text, fontsize=100, fontsize=200 + # should be: + # fontsize=200 text + oldSize = len(tagList) + _optimize(tagList, "fontsize", int) + _optimize(tagList, "fontweight", int) + return oldSize - len(tagList) + + diff --git a/src/calibre/ebooks/lrf/pylrs/pylrs.py b/src/calibre/ebooks/lrf/pylrs/pylrs.py index e2bfc2e2a9..0847d4ba73 100644 --- a/src/calibre/ebooks/lrf/pylrs/pylrs.py +++ b/src/calibre/ebooks/lrf/pylrs/pylrs.py @@ -1,2593 +1,2594 @@ -# Copyright (c) 2007 Mike Higgins (Falstaff) -# Modifications from the original: -# Copyright (C) 2007 Kovid Goyal -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -# -# Current limitations and bugs: -# Bug: Does not check if most setting values are valid unless lrf is created. -# -# Unsupported objects: MiniPage, SimpleTextBlock, Canvas, Window, -# PopUpWindow, Sound, Import, SoundStream, -# ObjectInfo -# -# Does not support background images for blocks or pages. -# -# The only button type supported are JumpButtons. -# -# None of the Japanese language tags are supported. -# -# Other unsupported tags: PageDiv, SoundStop, Wait, pos, -# Plot, Image (outside of ImageBlock), -# EmpLine, EmpDots - -import os, re, codecs, operator -from xml.sax.saxutils import escape -from datetime import date -try: - from elementtree.ElementTree import (Element, SubElement) -except ImportError: - from xml.etree.ElementTree import (Element, SubElement) - -from elements import ElementWriter -from pylrf import (LrfWriter, LrfObject, LrfTag, LrfToc, - STREAM_COMPRESSED, LrfTagStream, LrfStreamBase, IMAGE_TYPE_ENCODING, - BINDING_DIRECTION_ENCODING, LINE_TYPE_ENCODING, LrfFileStream, - STREAM_FORCE_COMPRESSED) - -DEFAULT_SOURCE_ENCODING = "cp1252" # defualt is us-windows character set -DEFAULT_GENREADING = "fs" # default is yes to both lrf and lrs - -from calibre import __appname__, __version__ -from calibre import entity_to_unicode - -class LrsError(Exception): - pass - -class ContentError(Exception): - pass - -def _checkExists(filename): - if not os.path.exists(filename): - raise LrsError, "file '%s' not found" % filename - - -def _formatXml(root): - """ A helper to make the LRS output look nicer. """ - for elem in root.getiterator(): - if len(elem) > 0 and (not elem.text or not elem.text.strip()): - elem.text = "\n" - if not elem.tail or not elem.tail.strip(): - elem.tail = "\n" - - - -def ElementWithText(tag, text, **extra): - """ A shorthand function to create Elements with text. """ - e = Element(tag, **extra) - e.text = text - return e - - - -def ElementWithReading(tag, text, reading=False): - """ A helper function that creates reading attributes. """ - - # note: old lrs2lrf parser only allows reading = "" - - if text is None: - readingText = "" - elif isinstance(text, basestring): - readingText = text - else: - # assumed to be a sequence of (name, sortas) - readingText = text[1] - text = text[0] - - - if not reading: - readingText = "" - return ElementWithText(tag, text, reading=readingText) - - - -def appendTextElements(e, contentsList, se): - """ A helper function to convert text streams into the proper elements. """ - - def uconcat(text, newText, se): - if type(newText) != type(text): - if type(text) is str: - text = text.decode(se) - else: - newText = newText.decode(se) - - return text + newText - - - e.text = "" - lastElement = None - - for content in contentsList: - if not isinstance(content, Text): - newElement = content.toElement(se) - if newElement is None: - continue - lastElement = newElement - lastElement.tail = "" - e.append(lastElement) - else: - if lastElement is None: - e.text = uconcat(e.text, content.text, se) - else: - lastElement.tail = uconcat(lastElement.tail, content.text, se) - - - -class Delegator(object): - """ A mixin class to create delegated methods that create elements. """ - def __init__(self, delegates): - self.delegates = delegates - self.delegatedMethods = [] - #self.delegatedSettingsDict = {} - #self.delegatedSettings = [] - for d in delegates: - d.parent = self - methods = d.getMethods() - self.delegatedMethods += methods - for m in methods: - setattr(self, m, getattr(d, m)) - - """ - for setting in d.getSettings(): - if isinstance(setting, basestring): - setting = (d, setting) - delegates = \ - self.delegatedSettingsDict.setdefault(setting[1], []) - delegates.append(setting[0]) - self.delegatedSettings.append(setting) - """ - - - def applySetting(self, name, value, testValid=False): - applied = False - if name in self.getSettings(): - setattr(self, name, value) - applied = True - - for d in self.delegates: - if hasattr(d, "applySetting"): - applied = applied or d.applySetting(name, value) - else: - if name in d.getSettings(): - setattr(d, name, value) - applied = True - - if testValid and not applied: - raise LrsError, "setting %s not valid" % name - - return applied - - - def applySettings(self, settings, testValid=False): - for (setting, value) in settings.items(): - self.applySetting(setting, value, testValid) - """ - if setting not in self.delegatedSettingsDict: - raise LrsError, "setting %s not valid" % setting - delegates = self.delegatedSettingsDict[setting] - for d in delegates: - setattr(d, setting, value) - """ - - - def appendDelegates(self, element, sourceEncoding): - for d in self.delegates: - e = d.toElement(sourceEncoding) - if e is not None: - if isinstance(e, list): - for e1 in e: element.append(e1) - else: - element.append(e) - - - def appendReferencedObjects(self, parent): - for d in self.delegates: - d.appendReferencedObjects(parent) - - - def getMethods(self): - return self.delegatedMethods - - - def getSettings(self): - return [] - - - def toLrfDelegates(self, lrfWriter): - for d in self.delegates: - d.toLrf(lrfWriter) - - - def toLrf(self, lrfWriter): - self.toLrfDelegates(lrfWriter) - - - -class LrsAttributes(object): - """ A mixin class to handle default and user supplied attributes. """ - def __init__(self, defaults, alsoAllow=None, **settings): - if alsoAllow is None: - alsoAllow = [] - self.attrs = defaults.copy() - for (name, value) in settings.items(): - if name not in self.attrs and name not in alsoAllow: - raise LrsError, "%s does not support setting %s" % \ - (self.__class__.__name__, name) - if type(value) is int: - value = str(value) - self.attrs[name] = value - - - -class LrsContainer(object): - """ This class is a mixin class for elements that are contained in or - contain an unknown number of other elements. - """ - def __init__(self, validChildren): - self.parent = None - self.contents = [] - self.validChildren = validChildren - self.must_append = False #: If True even an empty container is appended by append_to - - def has_text(self): - ''' Return True iff this container has non whitespace text ''' - if hasattr(self, 'text'): - if self.text.strip(): - return True - if hasattr(self, 'contents'): - for child in self.contents: - if child.has_text(): - return True - for item in self.contents: - if isinstance(item, (Plot, ImageBlock, Canvas, CR)): - return True - return False - - def append_to(self, parent): - ''' - Append self to C{parent} iff self has non whitespace textual content - @type parent: LrsContainer - ''' - if self.contents or self.must_append: - parent.append(self) - - - def appendReferencedObjects(self, parent): - for c in self.contents: - c.appendReferencedObjects(parent) - - - def setParent(self, parent): - if self.parent is not None: - raise LrsError, "object already has parent" - - self.parent = parent - - - def append(self, content, convertText=True): - """ - Appends valid objects to container. Can auto-covert text strings - to Text objects. - """ - for validChild in self.validChildren: - if isinstance(content, validChild): - break - else: - raise LrsError, "can't append %s to %s" % \ - (content.__class__.__name__, - self.__class__.__name__) - - if convertText and isinstance(content, basestring): - content = Text(content) - - content.setParent(self) - - if isinstance(content, LrsObject): - content.assignId() - - self.contents.append(content) - return self - - def get_all(self, predicate=lambda x: x): - for child in self.contents: - if predicate(child): - yield child - if hasattr(child, 'get_all'): - for grandchild in child.get_all(predicate): - yield grandchild - - - -class LrsObject(object): - """ A mixin class for elements that need an object id. """ - nextObjId = 0 - - @classmethod - def getNextObjId(selfClass): - selfClass.nextObjId += 1 - return selfClass.nextObjId - - def __init__(self, assignId=False): - if assignId: - self.objId = LrsObject.getNextObjId() - else: - self.objId = 0 - - - def assignId(self): - if self.objId != 0: - raise LrsError, "id already assigned to " + self.__class__.__name__ - - self.objId = LrsObject.getNextObjId() - - - def lrsObjectElement(self, name, objlabel="objlabel", labelName=None, - labelDecorate=True, **settings): - element = Element(name) - element.attrib["objid"] = str(self.objId) - if labelName is None: - labelName = name - if labelDecorate: - label = "%s.%d" % (labelName, self.objId) - else: - label = str(self.objId) - element.attrib[objlabel] = label - element.attrib.update(settings) - return element - - - -class Book(Delegator): - """ - Main class for any lrs or lrf. All objects must be appended to - the Book class in some way or another in order to be rendered as - an LRS or LRF file. - - The following settings are available on the contructor of Book: - - author="book author" or author=("book author", "sort as") - Author of the book. - - title="book title" or title=("book title", "sort as") - Title of the book. - - sourceencoding="codec" - Gives the assumed encoding for all non-unicode strings. - - - thumbnail="thumbnail file name" - A small (80x80?) graphics file with a thumbnail of the book's cover. - - bookid="book id" - A unique id for the book. - - textstyledefault= - Sets the default values for all TextStyles. - - pagetstyledefault= - Sets the default values for all PageStyles. - - blockstyledefault= - Sets the default values for all BlockStyles. - - booksetting=BookSetting() - Override the default BookSetting. - - setdefault=StyleDefault() - Override the default SetDefault. - - There are several other settings -- see the BookInfo class for more. - """ - - def __init__(self, textstyledefault=None, blockstyledefault=None, - pagestyledefault=None, - optimizeTags=False, - optimizeCompression=False, - **settings): - - self.parent = None # we are the top of the parent chain - - if "thumbnail" in settings: - _checkExists(settings["thumbnail"]) - - # highly experimental -- use with caution - self.optimizeTags = optimizeTags - self.optimizeCompression = optimizeCompression - - pageStyle = PageStyle(**PageStyle.baseDefaults.copy()) - blockStyle = BlockStyle(**BlockStyle.baseDefaults.copy()) - textStyle = TextStyle(**TextStyle.baseDefaults.copy()) - - if textstyledefault is not None: - textStyle.update(textstyledefault) - - if blockstyledefault is not None: - blockStyle.update(blockstyledefault) - - if pagestyledefault is not None: - pageStyle.update(pagestyledefault) - - self.defaultPageStyle = pageStyle - self.defaultTextStyle = textStyle - self.defaultBlockStyle = blockStyle - LrsObject.nextObjId += 1 - - styledefault = StyleDefault() - if settings.has_key('setdefault'): - styledefault = settings.pop('setdefault') - Delegator.__init__(self, [BookInformation(), Main(), - Template(), Style(styledefault), Solos(), Objects()]) - - self.sourceencoding = None - - # apply default settings - self.applySetting("genreading", DEFAULT_GENREADING) - self.applySetting("sourceencoding", DEFAULT_SOURCE_ENCODING) - - self.applySettings(settings, testValid=True) - - self.allow_new_page = True #: If False L{create_page} raises an exception - self.gc_count = 0 - - - def set_title(self, title): - ot = self.delegates[0].delegates[0].delegates[0].title - self.delegates[0].delegates[0].delegates[0].title = (title, ot[1]) - - def set_author(self, author): - ot = self.delegates[0].delegates[0].delegates[0].author - self.delegates[0].delegates[0].delegates[0].author = (author, ot[1]) - - def create_text_style(self, **settings): - ans = TextStyle(**self.defaultTextStyle.attrs.copy()) - ans.update(settings) - return ans - - def create_block_style(self, **settings): - ans = BlockStyle(**self.defaultBlockStyle.attrs.copy()) - ans.update(settings) - return ans - - def create_page_style(self, **settings): - if not self.allow_new_page: - raise ContentError - ans = PageStyle(**self.defaultPageStyle.attrs.copy()) - ans.update(settings) - return ans - - def create_page(self, pageStyle=None, **settings): - ''' - Return a new L{Page}. The page has not been appended to this book. - @param pageStyle: If None the default pagestyle is used. - @type pageStyle: L{PageStyle} - ''' - if not pageStyle: - pageStyle = self.defaultPageStyle - return Page(pageStyle=pageStyle, **settings) - - def create_text_block(self, textStyle=None, blockStyle=None, **settings): - ''' - Return a new L{TextBlock}. The block has not been appended to this - book. - @param textStyle: If None the default text style is used - @type textStyle: L{TextStyle} - @param blockStyle: If None the default block style is used. - @type blockStyle: L{BlockStyle} - ''' - if not textStyle: - textStyle = self.defaultTextStyle - if not blockStyle: - blockStyle = self.defaultBlockStyle - return TextBlock(textStyle=textStyle, blockStyle=blockStyle, **settings) - - def pages(self): - '''Return list of Page objects in this book ''' - ans = [] - for item in self.delegates: - if isinstance(item, Main): - for candidate in item.contents: - if isinstance(candidate, Page): - ans.append(candidate) - break - return ans - - def last_page(self): - '''Return last Page in this book ''' - for item in self.delegates: - if isinstance(item, Main): - temp = list(item.contents) - temp.reverse() - for candidate in temp: - if isinstance(candidate, Page): - return candidate - - def embed_font(self, file, facename): - f = Font(file, facename) - self.append(f) - - def getSettings(self): - return ["sourceencoding"] - - - def append(self, content): - """ Find and invoke the correct appender for this content. """ - - className = content.__class__.__name__ - try: - method = getattr(self, "append" + className) - except AttributeError: - raise LrsError, "can't append %s to Book" % className - - method(content) - - - def rationalize_font_sizes(self, base_font_size=10): - base_font_size *= 10. - main = None - for obj in self.delegates: - if isinstance(obj, Main): - main = obj - break - - fonts = {} - for text in main.get_all(lambda x: isinstance(x, Text)): - fs = base_font_size - ancestor = text.parent - while ancestor: - try: - fs = int(ancestor.attrs['fontsize']) - break - except (AttributeError, KeyError): - pass - try: - fs = int(ancestor.textSettings['fontsize']) - break - except (AttributeError, KeyError): - pass - try: - fs = int(ancestor.textStyle.attrs['fontsize']) - break - except (AttributeError, KeyError): - pass - ancestor = ancestor.parent - length = len(text.text) - fonts[fs] = fonts.get(fs, 0) + length - if not fonts: - print 'WARNING: LRF seems to have no textual content. Cannot rationalize font sizes.' - return - - old_base_font_size = float(max(fonts.items(), key=operator.itemgetter(1))[0]) - factor = base_font_size / old_base_font_size - def rescale(old): - return str(int(int(old) * factor)) - - text_blocks = list(main.get_all(lambda x: isinstance(x, TextBlock))) - for tb in text_blocks: - if tb.textSettings.has_key('fontsize'): - tb.textSettings['fontsize'] = rescale(tb.textSettings['fontsize']) - for span in tb.get_all(lambda x: isinstance(x, Span)): - if span.attrs.has_key('fontsize'): - span.attrs['fontsize'] = rescale(span.attrs['fontsize']) - if span.attrs.has_key('baselineskip'): - span.attrs['baselineskip'] = rescale(span.attrs['baselineskip']) - - text_styles = set(tb.textStyle for tb in text_blocks) - for ts in text_styles: - ts.attrs['fontsize'] = rescale(ts.attrs['fontsize']) - ts.attrs['baselineskip'] = rescale(ts.attrs['baselineskip']) - - - def renderLrs(self, lrsFile, encoding="UTF-8"): - if isinstance(lrsFile, basestring): - lrsFile = codecs.open(lrsFile, "wb", encoding=encoding) - self.render(lrsFile, outputEncodingName=encoding) - lrsFile.close() - - - def renderLrf(self, lrfFile): - self.appendReferencedObjects(self) - if isinstance(lrfFile, basestring): - lrfFile = file(lrfFile, "wb") - lrfWriter = LrfWriter(self.sourceencoding) - - lrfWriter.optimizeTags = self.optimizeTags - lrfWriter.optimizeCompression = self.optimizeCompression - - self.toLrf(lrfWriter) - lrfWriter.writeFile(lrfFile) - lrfFile.close() - - - def toElement(self, se): - root = Element("BBeBXylog", version="1.0") - root.append(Element("Property")) - self.appendDelegates(root, self.sourceencoding) - return root - - - def render(self, f, outputEncodingName='UTF-8'): - """ Write the book as an LRS to file f. """ - - self.appendReferencedObjects(self) - - # create the root node, and populate with the parts of the book - - root = self.toElement(self.sourceencoding) - - # now, add some newlines to make it easier to look at - - _formatXml(root) - - writer = ElementWriter(root, header=True, - sourceEncoding=self.sourceencoding, - spaceBeforeClose=False, - outputEncodingName=outputEncodingName) - writer.write(f) - - - -class BookInformation(Delegator): - """ Just a container for the Info and TableOfContents elements. """ - def __init__(self): - Delegator.__init__(self, [Info(), TableOfContents()]) - - - def toElement(self, se): - bi = Element("BookInformation") - self.appendDelegates(bi, se) - return bi - - - -class Info(Delegator): - """ Just a container for the BookInfo and DocInfo elements. """ - def __init__(self): - self.genreading = DEFAULT_GENREADING - Delegator.__init__(self, [BookInfo(), DocInfo()]) - - - def getSettings(self): - return ["genreading"] #+ self.delegatedSettings - - - def toElement(self, se): - info = Element("Info", version="1.1") - info.append( - self.delegates[0].toElement(se, reading="s" in self.genreading)) - info.append(self.delegates[1].toElement(se)) - return info - - - def toLrf(self, lrfWriter): - # this info is set in XML form in the LRF - info = Element("Info", version="1.1") - #self.appendDelegates(info) - info.append( - self.delegates[0].toElement(lrfWriter.getSourceEncoding(), reading="f" in self.genreading)) - info.append(self.delegates[1].toElement(lrfWriter.getSourceEncoding())) - - # look for the thumbnail file and get the filename - tnail = info.find("DocInfo/CThumbnail") - if tnail is not None: - lrfWriter.setThumbnailFile(tnail.get("file")) - # does not work: info.remove(tnail) - - - _formatXml(info) - - # fix up the doc info to match the LRF format - # NB: generates an encoding attribute, which lrs2lrf does not - xmlInfo = ElementWriter(info, header=True, sourceEncoding=lrfWriter.getSourceEncoding(), - spaceBeforeClose=False).toString() - - xmlInfo = re.sub(r"\n", "", xmlInfo) - xmlInfo = xmlInfo.replace("SumPage>", "Page>") - lrfWriter.docInfoXml = xmlInfo - - - -class TableOfContents(object): - def __init__(self): - self.tocEntries = [] - - - def appendReferencedObjects(self, parent): - pass - - - def getMethods(self): - return ["addTocEntry"] - - - def getSettings(self): - return [] - - - def addTocEntry(self, tocLabel, textBlock): - if not isinstance(textBlock, (Canvas, TextBlock, ImageBlock, RuledLine)): - raise LrsError, "TOC destination must be a Canvas, TextBlock, ImageBlock or RuledLine"+\ - " not a " + str(type(textBlock)) - - if textBlock.parent is None: - raise LrsError, "TOC text block must be already appended to a page" - - if False and textBlock.parent.parent is None: - raise LrsError, \ - "TOC destination page must be already appended to a book" - - if not hasattr(textBlock.parent, 'objId'): - raise LrsError, "TOC destination must be appended to a container with an objID" - - for tl in self.tocEntries: - if tl.label == tocLabel and tl.textBlock == textBlock: - return - - self.tocEntries.append(TocLabel(tocLabel, textBlock)) - textBlock.tocLabel = tocLabel - - - def toElement(self, se): - if len(self.tocEntries) == 0: - return None - - toc = Element("TOC") - - for t in self.tocEntries: - toc.append(t.toElement(se)) - - return toc - - - def toLrf(self, lrfWriter): - if len(self.tocEntries) == 0: - return - - toc = [] - for t in self.tocEntries: - toc.append((t.textBlock.parent.objId, t.textBlock.objId, t.label)) - - lrfToc = LrfToc(LrsObject.getNextObjId(), toc, lrfWriter.getSourceEncoding()) - lrfWriter.append(lrfToc) - lrfWriter.setTocObject(lrfToc) - - - -class TocLabel(object): - def __init__(self, label, textBlock): - self.label = escape(re.sub(r'&(\S+?);', entity_to_unicode, label)) - self.textBlock = textBlock - - - def toElement(self, se): - return ElementWithText("TocLabel", self.label, - refobj=str(self.textBlock.objId), - refpage=str(self.textBlock.parent.objId)) - - - -class BookInfo(object): - def __init__(self): - self.title = "Untitled" - self.author = "Anonymous" - self.bookid = None - self.pi = None - self.isbn = None - self.publisher = None - self.freetext = "\n\n" - self.label = None - self.category = None - self.classification = None - - def appendReferencedObjects(self, parent): - pass - - - def getMethods(self): - return [] - - - def getSettings(self): - return ["author", "title", "bookid", "isbn", "publisher", - "freetext", "label", "category", "classification"] - - - def _appendISBN(self, bi): - pi = Element("ProductIdentifier") - isbnElement = ElementWithText("ISBNPrintable", self.isbn) - isbnValueElement = ElementWithText("ISBNValue", - self.isbn.replace("-", "")) - - pi.append(isbnElement) - pi.append(isbnValueElement) - bi.append(pi) - - - def toElement(self, se, reading=True): - bi = Element("BookInfo") - bi.append(ElementWithReading("Title", self.title, reading=reading)) - bi.append(ElementWithReading("Author", self.author, reading=reading)) - bi.append(ElementWithText("BookID", self.bookid)) - if self.isbn is not None: - self._appendISBN(bi) - - if self.publisher is not None: - bi.append(ElementWithReading("Publisher", self.publisher)) - - bi.append(ElementWithReading("Label", self.label, reading=reading)) - bi.append(ElementWithText("Category", self.category)) - bi.append(ElementWithText("Classification", self.classification)) - bi.append(ElementWithText("FreeText", self.freetext)) - return bi - - - -class DocInfo(object): - def __init__(self): - self.thumbnail = None - self.language = "en" - self.creator = None - self.creationdate = date.today().isoformat() - self.producer = "%s v%s"%(__appname__, __version__) - self.numberofpages = "0" - - - def appendReferencedObjects(self, parent): - pass - - - def getMethods(self): - return [] - - - def getSettings(self): - return ["thumbnail", "language", "creator", "creationdate", - "producer", "numberofpages"] - - - def toElement(self, se): - docInfo = Element("DocInfo") - - if self.thumbnail is not None: - docInfo.append(Element("CThumbnail", file=self.thumbnail)) - - docInfo.append(ElementWithText("Language", self.language)) - docInfo.append(ElementWithText("Creator", self.creator)) - docInfo.append(ElementWithText("CreationDate", self.creationdate)) - docInfo.append(ElementWithText("Producer", self.producer)) - docInfo.append(ElementWithText("SumPage", str(self.numberofpages))) - return docInfo - - - -class Main(LrsContainer): - def __init__(self): - LrsContainer.__init__(self, [Page]) - - - def getMethods(self): - return ["appendPage", "Page"] - - - def getSettings(self): - return [] - - - def Page(self, *args, **kwargs): - p = Page(*args, **kwargs) - self.append(p) - return p - - - def appendPage(self, page): - self.append(page) - - - def toElement(self, sourceEncoding): - main = Element(self.__class__.__name__) - - for page in self.contents: - main.append(page.toElement(sourceEncoding)) - - return main - - - def toLrf(self, lrfWriter): - pageIds = [] - - # set this id now so that pages can see it - pageTreeId = LrsObject.getNextObjId() - lrfWriter.setPageTreeId(pageTreeId) - - # create a list of all the page object ids while dumping the pages - - for p in self.contents: - pageIds.append(p.objId) - p.toLrf(lrfWriter) - - # create a page tree object - - pageTree = LrfObject("PageTree", pageTreeId) - pageTree.appendLrfTag(LrfTag("PageList", pageIds)) - - lrfWriter.append(pageTree) - - - -class Solos(LrsContainer): - def __init__(self): - LrsContainer.__init__(self, [Solo]) - - - def getMethods(self): - return ["appendSolo", "Solo"] - - - def getSettings(self): - return [] - - - def Solo(self, *args, **kwargs): - p = Solo(*args, **kwargs) - self.append(p) - return p - - - def appendSolo(self, solo): - self.append(solo) - - - def toLrf(self, lrfWriter): - for s in self.contents: - s.toLrf(lrfWriter) - - - def toElement(self, se): - solos = [] - for s in self.contents: - solos.append(s.toElement(se)) - - if len(solos) == 0: - return None - - - return solos - - - -class Solo(Main): - pass - - -class Template(object): - """ Does nothing that I know of. """ - - def appendReferencedObjects(self, parent): - pass - - - def getMethods(self): - return [] - - - def getSettings(self): - return [] - - - def toElement(self, se): - t = Element("Template") - t.attrib["version"] = "1.0" - return t - - def toLrf(self, lrfWriter): - # does nothing - pass - -class StyleDefault(LrsAttributes): - """ - Supply some defaults for all TextBlocks. - The legal values are a subset of what is allowed on a - TextBlock -- ruby, emphasis, and waitprop settings. - """ - defaults = dict(rubyalign="start", rubyadjust="none", - rubyoverhang="none", empdotsposition="before", - empdotsfontname="Dutch801 Rm BT Roman", - empdotscode="0x002e", emplineposition="after", - emplinetype = "solid", setwaitprop="noreplay") - - alsoAllow = ["refempdotsfont", "rubyAlignAndAdjust"] - - def __init__(self, **settings): - LrsAttributes.__init__(self, self.defaults, - alsoAllow=self.alsoAllow, **settings) - - - def toElement(self, se): - return Element("SetDefault", self.attrs) - - -class Style(LrsContainer, Delegator): - def __init__(self, styledefault=StyleDefault()): - LrsContainer.__init__(self, [PageStyle, TextStyle, BlockStyle]) - Delegator.__init__(self, [BookStyle(styledefault=styledefault)]) - self.bookStyle = self.delegates[0] - self.appendPageStyle = self.appendTextStyle = \ - self.appendBlockStyle = self.append - - - def appendReferencedObjects(self, parent): - LrsContainer.appendReferencedObjects(self, parent) - - - def getMethods(self): - return ["PageStyle", "TextStyle", "BlockStyle", - "appendPageStyle", "appendTextStyle", "appendBlockStyle"] + \ - self.delegatedMethods - - def getSettings(self): - return [(self.bookStyle, x) for x in self.bookStyle.getSettings()] - - - def PageStyle(self, *args, **kwargs): - ps = PageStyle(*args, **kwargs) - self.append(ps) - return ps - - - def TextStyle(self, *args, **kwargs): - ts = TextStyle(*args, **kwargs) - self.append(ts) - return ts - - - def BlockStyle(self, *args, **kwargs): - bs = BlockStyle(*args, **kwargs) - self.append(bs) - return bs - - - def toElement(self, se): - style = Element("Style") - style.append(self.bookStyle.toElement(se)) - - for content in self.contents: - style.append(content.toElement(se)) - - return style - - - def toLrf(self, lrfWriter): - self.bookStyle.toLrf(lrfWriter) - - for s in self.contents: - s.toLrf(lrfWriter) - - - -class BookStyle(LrsObject, LrsContainer): - def __init__(self, styledefault=StyleDefault()): - LrsObject.__init__(self, assignId=True) - LrsContainer.__init__(self, [Font]) - self.styledefault = styledefault - self.booksetting = BookSetting() - self.appendFont = self.append - - - def getSettings(self): - return ["styledefault", "booksetting"] - - - def getMethods(self): - return ["Font", "appendFont"] - - - def Font(self, *args, **kwargs): - f = Font(*args, **kwargs) - self.append(f) - return - - - def toElement(self, se): - bookStyle = self.lrsObjectElement("BookStyle", objlabel="stylelabel", - labelDecorate=False) - bookStyle.append(self.styledefault.toElement(se)) - bookStyle.append(self.booksetting.toElement(se)) - for font in self.contents: - bookStyle.append(font.toElement(se)) - - return bookStyle - - - def toLrf(self, lrfWriter): - bookAtr = LrfObject("BookAtr", self.objId) - bookAtr.appendLrfTag(LrfTag("ChildPageTree", lrfWriter.getPageTreeId())) - bookAtr.appendTagDict(self.styledefault.attrs) - - self.booksetting.toLrf(lrfWriter) - - lrfWriter.append(bookAtr) - lrfWriter.setRootObject(bookAtr) - - for font in self.contents: - font.toLrf(lrfWriter) - - - - - - -class BookSetting(LrsAttributes): - def __init__(self, **settings): - defaults = dict(bindingdirection="Lr", dpi="1660", - screenheight="800", screenwidth="600", colordepth="24") - LrsAttributes.__init__(self, defaults, **settings) - - - def toLrf(self, lrfWriter): - a = self.attrs - lrfWriter.dpi = int(a["dpi"]) - lrfWriter.bindingdirection = \ - BINDING_DIRECTION_ENCODING[a["bindingdirection"]] - lrfWriter.height = int(a["screenheight"]) - lrfWriter.width = int(a["screenwidth"]) - lrfWriter.colorDepth = int(a["colordepth"]) - - def toElement(self, se): - return Element("BookSetting", self.attrs) - - - -class LrsStyle(LrsObject, LrsAttributes, LrsContainer): - """ A mixin class for styles. """ - def __init__(self, elementName, defaults=None, alsoAllow=None, **overrides): - if defaults is None: - defaults = {} - - LrsObject.__init__(self) - LrsAttributes.__init__(self, defaults, alsoAllow=alsoAllow, **overrides) - LrsContainer.__init__(self, []) - self.elementName = elementName - self.objectsAppended = False - #self.label = "%s.%d" % (elementName, self.objId) - #self.label = str(self.objId) - #self.parent = None - - - def update(self, settings): - for name, value in settings.items(): - if name not in self.__class__.validSettings: - raise LrsError, "%s not a valid setting for %s" % \ - (name, self.__class__.__name__) - self.attrs[name] = value - - def getLabel(self): - return str(self.objId) - - - def toElement(self, se): - element = Element(self.elementName, stylelabel=self.getLabel(), - objid=str(self.objId)) - element.attrib.update(self.attrs) - return element - - - def toLrf(self, lrfWriter): - obj = LrfObject(self.elementName, self.objId) - obj.appendTagDict(self.attrs, self.__class__.__name__) - lrfWriter.append(obj) - - def __eq__(self, other): - if hasattr(other, 'attrs'): - return self.__class__ == other.__class__ and self.attrs == other.attrs - return False - -class TextStyle(LrsStyle): - """ - The text style of a TextBlock. Default is 10 pt. Times Roman. - - Setting Value Default - -------- ----- ------- - align "head","center","foot" "head" (left aligned) - baselineskip points * 10 120 (12 pt. distance between - bottoms of lines) - fontsize points * 10 100 (10 pt.) - fontweight 1 to 1000 400 (normal, 800 is bold) - fontwidth points * 10 or -10 -10 (use values from font) - linespace points * 10 10 (min space btw. lines?) - wordspace points * 10 25 (min space btw. each word) - - """ - baseDefaults = dict( - columnsep="0", charspace="0", - textlinewidth="2", align="head", linecolor="0x00000000", - column="1", fontsize="100", fontwidth="-10", fontescapement="0", - fontorientation="0", fontweight="400", - fontfacename="Dutch801 Rm BT Roman", - textcolor="0x00000000", wordspace="25", letterspace="0", - baselineskip="120", linespace="10", parindent="0", parskip="0", - textbgcolor="0xFF000000") - - alsoAllow = ["empdotscode", "empdotsfontname", "refempdotsfont", - "rubyadjust", "rubyalign", "rubyoverhang", - "empdotsposition", 'emplinetype', 'emplineposition'] - - validSettings = baseDefaults.keys() + alsoAllow - - defaults = baseDefaults.copy() - - def __init__(self, **overrides): - LrsStyle.__init__(self, "TextStyle", self.defaults, - alsoAllow=self.alsoAllow, **overrides) - - def copy(self): - tb = TextStyle() - tb.attrs = self.attrs.copy() - return tb - - - -class BlockStyle(LrsStyle): - """ - The block style of a TextBlock. Default is an expandable 560 pixel - wide area with no space for headers or footers. - - Setting Value Default - -------- ----- ------- - blockwidth pixels 560 - sidemargin pixels 0 - """ - - baseDefaults = dict( - bgimagemode="fix", framemode="square", blockwidth="560", - blockheight="100", blockrule="horz-adjustable", layout="LrTb", - framewidth="0", framecolor="0x00000000", topskip="0", - sidemargin="0", footskip="0", bgcolor="0xFF000000") - - validSettings = baseDefaults.keys() - defaults = baseDefaults.copy() - - def __init__(self, **overrides): - LrsStyle.__init__(self, "BlockStyle", self.defaults, **overrides) - - def copy(self): - tb = BlockStyle() - tb.attrs = self.attrs.copy() - return tb - - - -class PageStyle(LrsStyle): - """ - Setting Value Default - -------- ----- ------- - evensidemargin pixels 20 - oddsidemargin pixels 20 - topmargin pixels 20 - """ - baseDefaults = dict( - topmargin="20", headheight="0", headsep="0", - oddsidemargin="20", textheight="747", textwidth="575", - footspace="0", evensidemargin="20", footheight="0", - layout="LrTb", bgimagemode="fix", pageposition="any", - setwaitprop="noreplay", setemptyview="show") - - alsoAllow = ["header", "evenheader", "oddheader", - "footer", "evenfooter", "oddfooter"] - - validSettings = baseDefaults.keys() + alsoAllow - defaults = baseDefaults.copy() - - @classmethod - def translateHeaderAndFooter(selfClass, parent, settings): - selfClass._fixup(parent, "header", settings) - selfClass._fixup(parent, "footer", settings) - - - @classmethod - def _fixup(selfClass, parent, basename, settings): - evenbase = "even" + basename - oddbase = "odd" + basename - if basename in settings: - baseObj = settings[basename] - del settings[basename] - settings[evenbase] = settings[oddbase] = baseObj - - if evenbase in settings: - evenObj = settings[evenbase] - del settings[evenbase] - if evenObj.parent is None: - parent.append(evenObj) - settings[evenbase + "id"] = str(evenObj.objId) - - if oddbase in settings: - oddObj = settings[oddbase] - del settings[oddbase] - if oddObj.parent is None: - parent.append(oddObj) - settings[oddbase + "id"] = str(oddObj.objId) - - - def appendReferencedObjects(self, parent): - if self.objectsAppended: - return - PageStyle.translateHeaderAndFooter(parent, self.attrs) - self.objectsAppended = True - - - - def __init__(self, **settings): - #self.fixHeaderSettings(settings) - LrsStyle.__init__(self, "PageStyle", self.defaults, - alsoAllow=self.alsoAllow, **settings) - - -class Page(LrsObject, LrsContainer): - """ - Pages are added to Books. Pages can be supplied a PageStyle. - If they are not, Page.defaultPageStyle will be used. - """ - defaultPageStyle = PageStyle() - - def __init__(self, pageStyle=defaultPageStyle, **settings): - LrsObject.__init__(self) - LrsContainer.__init__(self, [TextBlock, BlockSpace, RuledLine, - ImageBlock, Canvas]) - - self.pageStyle = pageStyle - - for settingName in settings.keys(): - if settingName not in PageStyle.defaults and \ - settingName not in PageStyle.alsoAllow: - raise LrsError, "setting %s not allowed on Page" % settingName - - self.settings = settings.copy() - - - def appendReferencedObjects(self, parent): - PageStyle.translateHeaderAndFooter(parent, self.settings) - - self.pageStyle.appendReferencedObjects(parent) - - if self.pageStyle.parent is None: - parent.append(self.pageStyle) - - LrsContainer.appendReferencedObjects(self, parent) - - - def RuledLine(self, *args, **kwargs): - rl = RuledLine(*args, **kwargs) - self.append(rl) - return rl - - - def BlockSpace(self, *args, **kwargs): - bs = BlockSpace(*args, **kwargs) - self.append(bs) - return bs - - - def TextBlock(self, *args, **kwargs): - """ Create and append a new text block (shortcut). """ - tb = TextBlock(*args, **kwargs) - self.append(tb) - return tb - - - def ImageBlock(self, *args, **kwargs): - """ Create and append and new Image block (shorthand). """ - ib = ImageBlock(*args, **kwargs) - self.append(ib) - return ib - - - def addLrfObject(self, objId): - self.stream.appendLrfTag(LrfTag("Link", objId)) - - - def appendLrfTag(self, lrfTag): - self.stream.appendLrfTag(lrfTag) - - - def toLrf(self, lrfWriter): - # tags: - # ObjectList - # Link to pagestyle - # Parent page tree id - # stream of tags - - p = LrfObject("Page", self.objId) - lrfWriter.append(p) - - pageContent = set() - self.stream = LrfTagStream(0) - for content in self.contents: - content.toLrfContainer(lrfWriter, self) - if hasattr(content, "getReferencedObjIds"): - pageContent.update(content.getReferencedObjIds()) - - - #print "page contents:", pageContent - # ObjectList not needed and causes slowdown in SONY LRF renderer - #p.appendLrfTag(LrfTag("ObjectList", pageContent)) - p.appendLrfTag(LrfTag("Link", self.pageStyle.objId)) - p.appendLrfTag(LrfTag("ParentPageTree", lrfWriter.getPageTreeId())) - p.appendTagDict(self.settings) - p.appendLrfTags(self.stream.getStreamTags(lrfWriter.getSourceEncoding())) - - - def toElement(self, sourceEncoding): - page = self.lrsObjectElement("Page") - page.set("pagestyle", self.pageStyle.getLabel()) - page.attrib.update(self.settings) - - for content in self.contents: - page.append(content.toElement(sourceEncoding)) - - return page - - - - - -class TextBlock(LrsObject, LrsContainer): - """ - TextBlocks are added to Pages. They hold Paragraphs or CRs. - - If a TextBlock is used in a header, it should be appended to - the Book, not to a specific Page. - """ - defaultTextStyle = TextStyle() - defaultBlockStyle = BlockStyle() - - def __init__(self, textStyle=defaultTextStyle, \ - blockStyle=defaultBlockStyle, \ - **settings): - ''' - Create TextBlock. - @param textStyle: The L{TextStyle} for this block. - @param blockStyle: The L{BlockStyle} for this block. - @param settings: C{dict} of extra settings to apply to this block. - ''' - LrsObject.__init__(self) - LrsContainer.__init__(self, [Paragraph, CR]) - - self.textSettings = {} - self.blockSettings = {} - - for name, value in settings.items(): - if name in TextStyle.validSettings: - self.textSettings[name] = value - elif name in BlockStyle.validSettings: - self.blockSettings[name] = value - elif name == 'toclabel': - self.tocLabel = value - else: - raise LrsError, "%s not a valid setting for TextBlock" % name - - self.textStyle = textStyle - self.blockStyle = blockStyle - - # create a textStyle with our current text settings (for Span to find) - self.currentTextStyle = textStyle.copy() if self.textSettings else textStyle - self.currentTextStyle.attrs.update(self.textSettings) - - - def appendReferencedObjects(self, parent): - if self.textStyle.parent is None: - parent.append(self.textStyle) - - if self.blockStyle.parent is None: - parent.append(self.blockStyle) - - LrsContainer.appendReferencedObjects(self, parent) - - - def Paragraph(self, *args, **kwargs): - """ - Create and append a Paragraph to this TextBlock. A CR is - automatically inserted after the Paragraph. To avoid this - behavior, create the Paragraph and append it to the TextBlock - in a separate call. - """ - p = Paragraph(*args, **kwargs) - self.append(p) - self.append(CR()) - return p - - - - def toElement(self, sourceEncoding): - tb = self.lrsObjectElement("TextBlock", labelName="Block") - tb.attrib.update(self.textSettings) - tb.attrib.update(self.blockSettings) - tb.set("textstyle", self.textStyle.getLabel()) - tb.set("blockstyle", self.blockStyle.getLabel()) - if hasattr(self, "tocLabel"): - tb.set("toclabel", self.tocLabel) - - for content in self.contents: - tb.append(content.toElement(sourceEncoding)) - - return tb - - def getReferencedObjIds(self): - ids = [self.objId, self.extraId, self.blockStyle.objId, - self.textStyle.objId] - for content in self.contents: - if hasattr(content, "getReferencedObjIds"): - ids.extend(content.getReferencedObjIds()) - - return ids - - - def toLrf(self, lrfWriter): - self.toLrfContainer(lrfWriter, lrfWriter) - - - def toLrfContainer(self, lrfWriter, container): - # id really belongs to the outer block - extraId = LrsObject.getNextObjId() - - b = LrfObject("Block", self.objId) - b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) - b.appendLrfTags( - LrfTagStream(0, [LrfTag("Link", extraId)]). \ - getStreamTags(lrfWriter.getSourceEncoding())) - b.appendTagDict(self.blockSettings) - container.addLrfObject(b.objId) - lrfWriter.append(b) - - tb = LrfObject("TextBlock", extraId) - tb.appendLrfTag(LrfTag("Link", self.textStyle.objId)) - tb.appendTagDict(self.textSettings) - - stream = LrfTagStream(STREAM_COMPRESSED) - for content in self.contents: - content.toLrfContainer(lrfWriter, stream) - - if lrfWriter.saveStreamTags: # true only if testing - tb.saveStreamTags = stream.tags - - tb.appendLrfTags( - stream.getStreamTags(lrfWriter.getSourceEncoding(), - optimizeTags=lrfWriter.optimizeTags, - optimizeCompression=lrfWriter.optimizeCompression)) - lrfWriter.append(tb) - - self.extraId = extraId - - -class Paragraph(LrsContainer): - """ - Note:

alone does not make a paragraph. Only a CR inserted - into a text block right after a

makes a real paragraph. - Two Paragraphs appended in a row act like a single Paragraph. - - Also note that there are few autoappenders for Paragraph (and - the things that can go in it.) It's less confusing (to me) to use - explicit .append methods to build up the text stream. - """ - def __init__(self, text=None): - LrsContainer.__init__(self, [Text, CR, DropCaps, CharButton, - LrsSimpleChar1, basestring]) - if text is not None: - if isinstance(text, basestring): - text = Text(text) - self.append(text) - - def CR(self): - # Okay, here's a single autoappender for this common operation - cr = CR() - self.append(cr) - return cr - - - def getReferencedObjIds(self): - ids = [] - for content in self.contents: - if hasattr(content, "getReferencedObjIds"): - ids.extend(content.getReferencedObjIds()) - - return ids - - - def toLrfContainer(self, lrfWriter, parent): - parent.appendLrfTag(LrfTag("pstart", 0)) - for content in self.contents: - content.toLrfContainer(lrfWriter, parent) - parent.appendLrfTag(LrfTag("pend")) - - - def toElement(self, sourceEncoding): - p = Element("P") - appendTextElements(p, self.contents, sourceEncoding) - return p - - - -class LrsTextTag(LrsContainer): - def __init__(self, text, validContents): - LrsContainer.__init__(self, [Text, basestring] + validContents) - if text is not None: - self.append(text) - - - def toLrfContainer(self, lrfWriter, parent): - if hasattr(self, "tagName"): - tagName = self.tagName - else: - tagName = self.__class__.__name__ - - parent.appendLrfTag(LrfTag(tagName)) - - for content in self.contents: - content.toLrfContainer(lrfWriter, parent) - - parent.appendLrfTag(LrfTag(tagName + "End")) - - - def toElement(self, se): - if hasattr(self, "tagName"): - tagName = self.tagName - else: - tagName = self.__class__.__name__ - - p = Element(tagName) - appendTextElements(p, self.contents, se) - return p - - -class LrsSimpleChar1(object): - def isEmpty(self): - for content in self.contents: - if not content.isEmpty(): - return False - return True - - def hasFollowingContent(self): - foundSelf = False - for content in self.parent.contents: - if content == self: - foundSelf = True - elif foundSelf: - if not content.isEmpty(): - return True - return False - - -class DropCaps(LrsTextTag): - - def __init__(self, line=1): - LrsTextTag.__init__(self, None, [LrsSimpleChar1]) - if int(line) <= 0: - raise LrsError('A DrawChar must span at least one line.') - self.line = int(line) - - def isEmpty(self): - return self.text == None or not self.text.strip() - - def toElement(self, se): - elem = Element('DrawChar', line=str(self.line)) - appendTextElements(elem, self.contents, se) - return elem - - def toLrfContainer(self, lrfWriter, parent): - parent.appendLrfTag(LrfTag('DrawChar', (int(self.line),))) - - for content in self.contents: - content.toLrfContainer(lrfWriter, parent) - - parent.appendLrfTag(LrfTag("DrawCharEnd")) - - - -class Button(LrsObject, LrsContainer): - def __init__(self, **settings): - LrsObject.__init__(self, **settings) - LrsContainer.__init__(self, [PushButton]) - - def findJumpToRefs(self): - for sub1 in self.contents: - if isinstance(sub1, PushButton): - for sub2 in sub1.contents: - if isinstance(sub2, JumpTo): - return (sub2.textBlock.objId, sub2.textBlock.parent.objId) - raise LrsError, "%s has no PushButton or JumpTo subs"%self.__class__.__name__ - - def toLrf(self, lrfWriter): - (refobj, refpage) = self.findJumpToRefs() - # print "Button writing JumpTo refobj=", jumpto.refobj, ", and refpage=", jumpto.refpage - button = LrfObject("Button", self.objId) - button.appendLrfTag(LrfTag("buttonflags", 0x10)) # pushbutton - button.appendLrfTag(LrfTag("PushButtonStart")) - button.appendLrfTag(LrfTag("buttonactions")) - button.appendLrfTag(LrfTag("jumpto", (int(refpage), int(refobj)))) - button.append(LrfTag("endbuttonactions")) - button.appendLrfTag(LrfTag("PushButtonEnd")) - lrfWriter.append(button) - - def toElement(self, se): - b = self.lrsObjectElement("Button") - - for content in self.contents: - b.append(content.toElement(se)) - - return b - -class ButtonBlock(Button): - pass - -class PushButton(LrsContainer): - - def __init__(self, **settings): - LrsContainer.__init__(self, [JumpTo]) - - def toElement(self, se): - b = Element("PushButton") - - for content in self.contents: - b.append(content.toElement(se)) - - return b - -class JumpTo(LrsContainer): - - def __init__(self, textBlock): - LrsContainer.__init__(self, []) - self.textBlock=textBlock - - def setTextBlock(self, textBlock): - self.textBlock = textBlock - - def toElement(self, se): - return Element("JumpTo", refpage=str(self.textBlock.parent.objId), refobj=str(self.textBlock.objId)) - - - - - -class Plot(LrsSimpleChar1, LrsContainer): - - ADJUSTMENT_VALUES = {'center':1, 'baseline':2, 'top':3, 'bottom':4} - - def __init__(self, obj, xsize=0, ysize=0, adjustment=None): - LrsContainer.__init__(self, []) - if obj != None: - self.setObj(obj) - if xsize < 0 or ysize < 0: - raise LrsError('Sizes must be positive semi-definite') - self.xsize = int(xsize) - self.ysize = int(ysize) - if adjustment and adjustment not in Plot.ADJUSTMENT_VALUES.keys(): - raise LrsError('adjustment must be one of' + Plot.ADJUSTMENT_VALUES.keys()) - self.adjustment = adjustment - - def setObj(self, obj): - if not isinstance(obj, (Image, Button)): - raise LrsError('Plot elements can only refer to Image or Button elements') - self.obj = obj - - def getReferencedObjIds(self): - return [self.obj.objId] - - def appendReferencedObjects(self, parent): - if self.obj.parent is None: - parent.append(self.obj) - - def toElement(self, se): - elem = Element('Plot', xsize=str(self.xsize), ysize=str(self.ysize), \ - refobj=str(self.obj.objId)) - if self.adjustment: - elem.set('adjustment', self.adjustment) - return elem - - def toLrfContainer(self, lrfWriter, parent): - adj = self.adjustment if self.adjustment else 'bottom' - params = (int(self.xsize), int(self.ysize), int(self.obj.objId), \ - Plot.ADJUSTMENT_VALUES[adj]) - parent.appendLrfTag(LrfTag("Plot", params)) - -class Text(LrsContainer): - """ A object that represents raw text. Does not have a toElement. """ - def __init__(self, text): - LrsContainer.__init__(self, []) - self.text = text - - def isEmpty(self): - return not self.text or not self.text.strip() - - def toLrfContainer(self, lrfWriter, parent): - if self.text: - if isinstance(self.text, str): - parent.appendLrfTag(LrfTag("rawtext", self.text)) - else: - parent.appendLrfTag(LrfTag("textstring", self.text)) - - -class CR(LrsSimpleChar1, LrsContainer): - """ - A line break (when appended to a Paragraph) or a paragraph break - (when appended to a TextBlock). - """ - def __init__(self): - LrsContainer.__init__(self, []) - - - def toElement(self, se): - return Element("CR") - - - def toLrfContainer(self, lrfWriter, parent): - parent.appendLrfTag(LrfTag("CR")) - - - -class Italic(LrsSimpleChar1, LrsTextTag): - def __init__(self, text=None): - LrsTextTag.__init__(self, text, [LrsSimpleChar1]) - -class Sub(LrsSimpleChar1, LrsTextTag): - def __init__(self, text=None): - LrsTextTag.__init__(self, text, []) - - - -class Sup(LrsSimpleChar1, LrsTextTag): - def __init__(self, text=None): - LrsTextTag.__init__(self, text, []) - - - -class NoBR(LrsSimpleChar1, LrsTextTag): - def __init__(self, text=None): - LrsTextTag.__init__(self, text, [LrsSimpleChar1]) - - -class Space(LrsSimpleChar1, LrsContainer): - def __init__(self, xsize=0, x=0): - LrsContainer.__init__(self, []) - if xsize == 0 and x != 0: xsize = x - self.xsize = xsize - - - def toElement(self, se): - if self.xsize == 0: - return - - return Element("Space", xsize=str(self.xsize)) - - - def toLrfContainer(self, lrfWriter, container): - if self.xsize != 0: - container.appendLrfTag(LrfTag("Space", self.xsize)) - - -class Box(LrsSimpleChar1, LrsContainer): - """ - Draw a box around text. Unfortunately, does not seem to do - anything on the PRS-500. - """ - def __init__(self, linetype="solid"): - LrsContainer.__init__(self, [Text, basestring]) - if linetype not in LINE_TYPE_ENCODING: - raise LrsError, linetype + " is not a valid line type" - self.linetype = linetype - - - def toElement(self, se): - e = Element("Box", linetype=self.linetype) - appendTextElements(e, self.contents, se) - return e - - - def toLrfContainer(self, lrfWriter, container): - container.appendLrfTag(LrfTag("Box", self.linetype)) - for content in self.contents: - content.toLrfContainer(lrfWriter, container) - container.appendLrfTag(LrfTag("BoxEnd")) - - - - -class Span(LrsSimpleChar1, LrsContainer): - def __init__(self, text=None, **attrs): - LrsContainer.__init__(self, [LrsSimpleChar1, Text, basestring]) - if text is not None: - if isinstance(text, basestring): - text = Text(text) - self.append(text) - - for attrname in attrs.keys(): - if attrname not in TextStyle.defaults and \ - attrname not in TextStyle.alsoAllow: - raise LrsError, "setting %s not allowed on Span" % attrname - self.attrs = attrs - - - def findCurrentTextStyle(self): - parent = self.parent - while 1: - if parent is None or hasattr(parent, "currentTextStyle"): - break - parent = parent.parent - - if parent is None: - raise LrsError, "no enclosing current TextStyle found" - - return parent.currentTextStyle - - - - def toLrfContainer(self, lrfWriter, container): - - # find the currentTextStyle - oldTextStyle = self.findCurrentTextStyle() - - # set the attributes we want changed - for (name, value) in self.attrs.items(): - if name in oldTextStyle.attrs and oldTextStyle.attrs[name] == self.attrs[name]: - self.attrs.pop(name) - else: - container.appendLrfTag(LrfTag(name, value)) - - # set a currentTextStyle so nested span can put things back - oldTextStyle = self.findCurrentTextStyle() - self.currentTextStyle = oldTextStyle.copy() - self.currentTextStyle.attrs.update(self.attrs) - - for content in self.contents: - content.toLrfContainer(lrfWriter, container) - - # put the attributes back the way we found them - # the attributes persist beyond the next

- # if self.hasFollowingContent(): - for name in self.attrs.keys(): - container.appendLrfTag(LrfTag(name, oldTextStyle.attrs[name])) - - - def toElement(self, se): - element = Element('Span') - for (key, value) in self.attrs.items(): - element.set(key, str(value)) - - appendTextElements(element, self.contents, se) - return element - -class EmpLine(LrsTextTag, LrsSimpleChar1): - emplinetypes = ['none', 'solid', 'dotted', 'dashed', 'double'] - emplinepositions = ['before', 'after'] - - def __init__(self, text=None, emplineposition='before', emplinetype='solid'): - LrsTextTag.__init__(self, text, [LrsSimpleChar1]) - if emplineposition not in self.__class__.emplinepositions: - raise LrsError('emplineposition for an EmpLine must be one of: '+str(self.__class__.emplinepositions)) - if emplinetype not in self.__class__.emplinetypes: - raise LrsError('emplinetype for an EmpLine must be one of: '+str(self.__class__.emplinetypes)) - - self.emplinetype = emplinetype - self.emplineposition = emplineposition - - - - def toLrfContainer(self, lrfWriter, parent): - parent.appendLrfTag(LrfTag(self.__class__.__name__, (self.emplineposition, self.emplinetype))) - parent.appendLrfTag(LrfTag('emplineposition', self.emplineposition)) - parent.appendLrfTag(LrfTag('emplinetype', self.emplinetype)) - for content in self.contents: - content.toLrfContainer(lrfWriter, parent) - - parent.appendLrfTag(LrfTag(self.__class__.__name__ + "End")) - - def toElement(self, se): - element = Element(self.__class__.__name__) - element.set('emplineposition', self.emplineposition) - element.set('emplinetype', self.emplinetype) - - appendTextElements(element, self.contents, se) - return element - -class Bold(Span): - """ - There is no known "bold" lrf tag. Use Span with a fontweight in LRF, - but use the word Bold in the LRS. - """ - def __init__(self, text=None): - Span.__init__(self, text, fontweight=800) - - def toElement(self, se): - e = Element("Bold") - appendTextElements(e, self.contents, se) - return e - - -class BlockSpace(LrsContainer): - """ Can be appended to a page to move the text point. """ - def __init__(self, xspace=0, yspace=0, x=0, y=0): - LrsContainer.__init__(self, []) - if xspace == 0 and x != 0: - xspace = x - if yspace == 0 and y != 0: - yspace = y - self.xspace = xspace - self.yspace = yspace - - - def toLrfContainer(self, lrfWriter, container): - if self.xspace != 0: - container.appendLrfTag(LrfTag("xspace", self.xspace)) - if self.yspace != 0: - container.appendLrfTag(LrfTag("yspace", self.yspace)) - - - def toElement(self, se): - element = Element("BlockSpace") - - if self.xspace != 0: - element.attrib["xspace"] = str(self.xspace) - if self.yspace != 0: - element.attrib["yspace"] = str(self.yspace) - - return element - - - -class CharButton(LrsSimpleChar1, LrsContainer): - """ - Define the text and target of a CharButton. Must be passed a - JumpButton that is the destination of the CharButton. - - Only text or SimpleChars can be appended to the CharButton. - """ - def __init__(self, button, text=None): - LrsContainer.__init__(self, [basestring, Text, LrsSimpleChar1]) - self.button = None - if button != None: - self.setButton(button) - - if text is not None: - self.append(text) - - def setButton(self, button): - if not isinstance(button, (JumpButton, Button)): - raise LrsError, "CharButton button must be a JumpButton or Button" - - self.button = button - - - def appendReferencedObjects(self, parent): - if self.button.parent is None: - parent.append(self.button) - - - def getReferencedObjIds(self): - return [self.button.objId] - - - def toLrfContainer(self, lrfWriter, container): - container.appendLrfTag(LrfTag("CharButton", self.button.objId)) - - for content in self.contents: - content.toLrfContainer(lrfWriter, container) - - container.appendLrfTag(LrfTag("CharButtonEnd")) - - - def toElement(self, se): - cb = Element("CharButton", refobj=str(self.button.objId)) - appendTextElements(cb, self.contents, se) - return cb - - - -class Objects(LrsContainer): - def __init__(self): - LrsContainer.__init__(self, [JumpButton, TextBlock, HeaderOrFooter, - ImageStream, Image, ImageBlock, Button, ButtonBlock]) - self.appendJumpButton = self.appendTextBlock = self.appendHeader = \ - self.appendFooter = self.appendImageStream = \ - self.appendImage = self.appendImageBlock = self.append - - - def getMethods(self): - return ["JumpButton", "appendJumpButton", "TextBlock", - "appendTextBlock", "Header", "appendHeader", - "Footer", "appendFooter", "ImageBlock", - "ImageStream", "appendImageStream", - 'Image','appendImage', 'appendImageBlock'] - - - def getSettings(self): - return [] - - - def ImageBlock(self, *args, **kwargs): - ib = ImageBlock(*args, **kwargs) - self.append(ib) - return ib - - def JumpButton(self, textBlock): - b = JumpButton(textBlock) - self.append(b) - return b - - - def TextBlock(self, *args, **kwargs): - tb = TextBlock(*args, **kwargs) - self.append(tb) - return tb - - - def Header(self, *args, **kwargs): - h = Header(*args, **kwargs) - self.append(h) - return h - - - def Footer(self, *args, **kwargs): - h = Footer(*args, **kwargs) - self.append(h) - return h - - - def ImageStream(self, *args, **kwargs): - i = ImageStream(*args, **kwargs) - self.append(i) - return i - - def Image(self, *args, **kwargs): - i = Image(*args, **kwargs) - self.append(i) - return i - - def toElement(self, se): - o = Element("Objects") - - for content in self.contents: - o.append(content.toElement(se)) - - return o - - - def toLrf(self, lrfWriter): - for content in self.contents: - content.toLrf(lrfWriter) - - -class JumpButton(LrsObject, LrsContainer): - """ - The target of a CharButton. Needs a parented TextBlock to jump to. - Actually creates several elements in the XML. JumpButtons must - be eventually appended to a Book (actually, an Object.) - """ - def __init__(self, textBlock): - LrsObject.__init__(self) - LrsContainer.__init__(self, []) - self.textBlock = textBlock - - def setTextBlock(self, textBlock): - self.textBlock = textBlock - - def toLrf(self, lrfWriter): - button = LrfObject("Button", self.objId) - button.appendLrfTag(LrfTag("buttonflags", 0x10)) # pushbutton - button.appendLrfTag(LrfTag("PushButtonStart")) - button.appendLrfTag(LrfTag("buttonactions")) - button.appendLrfTag(LrfTag("jumpto", - (self.textBlock.parent.objId, self.textBlock.objId))) - button.append(LrfTag("endbuttonactions")) - button.appendLrfTag(LrfTag("PushButtonEnd")) - lrfWriter.append(button) - - - def toElement(self, se): - b = self.lrsObjectElement("Button") - pb = SubElement(b, "PushButton") - jt = SubElement(pb, "JumpTo", - refpage=str(self.textBlock.parent.objId), - refobj=str(self.textBlock.objId)) - return b - - - -class RuledLine(LrsContainer, LrsAttributes, LrsObject): - """ A line. Default is 500 pixels long, 2 pixels wide. """ - - defaults = dict( - linelength="500", linetype="solid", linewidth="2", - linecolor="0x00000000") - - def __init__(self, **settings): - LrsContainer.__init__(self, []) - LrsAttributes.__init__(self, self.defaults, **settings) - LrsObject.__init__(self) - - - def toLrfContainer(self, lrfWriter, container): - a = self.attrs - container.appendLrfTag(LrfTag("RuledLine", - (a["linelength"], a["linetype"], a["linewidth"], a["linecolor"]))) - - - def toElement(self, se): - return Element("RuledLine", self.attrs) - - - -class HeaderOrFooter(LrsObject, LrsContainer, LrsAttributes): - """ - Creates empty header or footer objects. Append PutObj objects to - the header or footer to create the text. - - Note: it seems that adding multiple PutObjs to a header or footer - only shows the last one. - """ - defaults = dict(framemode="square", layout="LrTb", framewidth="0", - framecolor="0x00000000", bgcolor="0xFF000000") - - def __init__(self, **settings): - LrsObject.__init__(self) - LrsContainer.__init__(self, [PutObj]) - LrsAttributes.__init__(self, self.defaults, **settings) - - def put_object(self, obj, x1, y1): - self.append(PutObj(obj, x1, y1)) - - def PutObj(self, *args, **kwargs): - p = PutObj(*args, **kwargs) - self.append(p) - return p - - - def toLrf(self, lrfWriter): - hd = LrfObject(self.__class__.__name__, self.objId) - hd.appendTagDict(self.attrs) - - stream = LrfTagStream(0) - for content in self.contents: - content.toLrfContainer(lrfWriter, stream) - - hd.appendLrfTags(stream.getStreamTags(lrfWriter.getSourceEncoding())) - lrfWriter.append(hd) - - - def toElement(self, se): - name = self.__class__.__name__ - labelName = name.lower() + "label" - hd = self.lrsObjectElement(name, objlabel=labelName) - hd.attrib.update(self.attrs) - - for content in self.contents: - hd.append(content.toElement(se)) - - return hd - - -class Header(HeaderOrFooter): - pass - - - -class Footer(HeaderOrFooter): - pass - -class Canvas(LrsObject, LrsContainer, LrsAttributes): - defaults = dict(framemode="square", layout="LrTb", framewidth="0", - framecolor="0x00000000", bgcolor="0xFF000000", - canvasheight=0, canvaswidth=0, blockrule='block-adjustable') - - def __init__(self, width, height, **settings): - LrsObject.__init__(self) - LrsContainer.__init__(self, [PutObj]) - LrsAttributes.__init__(self, self.defaults, **settings) - - self.settings = self.defaults.copy() - self.settings.update(settings) - self.settings['canvasheight'] = int(height) - self.settings['canvaswidth'] = int(width) - - def put_object(self, obj, x1, y1): - self.append(PutObj(obj, x1, y1)) - - def toElement(self, source_encoding): - el = self.lrsObjectElement("Canvas", **self.settings) - for po in self.contents: - el.append(po.toElement(source_encoding)) - return el - - def toLrf(self, lrfWriter): - self.toLrfContainer(lrfWriter, lrfWriter) - - - def toLrfContainer(self, lrfWriter, container): - c = LrfObject("Canvas", self.objId) - c.appendTagDict(self.settings) - stream = LrfTagStream(STREAM_COMPRESSED) - for content in self.contents: - content.toLrfContainer(lrfWriter, stream) - if lrfWriter.saveStreamTags: # true only if testing - c.saveStreamTags = stream.tags - - c.appendLrfTags( - stream.getStreamTags(lrfWriter.getSourceEncoding(), - optimizeTags=lrfWriter.optimizeTags, - optimizeCompression=lrfWriter.optimizeCompression)) - container.addLrfObject(c.objId) - lrfWriter.append(c) - - def has_text(self): - return bool(self.contents) - - - -class PutObj(LrsContainer): - """ PutObj holds other objects that are drawn on a Canvas or Header. """ - - def __init__(self, content, x1=0, y1=0): - LrsContainer.__init__(self, [TextBlock, ImageBlock]) - self.content = content - self.x1 = int(x1) - self.y1 = int(y1) - - def setContent(self, content): - self.content = content - - def appendReferencedObjects(self, parent): - if self.content.parent is None: - parent.append(self.content) - - def toLrfContainer(self, lrfWriter, container): - container.appendLrfTag(LrfTag("PutObj", (self.x1, self.y1, - self.content.objId))) - - - def toElement(self, se): - el = Element("PutObj", x1=str(self.x1), y1=str(self.y1), - refobj=str(self.content.objId)) - return el - - - - -class ImageStream(LrsObject, LrsContainer): - """ - Embed an image file into an Lrf. - """ - - VALID_ENCODINGS = [ "JPEG", "GIF", "BMP", "PNG" ] - - def __init__(self, file=None, encoding=None, comment=None): - LrsObject.__init__(self) - LrsContainer.__init__(self, []) - _checkExists(file) - self.filename = file - self.comment = comment - # TODO: move encoding from extension to lrf module - if encoding is None: - extension = os.path.splitext(file)[1] - if not extension: - raise LrsError, \ - "file must have extension if encoding is not specified" - extension = extension[1:].upper() - - if extension == "JPG": - extension = "JPEG" - - encoding = extension - else: - encoding = encoding.upper() - - if encoding not in self.VALID_ENCODINGS: - raise LrsError, \ - "encoding or file extension not JPEG, GIF, BMP, or PNG" - - self.encoding = encoding - - - def toLrf(self, lrfWriter): - imageFile = file(self.filename, "rb") - imageData = imageFile.read() - imageFile.close() - - isObj = LrfObject("ImageStream", self.objId) - if self.comment is not None: - isObj.appendLrfTag(LrfTag("comment", self.comment)) - - streamFlags = IMAGE_TYPE_ENCODING[self.encoding] - stream = LrfStreamBase(streamFlags, imageData) - isObj.appendLrfTags(stream.getStreamTags()) - lrfWriter.append(isObj) - - - def toElement(self, se): - element = self.lrsObjectElement("ImageStream", - objlabel="imagestreamlabel", - encoding=self.encoding, file=self.filename) - element.text = self.comment - return element - -class Image(LrsObject, LrsContainer, LrsAttributes): - - defaults = dict() - - def __init__(self, refstream, x0=0, x1=0, \ - y0=0, y1=0, xsize=0, ysize=0, **settings): - LrsObject.__init__(self) - LrsContainer.__init__(self, []) - LrsAttributes.__init__(self, self.defaults, settings) - self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1) - self.xsize, self.ysize = int(xsize), int(ysize) - self.setRefstream(refstream) - - def setRefstream(self, refstream): - self.refstream = refstream - - def appendReferencedObjects(self, parent): - if self.refstream.parent is None: - parent.append(self.refstream) - - def getReferencedObjIds(self): - return [self.objId, self.refstream.objId] - - def toElement(self, se): - element = self.lrsObjectElement("Image", **self.attrs) - element.set("refstream", str(self.refstream.objId)) - for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]: - element.set(name, str(getattr(self, name))) - return element - - def toLrf(self, lrfWriter): - ib = LrfObject("Image", self.objId) - ib.appendLrfTag(LrfTag("ImageRect", - (self.x0, self.y0, self.x1, self.y1))) - ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize))) - ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId)) - lrfWriter.append(ib) - - - - - -class ImageBlock(LrsObject, LrsContainer, LrsAttributes): - """ Create an image on a page. """ - # TODO: allow other block attributes - - defaults = BlockStyle.baseDefaults.copy() - - def __init__(self, refstream, x0="0", y0="0", x1="600", y1="800", - xsize="600", ysize="800", - blockStyle=BlockStyle(blockrule='block-fixed'), - alttext=None, **settings): - LrsObject.__init__(self) - LrsContainer.__init__(self, [Text, Image]) - LrsAttributes.__init__(self, self.defaults, **settings) - self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1) - self.xsize, self.ysize = int(xsize), int(ysize) - self.setRefstream(refstream) - self.blockStyle = blockStyle - self.alttext = alttext - - def setRefstream(self, refstream): - self.refstream = refstream - - def appendReferencedObjects(self, parent): - if self.refstream.parent is None: - parent.append(self.refstream) - - if self.blockStyle is not None and self.blockStyle.parent is None: - parent.append(self.blockStyle) - - - def getReferencedObjIds(self): - objects = [self.objId, self.extraId, self.refstream.objId] - if self.blockStyle is not None: - objects.append(self.blockStyle.objId) - - return objects - - - def toLrf(self, lrfWriter): - self.toLrfContainer(lrfWriter, lrfWriter) - - - def toLrfContainer(self, lrfWriter, container): - # id really belongs to the outer block - - extraId = LrsObject.getNextObjId() - - b = LrfObject("Block", self.objId) - if self.blockStyle is not None: - b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) - b.appendTagDict(self.attrs) - - b.appendLrfTags( - LrfTagStream(0, - [LrfTag("Link", extraId)]).getStreamTags(lrfWriter.getSourceEncoding())) - container.addLrfObject(b.objId) - lrfWriter.append(b) - - ib = LrfObject("Image", extraId) - - ib.appendLrfTag(LrfTag("ImageRect", - (self.x0, self.y0, self.x1, self.y1))) - ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize))) - ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId)) - if self.alttext: - ib.appendLrfTag("Comment", self.alttext) - - - lrfWriter.append(ib) - self.extraId = extraId - - - def toElement(self, se): - element = self.lrsObjectElement("ImageBlock", **self.attrs) - element.set("refstream", str(self.refstream.objId)) - for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]: - element.set(name, str(getattr(self, name))) - element.text = self.alttext - return element - - - -class Font(LrsContainer): - """ Allows a TrueType file to be embedded in an Lrf. """ - def __init__(self, file=None, fontname=None, fontfilename=None, encoding=None): - LrsContainer.__init__(self, []) - try: - _checkExists(fontfilename) - self.truefile = fontfilename - except: - try: - _checkExists(file) - self.truefile = file - except: - raise LrsError, "neither '%s' nor '%s' exists"%(fontfilename, file) - - self.file = file - self.fontname = fontname - self.fontfilename = fontfilename - self.encoding = encoding - - - def toLrf(self, lrfWriter): - font = LrfObject("Font", LrsObject.getNextObjId()) - lrfWriter.registerFontId(font.objId) - font.appendLrfTag(LrfTag("FontFilename", - lrfWriter.toUnicode(self.truefile))) - font.appendLrfTag(LrfTag("FontFacename", - lrfWriter.toUnicode(self.fontname))) - - stream = LrfFileStream(STREAM_FORCE_COMPRESSED, self.truefile) - font.appendLrfTags(stream.getStreamTags()) - - lrfWriter.append(font) - - - def toElement(self, se): - element = Element("RegistFont", encoding="TTF", fontname=self.fontname, - file=self.file, fontfilename=self.file) - return element +# Copyright (c) 2007 Mike Higgins (Falstaff) +# Modifications from the original: +# Copyright (C) 2007 Kovid Goyal +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# Current limitations and bugs: +# Bug: Does not check if most setting values are valid unless lrf is created. +# +# Unsupported objects: MiniPage, SimpleTextBlock, Canvas, Window, +# PopUpWindow, Sound, Import, SoundStream, +# ObjectInfo +# +# Does not support background images for blocks or pages. +# +# The only button type supported are JumpButtons. +# +# None of the Japanese language tags are supported. +# +# Other unsupported tags: PageDiv, SoundStop, Wait, pos, +# Plot, Image (outside of ImageBlock), +# EmpLine, EmpDots + +import os, re, codecs, operator +from xml.sax.saxutils import escape +from datetime import date +try: + from elementtree.ElementTree import (Element, SubElement) + Element, SubElement +except ImportError: + from xml.etree.ElementTree import (Element, SubElement) + +from elements import ElementWriter +from pylrf import (LrfWriter, LrfObject, LrfTag, LrfToc, + STREAM_COMPRESSED, LrfTagStream, LrfStreamBase, IMAGE_TYPE_ENCODING, + BINDING_DIRECTION_ENCODING, LINE_TYPE_ENCODING, LrfFileStream, + STREAM_FORCE_COMPRESSED) + +DEFAULT_SOURCE_ENCODING = "cp1252" # defualt is us-windows character set +DEFAULT_GENREADING = "fs" # default is yes to both lrf and lrs + +from calibre import __appname__, __version__ +from calibre import entity_to_unicode + +class LrsError(Exception): + pass + +class ContentError(Exception): + pass + +def _checkExists(filename): + if not os.path.exists(filename): + raise LrsError, "file '%s' not found" % filename + + +def _formatXml(root): + """ A helper to make the LRS output look nicer. """ + for elem in root.getiterator(): + if len(elem) > 0 and (not elem.text or not elem.text.strip()): + elem.text = "\n" + if not elem.tail or not elem.tail.strip(): + elem.tail = "\n" + + + +def ElementWithText(tag, text, **extra): + """ A shorthand function to create Elements with text. """ + e = Element(tag, **extra) + e.text = text + return e + + + +def ElementWithReading(tag, text, reading=False): + """ A helper function that creates reading attributes. """ + + # note: old lrs2lrf parser only allows reading = "" + + if text is None: + readingText = "" + elif isinstance(text, basestring): + readingText = text + else: + # assumed to be a sequence of (name, sortas) + readingText = text[1] + text = text[0] + + + if not reading: + readingText = "" + return ElementWithText(tag, text, reading=readingText) + + + +def appendTextElements(e, contentsList, se): + """ A helper function to convert text streams into the proper elements. """ + + def uconcat(text, newText, se): + if type(newText) != type(text): + if type(text) is str: + text = text.decode(se) + else: + newText = newText.decode(se) + + return text + newText + + + e.text = "" + lastElement = None + + for content in contentsList: + if not isinstance(content, Text): + newElement = content.toElement(se) + if newElement is None: + continue + lastElement = newElement + lastElement.tail = "" + e.append(lastElement) + else: + if lastElement is None: + e.text = uconcat(e.text, content.text, se) + else: + lastElement.tail = uconcat(lastElement.tail, content.text, se) + + + +class Delegator(object): + """ A mixin class to create delegated methods that create elements. """ + def __init__(self, delegates): + self.delegates = delegates + self.delegatedMethods = [] + #self.delegatedSettingsDict = {} + #self.delegatedSettings = [] + for d in delegates: + d.parent = self + methods = d.getMethods() + self.delegatedMethods += methods + for m in methods: + setattr(self, m, getattr(d, m)) + + """ + for setting in d.getSettings(): + if isinstance(setting, basestring): + setting = (d, setting) + delegates = \ + self.delegatedSettingsDict.setdefault(setting[1], []) + delegates.append(setting[0]) + self.delegatedSettings.append(setting) + """ + + + def applySetting(self, name, value, testValid=False): + applied = False + if name in self.getSettings(): + setattr(self, name, value) + applied = True + + for d in self.delegates: + if hasattr(d, "applySetting"): + applied = applied or d.applySetting(name, value) + else: + if name in d.getSettings(): + setattr(d, name, value) + applied = True + + if testValid and not applied: + raise LrsError, "setting %s not valid" % name + + return applied + + + def applySettings(self, settings, testValid=False): + for (setting, value) in settings.items(): + self.applySetting(setting, value, testValid) + """ + if setting not in self.delegatedSettingsDict: + raise LrsError, "setting %s not valid" % setting + delegates = self.delegatedSettingsDict[setting] + for d in delegates: + setattr(d, setting, value) + """ + + + def appendDelegates(self, element, sourceEncoding): + for d in self.delegates: + e = d.toElement(sourceEncoding) + if e is not None: + if isinstance(e, list): + for e1 in e: element.append(e1) + else: + element.append(e) + + + def appendReferencedObjects(self, parent): + for d in self.delegates: + d.appendReferencedObjects(parent) + + + def getMethods(self): + return self.delegatedMethods + + + def getSettings(self): + return [] + + + def toLrfDelegates(self, lrfWriter): + for d in self.delegates: + d.toLrf(lrfWriter) + + + def toLrf(self, lrfWriter): + self.toLrfDelegates(lrfWriter) + + + +class LrsAttributes(object): + """ A mixin class to handle default and user supplied attributes. """ + def __init__(self, defaults, alsoAllow=None, **settings): + if alsoAllow is None: + alsoAllow = [] + self.attrs = defaults.copy() + for (name, value) in settings.items(): + if name not in self.attrs and name not in alsoAllow: + raise LrsError, "%s does not support setting %s" % \ + (self.__class__.__name__, name) + if type(value) is int: + value = str(value) + self.attrs[name] = value + + + +class LrsContainer(object): + """ This class is a mixin class for elements that are contained in or + contain an unknown number of other elements. + """ + def __init__(self, validChildren): + self.parent = None + self.contents = [] + self.validChildren = validChildren + self.must_append = False #: If True even an empty container is appended by append_to + + def has_text(self): + ''' Return True iff this container has non whitespace text ''' + if hasattr(self, 'text'): + if self.text.strip(): + return True + if hasattr(self, 'contents'): + for child in self.contents: + if child.has_text(): + return True + for item in self.contents: + if isinstance(item, (Plot, ImageBlock, Canvas, CR)): + return True + return False + + def append_to(self, parent): + ''' + Append self to C{parent} iff self has non whitespace textual content + @type parent: LrsContainer + ''' + if self.contents or self.must_append: + parent.append(self) + + + def appendReferencedObjects(self, parent): + for c in self.contents: + c.appendReferencedObjects(parent) + + + def setParent(self, parent): + if self.parent is not None: + raise LrsError, "object already has parent" + + self.parent = parent + + + def append(self, content, convertText=True): + """ + Appends valid objects to container. Can auto-covert text strings + to Text objects. + """ + for validChild in self.validChildren: + if isinstance(content, validChild): + break + else: + raise LrsError, "can't append %s to %s" % \ + (content.__class__.__name__, + self.__class__.__name__) + + if convertText and isinstance(content, basestring): + content = Text(content) + + content.setParent(self) + + if isinstance(content, LrsObject): + content.assignId() + + self.contents.append(content) + return self + + def get_all(self, predicate=lambda x: x): + for child in self.contents: + if predicate(child): + yield child + if hasattr(child, 'get_all'): + for grandchild in child.get_all(predicate): + yield grandchild + + + +class LrsObject(object): + """ A mixin class for elements that need an object id. """ + nextObjId = 0 + + @classmethod + def getNextObjId(selfClass): + selfClass.nextObjId += 1 + return selfClass.nextObjId + + def __init__(self, assignId=False): + if assignId: + self.objId = LrsObject.getNextObjId() + else: + self.objId = 0 + + + def assignId(self): + if self.objId != 0: + raise LrsError, "id already assigned to " + self.__class__.__name__ + + self.objId = LrsObject.getNextObjId() + + + def lrsObjectElement(self, name, objlabel="objlabel", labelName=None, + labelDecorate=True, **settings): + element = Element(name) + element.attrib["objid"] = str(self.objId) + if labelName is None: + labelName = name + if labelDecorate: + label = "%s.%d" % (labelName, self.objId) + else: + label = str(self.objId) + element.attrib[objlabel] = label + element.attrib.update(settings) + return element + + + +class Book(Delegator): + """ + Main class for any lrs or lrf. All objects must be appended to + the Book class in some way or another in order to be rendered as + an LRS or LRF file. + + The following settings are available on the contructor of Book: + + author="book author" or author=("book author", "sort as") + Author of the book. + + title="book title" or title=("book title", "sort as") + Title of the book. + + sourceencoding="codec" + Gives the assumed encoding for all non-unicode strings. + + + thumbnail="thumbnail file name" + A small (80x80?) graphics file with a thumbnail of the book's cover. + + bookid="book id" + A unique id for the book. + + textstyledefault= + Sets the default values for all TextStyles. + + pagetstyledefault= + Sets the default values for all PageStyles. + + blockstyledefault= + Sets the default values for all BlockStyles. + + booksetting=BookSetting() + Override the default BookSetting. + + setdefault=StyleDefault() + Override the default SetDefault. + + There are several other settings -- see the BookInfo class for more. + """ + + def __init__(self, textstyledefault=None, blockstyledefault=None, + pagestyledefault=None, + optimizeTags=False, + optimizeCompression=False, + **settings): + + self.parent = None # we are the top of the parent chain + + if "thumbnail" in settings: + _checkExists(settings["thumbnail"]) + + # highly experimental -- use with caution + self.optimizeTags = optimizeTags + self.optimizeCompression = optimizeCompression + + pageStyle = PageStyle(**PageStyle.baseDefaults.copy()) + blockStyle = BlockStyle(**BlockStyle.baseDefaults.copy()) + textStyle = TextStyle(**TextStyle.baseDefaults.copy()) + + if textstyledefault is not None: + textStyle.update(textstyledefault) + + if blockstyledefault is not None: + blockStyle.update(blockstyledefault) + + if pagestyledefault is not None: + pageStyle.update(pagestyledefault) + + self.defaultPageStyle = pageStyle + self.defaultTextStyle = textStyle + self.defaultBlockStyle = blockStyle + LrsObject.nextObjId += 1 + + styledefault = StyleDefault() + if settings.has_key('setdefault'): + styledefault = settings.pop('setdefault') + Delegator.__init__(self, [BookInformation(), Main(), + Template(), Style(styledefault), Solos(), Objects()]) + + self.sourceencoding = None + + # apply default settings + self.applySetting("genreading", DEFAULT_GENREADING) + self.applySetting("sourceencoding", DEFAULT_SOURCE_ENCODING) + + self.applySettings(settings, testValid=True) + + self.allow_new_page = True #: If False L{create_page} raises an exception + self.gc_count = 0 + + + def set_title(self, title): + ot = self.delegates[0].delegates[0].delegates[0].title + self.delegates[0].delegates[0].delegates[0].title = (title, ot[1]) + + def set_author(self, author): + ot = self.delegates[0].delegates[0].delegates[0].author + self.delegates[0].delegates[0].delegates[0].author = (author, ot[1]) + + def create_text_style(self, **settings): + ans = TextStyle(**self.defaultTextStyle.attrs.copy()) + ans.update(settings) + return ans + + def create_block_style(self, **settings): + ans = BlockStyle(**self.defaultBlockStyle.attrs.copy()) + ans.update(settings) + return ans + + def create_page_style(self, **settings): + if not self.allow_new_page: + raise ContentError + ans = PageStyle(**self.defaultPageStyle.attrs.copy()) + ans.update(settings) + return ans + + def create_page(self, pageStyle=None, **settings): + ''' + Return a new L{Page}. The page has not been appended to this book. + @param pageStyle: If None the default pagestyle is used. + @type pageStyle: L{PageStyle} + ''' + if not pageStyle: + pageStyle = self.defaultPageStyle + return Page(pageStyle=pageStyle, **settings) + + def create_text_block(self, textStyle=None, blockStyle=None, **settings): + ''' + Return a new L{TextBlock}. The block has not been appended to this + book. + @param textStyle: If None the default text style is used + @type textStyle: L{TextStyle} + @param blockStyle: If None the default block style is used. + @type blockStyle: L{BlockStyle} + ''' + if not textStyle: + textStyle = self.defaultTextStyle + if not blockStyle: + blockStyle = self.defaultBlockStyle + return TextBlock(textStyle=textStyle, blockStyle=blockStyle, **settings) + + def pages(self): + '''Return list of Page objects in this book ''' + ans = [] + for item in self.delegates: + if isinstance(item, Main): + for candidate in item.contents: + if isinstance(candidate, Page): + ans.append(candidate) + break + return ans + + def last_page(self): + '''Return last Page in this book ''' + for item in self.delegates: + if isinstance(item, Main): + temp = list(item.contents) + temp.reverse() + for candidate in temp: + if isinstance(candidate, Page): + return candidate + + def embed_font(self, file, facename): + f = Font(file, facename) + self.append(f) + + def getSettings(self): + return ["sourceencoding"] + + + def append(self, content): + """ Find and invoke the correct appender for this content. """ + + className = content.__class__.__name__ + try: + method = getattr(self, "append" + className) + except AttributeError: + raise LrsError, "can't append %s to Book" % className + + method(content) + + + def rationalize_font_sizes(self, base_font_size=10): + base_font_size *= 10. + main = None + for obj in self.delegates: + if isinstance(obj, Main): + main = obj + break + + fonts = {} + for text in main.get_all(lambda x: isinstance(x, Text)): + fs = base_font_size + ancestor = text.parent + while ancestor: + try: + fs = int(ancestor.attrs['fontsize']) + break + except (AttributeError, KeyError): + pass + try: + fs = int(ancestor.textSettings['fontsize']) + break + except (AttributeError, KeyError): + pass + try: + fs = int(ancestor.textStyle.attrs['fontsize']) + break + except (AttributeError, KeyError): + pass + ancestor = ancestor.parent + length = len(text.text) + fonts[fs] = fonts.get(fs, 0) + length + if not fonts: + print 'WARNING: LRF seems to have no textual content. Cannot rationalize font sizes.' + return + + old_base_font_size = float(max(fonts.items(), key=operator.itemgetter(1))[0]) + factor = base_font_size / old_base_font_size + def rescale(old): + return str(int(int(old) * factor)) + + text_blocks = list(main.get_all(lambda x: isinstance(x, TextBlock))) + for tb in text_blocks: + if tb.textSettings.has_key('fontsize'): + tb.textSettings['fontsize'] = rescale(tb.textSettings['fontsize']) + for span in tb.get_all(lambda x: isinstance(x, Span)): + if span.attrs.has_key('fontsize'): + span.attrs['fontsize'] = rescale(span.attrs['fontsize']) + if span.attrs.has_key('baselineskip'): + span.attrs['baselineskip'] = rescale(span.attrs['baselineskip']) + + text_styles = set(tb.textStyle for tb in text_blocks) + for ts in text_styles: + ts.attrs['fontsize'] = rescale(ts.attrs['fontsize']) + ts.attrs['baselineskip'] = rescale(ts.attrs['baselineskip']) + + + def renderLrs(self, lrsFile, encoding="UTF-8"): + if isinstance(lrsFile, basestring): + lrsFile = codecs.open(lrsFile, "wb", encoding=encoding) + self.render(lrsFile, outputEncodingName=encoding) + lrsFile.close() + + + def renderLrf(self, lrfFile): + self.appendReferencedObjects(self) + if isinstance(lrfFile, basestring): + lrfFile = file(lrfFile, "wb") + lrfWriter = LrfWriter(self.sourceencoding) + + lrfWriter.optimizeTags = self.optimizeTags + lrfWriter.optimizeCompression = self.optimizeCompression + + self.toLrf(lrfWriter) + lrfWriter.writeFile(lrfFile) + lrfFile.close() + + + def toElement(self, se): + root = Element("BBeBXylog", version="1.0") + root.append(Element("Property")) + self.appendDelegates(root, self.sourceencoding) + return root + + + def render(self, f, outputEncodingName='UTF-8'): + """ Write the book as an LRS to file f. """ + + self.appendReferencedObjects(self) + + # create the root node, and populate with the parts of the book + + root = self.toElement(self.sourceencoding) + + # now, add some newlines to make it easier to look at + + _formatXml(root) + + writer = ElementWriter(root, header=True, + sourceEncoding=self.sourceencoding, + spaceBeforeClose=False, + outputEncodingName=outputEncodingName) + writer.write(f) + + + +class BookInformation(Delegator): + """ Just a container for the Info and TableOfContents elements. """ + def __init__(self): + Delegator.__init__(self, [Info(), TableOfContents()]) + + + def toElement(self, se): + bi = Element("BookInformation") + self.appendDelegates(bi, se) + return bi + + + +class Info(Delegator): + """ Just a container for the BookInfo and DocInfo elements. """ + def __init__(self): + self.genreading = DEFAULT_GENREADING + Delegator.__init__(self, [BookInfo(), DocInfo()]) + + + def getSettings(self): + return ["genreading"] #+ self.delegatedSettings + + + def toElement(self, se): + info = Element("Info", version="1.1") + info.append( + self.delegates[0].toElement(se, reading="s" in self.genreading)) + info.append(self.delegates[1].toElement(se)) + return info + + + def toLrf(self, lrfWriter): + # this info is set in XML form in the LRF + info = Element("Info", version="1.1") + #self.appendDelegates(info) + info.append( + self.delegates[0].toElement(lrfWriter.getSourceEncoding(), reading="f" in self.genreading)) + info.append(self.delegates[1].toElement(lrfWriter.getSourceEncoding())) + + # look for the thumbnail file and get the filename + tnail = info.find("DocInfo/CThumbnail") + if tnail is not None: + lrfWriter.setThumbnailFile(tnail.get("file")) + # does not work: info.remove(tnail) + + + _formatXml(info) + + # fix up the doc info to match the LRF format + # NB: generates an encoding attribute, which lrs2lrf does not + xmlInfo = ElementWriter(info, header=True, sourceEncoding=lrfWriter.getSourceEncoding(), + spaceBeforeClose=False).toString() + + xmlInfo = re.sub(r"\n", "", xmlInfo) + xmlInfo = xmlInfo.replace("SumPage>", "Page>") + lrfWriter.docInfoXml = xmlInfo + + + +class TableOfContents(object): + def __init__(self): + self.tocEntries = [] + + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return ["addTocEntry"] + + + def getSettings(self): + return [] + + + def addTocEntry(self, tocLabel, textBlock): + if not isinstance(textBlock, (Canvas, TextBlock, ImageBlock, RuledLine)): + raise LrsError, "TOC destination must be a Canvas, TextBlock, ImageBlock or RuledLine"+\ + " not a " + str(type(textBlock)) + + if textBlock.parent is None: + raise LrsError, "TOC text block must be already appended to a page" + + if False and textBlock.parent.parent is None: + raise LrsError, \ + "TOC destination page must be already appended to a book" + + if not hasattr(textBlock.parent, 'objId'): + raise LrsError, "TOC destination must be appended to a container with an objID" + + for tl in self.tocEntries: + if tl.label == tocLabel and tl.textBlock == textBlock: + return + + self.tocEntries.append(TocLabel(tocLabel, textBlock)) + textBlock.tocLabel = tocLabel + + + def toElement(self, se): + if len(self.tocEntries) == 0: + return None + + toc = Element("TOC") + + for t in self.tocEntries: + toc.append(t.toElement(se)) + + return toc + + + def toLrf(self, lrfWriter): + if len(self.tocEntries) == 0: + return + + toc = [] + for t in self.tocEntries: + toc.append((t.textBlock.parent.objId, t.textBlock.objId, t.label)) + + lrfToc = LrfToc(LrsObject.getNextObjId(), toc, lrfWriter.getSourceEncoding()) + lrfWriter.append(lrfToc) + lrfWriter.setTocObject(lrfToc) + + + +class TocLabel(object): + def __init__(self, label, textBlock): + self.label = escape(re.sub(r'&(\S+?);', entity_to_unicode, label)) + self.textBlock = textBlock + + + def toElement(self, se): + return ElementWithText("TocLabel", self.label, + refobj=str(self.textBlock.objId), + refpage=str(self.textBlock.parent.objId)) + + + +class BookInfo(object): + def __init__(self): + self.title = "Untitled" + self.author = "Anonymous" + self.bookid = None + self.pi = None + self.isbn = None + self.publisher = None + self.freetext = "\n\n" + self.label = None + self.category = None + self.classification = None + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return ["author", "title", "bookid", "isbn", "publisher", + "freetext", "label", "category", "classification"] + + + def _appendISBN(self, bi): + pi = Element("ProductIdentifier") + isbnElement = ElementWithText("ISBNPrintable", self.isbn) + isbnValueElement = ElementWithText("ISBNValue", + self.isbn.replace("-", "")) + + pi.append(isbnElement) + pi.append(isbnValueElement) + bi.append(pi) + + + def toElement(self, se, reading=True): + bi = Element("BookInfo") + bi.append(ElementWithReading("Title", self.title, reading=reading)) + bi.append(ElementWithReading("Author", self.author, reading=reading)) + bi.append(ElementWithText("BookID", self.bookid)) + if self.isbn is not None: + self._appendISBN(bi) + + if self.publisher is not None: + bi.append(ElementWithReading("Publisher", self.publisher)) + + bi.append(ElementWithReading("Label", self.label, reading=reading)) + bi.append(ElementWithText("Category", self.category)) + bi.append(ElementWithText("Classification", self.classification)) + bi.append(ElementWithText("FreeText", self.freetext)) + return bi + + + +class DocInfo(object): + def __init__(self): + self.thumbnail = None + self.language = "en" + self.creator = None + self.creationdate = date.today().isoformat() + self.producer = "%s v%s"%(__appname__, __version__) + self.numberofpages = "0" + + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return ["thumbnail", "language", "creator", "creationdate", + "producer", "numberofpages"] + + + def toElement(self, se): + docInfo = Element("DocInfo") + + if self.thumbnail is not None: + docInfo.append(Element("CThumbnail", file=self.thumbnail)) + + docInfo.append(ElementWithText("Language", self.language)) + docInfo.append(ElementWithText("Creator", self.creator)) + docInfo.append(ElementWithText("CreationDate", self.creationdate)) + docInfo.append(ElementWithText("Producer", self.producer)) + docInfo.append(ElementWithText("SumPage", str(self.numberofpages))) + return docInfo + + + +class Main(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [Page]) + + + def getMethods(self): + return ["appendPage", "Page"] + + + def getSettings(self): + return [] + + + def Page(self, *args, **kwargs): + p = Page(*args, **kwargs) + self.append(p) + return p + + + def appendPage(self, page): + self.append(page) + + + def toElement(self, sourceEncoding): + main = Element(self.__class__.__name__) + + for page in self.contents: + main.append(page.toElement(sourceEncoding)) + + return main + + + def toLrf(self, lrfWriter): + pageIds = [] + + # set this id now so that pages can see it + pageTreeId = LrsObject.getNextObjId() + lrfWriter.setPageTreeId(pageTreeId) + + # create a list of all the page object ids while dumping the pages + + for p in self.contents: + pageIds.append(p.objId) + p.toLrf(lrfWriter) + + # create a page tree object + + pageTree = LrfObject("PageTree", pageTreeId) + pageTree.appendLrfTag(LrfTag("PageList", pageIds)) + + lrfWriter.append(pageTree) + + + +class Solos(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [Solo]) + + + def getMethods(self): + return ["appendSolo", "Solo"] + + + def getSettings(self): + return [] + + + def Solo(self, *args, **kwargs): + p = Solo(*args, **kwargs) + self.append(p) + return p + + + def appendSolo(self, solo): + self.append(solo) + + + def toLrf(self, lrfWriter): + for s in self.contents: + s.toLrf(lrfWriter) + + + def toElement(self, se): + solos = [] + for s in self.contents: + solos.append(s.toElement(se)) + + if len(solos) == 0: + return None + + + return solos + + + +class Solo(Main): + pass + + +class Template(object): + """ Does nothing that I know of. """ + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return [] + + + def toElement(self, se): + t = Element("Template") + t.attrib["version"] = "1.0" + return t + + def toLrf(self, lrfWriter): + # does nothing + pass + +class StyleDefault(LrsAttributes): + """ + Supply some defaults for all TextBlocks. + The legal values are a subset of what is allowed on a + TextBlock -- ruby, emphasis, and waitprop settings. + """ + defaults = dict(rubyalign="start", rubyadjust="none", + rubyoverhang="none", empdotsposition="before", + empdotsfontname="Dutch801 Rm BT Roman", + empdotscode="0x002e", emplineposition="after", + emplinetype = "solid", setwaitprop="noreplay") + + alsoAllow = ["refempdotsfont", "rubyAlignAndAdjust"] + + def __init__(self, **settings): + LrsAttributes.__init__(self, self.defaults, + alsoAllow=self.alsoAllow, **settings) + + + def toElement(self, se): + return Element("SetDefault", self.attrs) + + +class Style(LrsContainer, Delegator): + def __init__(self, styledefault=StyleDefault()): + LrsContainer.__init__(self, [PageStyle, TextStyle, BlockStyle]) + Delegator.__init__(self, [BookStyle(styledefault=styledefault)]) + self.bookStyle = self.delegates[0] + self.appendPageStyle = self.appendTextStyle = \ + self.appendBlockStyle = self.append + + + def appendReferencedObjects(self, parent): + LrsContainer.appendReferencedObjects(self, parent) + + + def getMethods(self): + return ["PageStyle", "TextStyle", "BlockStyle", + "appendPageStyle", "appendTextStyle", "appendBlockStyle"] + \ + self.delegatedMethods + + def getSettings(self): + return [(self.bookStyle, x) for x in self.bookStyle.getSettings()] + + + def PageStyle(self, *args, **kwargs): + ps = PageStyle(*args, **kwargs) + self.append(ps) + return ps + + + def TextStyle(self, *args, **kwargs): + ts = TextStyle(*args, **kwargs) + self.append(ts) + return ts + + + def BlockStyle(self, *args, **kwargs): + bs = BlockStyle(*args, **kwargs) + self.append(bs) + return bs + + + def toElement(self, se): + style = Element("Style") + style.append(self.bookStyle.toElement(se)) + + for content in self.contents: + style.append(content.toElement(se)) + + return style + + + def toLrf(self, lrfWriter): + self.bookStyle.toLrf(lrfWriter) + + for s in self.contents: + s.toLrf(lrfWriter) + + + +class BookStyle(LrsObject, LrsContainer): + def __init__(self, styledefault=StyleDefault()): + LrsObject.__init__(self, assignId=True) + LrsContainer.__init__(self, [Font]) + self.styledefault = styledefault + self.booksetting = BookSetting() + self.appendFont = self.append + + + def getSettings(self): + return ["styledefault", "booksetting"] + + + def getMethods(self): + return ["Font", "appendFont"] + + + def Font(self, *args, **kwargs): + f = Font(*args, **kwargs) + self.append(f) + return + + + def toElement(self, se): + bookStyle = self.lrsObjectElement("BookStyle", objlabel="stylelabel", + labelDecorate=False) + bookStyle.append(self.styledefault.toElement(se)) + bookStyle.append(self.booksetting.toElement(se)) + for font in self.contents: + bookStyle.append(font.toElement(se)) + + return bookStyle + + + def toLrf(self, lrfWriter): + bookAtr = LrfObject("BookAtr", self.objId) + bookAtr.appendLrfTag(LrfTag("ChildPageTree", lrfWriter.getPageTreeId())) + bookAtr.appendTagDict(self.styledefault.attrs) + + self.booksetting.toLrf(lrfWriter) + + lrfWriter.append(bookAtr) + lrfWriter.setRootObject(bookAtr) + + for font in self.contents: + font.toLrf(lrfWriter) + + + + + + +class BookSetting(LrsAttributes): + def __init__(self, **settings): + defaults = dict(bindingdirection="Lr", dpi="1660", + screenheight="800", screenwidth="600", colordepth="24") + LrsAttributes.__init__(self, defaults, **settings) + + + def toLrf(self, lrfWriter): + a = self.attrs + lrfWriter.dpi = int(a["dpi"]) + lrfWriter.bindingdirection = \ + BINDING_DIRECTION_ENCODING[a["bindingdirection"]] + lrfWriter.height = int(a["screenheight"]) + lrfWriter.width = int(a["screenwidth"]) + lrfWriter.colorDepth = int(a["colordepth"]) + + def toElement(self, se): + return Element("BookSetting", self.attrs) + + + +class LrsStyle(LrsObject, LrsAttributes, LrsContainer): + """ A mixin class for styles. """ + def __init__(self, elementName, defaults=None, alsoAllow=None, **overrides): + if defaults is None: + defaults = {} + + LrsObject.__init__(self) + LrsAttributes.__init__(self, defaults, alsoAllow=alsoAllow, **overrides) + LrsContainer.__init__(self, []) + self.elementName = elementName + self.objectsAppended = False + #self.label = "%s.%d" % (elementName, self.objId) + #self.label = str(self.objId) + #self.parent = None + + + def update(self, settings): + for name, value in settings.items(): + if name not in self.__class__.validSettings: + raise LrsError, "%s not a valid setting for %s" % \ + (name, self.__class__.__name__) + self.attrs[name] = value + + def getLabel(self): + return str(self.objId) + + + def toElement(self, se): + element = Element(self.elementName, stylelabel=self.getLabel(), + objid=str(self.objId)) + element.attrib.update(self.attrs) + return element + + + def toLrf(self, lrfWriter): + obj = LrfObject(self.elementName, self.objId) + obj.appendTagDict(self.attrs, self.__class__.__name__) + lrfWriter.append(obj) + + def __eq__(self, other): + if hasattr(other, 'attrs'): + return self.__class__ == other.__class__ and self.attrs == other.attrs + return False + +class TextStyle(LrsStyle): + """ + The text style of a TextBlock. Default is 10 pt. Times Roman. + + Setting Value Default + -------- ----- ------- + align "head","center","foot" "head" (left aligned) + baselineskip points * 10 120 (12 pt. distance between + bottoms of lines) + fontsize points * 10 100 (10 pt.) + fontweight 1 to 1000 400 (normal, 800 is bold) + fontwidth points * 10 or -10 -10 (use values from font) + linespace points * 10 10 (min space btw. lines?) + wordspace points * 10 25 (min space btw. each word) + + """ + baseDefaults = dict( + columnsep="0", charspace="0", + textlinewidth="2", align="head", linecolor="0x00000000", + column="1", fontsize="100", fontwidth="-10", fontescapement="0", + fontorientation="0", fontweight="400", + fontfacename="Dutch801 Rm BT Roman", + textcolor="0x00000000", wordspace="25", letterspace="0", + baselineskip="120", linespace="10", parindent="0", parskip="0", + textbgcolor="0xFF000000") + + alsoAllow = ["empdotscode", "empdotsfontname", "refempdotsfont", + "rubyadjust", "rubyalign", "rubyoverhang", + "empdotsposition", 'emplinetype', 'emplineposition'] + + validSettings = baseDefaults.keys() + alsoAllow + + defaults = baseDefaults.copy() + + def __init__(self, **overrides): + LrsStyle.__init__(self, "TextStyle", self.defaults, + alsoAllow=self.alsoAllow, **overrides) + + def copy(self): + tb = TextStyle() + tb.attrs = self.attrs.copy() + return tb + + + +class BlockStyle(LrsStyle): + """ + The block style of a TextBlock. Default is an expandable 560 pixel + wide area with no space for headers or footers. + + Setting Value Default + -------- ----- ------- + blockwidth pixels 560 + sidemargin pixels 0 + """ + + baseDefaults = dict( + bgimagemode="fix", framemode="square", blockwidth="560", + blockheight="100", blockrule="horz-adjustable", layout="LrTb", + framewidth="0", framecolor="0x00000000", topskip="0", + sidemargin="0", footskip="0", bgcolor="0xFF000000") + + validSettings = baseDefaults.keys() + defaults = baseDefaults.copy() + + def __init__(self, **overrides): + LrsStyle.__init__(self, "BlockStyle", self.defaults, **overrides) + + def copy(self): + tb = BlockStyle() + tb.attrs = self.attrs.copy() + return tb + + + +class PageStyle(LrsStyle): + """ + Setting Value Default + -------- ----- ------- + evensidemargin pixels 20 + oddsidemargin pixels 20 + topmargin pixels 20 + """ + baseDefaults = dict( + topmargin="20", headheight="0", headsep="0", + oddsidemargin="20", textheight="747", textwidth="575", + footspace="0", evensidemargin="20", footheight="0", + layout="LrTb", bgimagemode="fix", pageposition="any", + setwaitprop="noreplay", setemptyview="show") + + alsoAllow = ["header", "evenheader", "oddheader", + "footer", "evenfooter", "oddfooter"] + + validSettings = baseDefaults.keys() + alsoAllow + defaults = baseDefaults.copy() + + @classmethod + def translateHeaderAndFooter(selfClass, parent, settings): + selfClass._fixup(parent, "header", settings) + selfClass._fixup(parent, "footer", settings) + + + @classmethod + def _fixup(selfClass, parent, basename, settings): + evenbase = "even" + basename + oddbase = "odd" + basename + if basename in settings: + baseObj = settings[basename] + del settings[basename] + settings[evenbase] = settings[oddbase] = baseObj + + if evenbase in settings: + evenObj = settings[evenbase] + del settings[evenbase] + if evenObj.parent is None: + parent.append(evenObj) + settings[evenbase + "id"] = str(evenObj.objId) + + if oddbase in settings: + oddObj = settings[oddbase] + del settings[oddbase] + if oddObj.parent is None: + parent.append(oddObj) + settings[oddbase + "id"] = str(oddObj.objId) + + + def appendReferencedObjects(self, parent): + if self.objectsAppended: + return + PageStyle.translateHeaderAndFooter(parent, self.attrs) + self.objectsAppended = True + + + + def __init__(self, **settings): + #self.fixHeaderSettings(settings) + LrsStyle.__init__(self, "PageStyle", self.defaults, + alsoAllow=self.alsoAllow, **settings) + + +class Page(LrsObject, LrsContainer): + """ + Pages are added to Books. Pages can be supplied a PageStyle. + If they are not, Page.defaultPageStyle will be used. + """ + defaultPageStyle = PageStyle() + + def __init__(self, pageStyle=defaultPageStyle, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [TextBlock, BlockSpace, RuledLine, + ImageBlock, Canvas]) + + self.pageStyle = pageStyle + + for settingName in settings.keys(): + if settingName not in PageStyle.defaults and \ + settingName not in PageStyle.alsoAllow: + raise LrsError, "setting %s not allowed on Page" % settingName + + self.settings = settings.copy() + + + def appendReferencedObjects(self, parent): + PageStyle.translateHeaderAndFooter(parent, self.settings) + + self.pageStyle.appendReferencedObjects(parent) + + if self.pageStyle.parent is None: + parent.append(self.pageStyle) + + LrsContainer.appendReferencedObjects(self, parent) + + + def RuledLine(self, *args, **kwargs): + rl = RuledLine(*args, **kwargs) + self.append(rl) + return rl + + + def BlockSpace(self, *args, **kwargs): + bs = BlockSpace(*args, **kwargs) + self.append(bs) + return bs + + + def TextBlock(self, *args, **kwargs): + """ Create and append a new text block (shortcut). """ + tb = TextBlock(*args, **kwargs) + self.append(tb) + return tb + + + def ImageBlock(self, *args, **kwargs): + """ Create and append and new Image block (shorthand). """ + ib = ImageBlock(*args, **kwargs) + self.append(ib) + return ib + + + def addLrfObject(self, objId): + self.stream.appendLrfTag(LrfTag("Link", objId)) + + + def appendLrfTag(self, lrfTag): + self.stream.appendLrfTag(lrfTag) + + + def toLrf(self, lrfWriter): + # tags: + # ObjectList + # Link to pagestyle + # Parent page tree id + # stream of tags + + p = LrfObject("Page", self.objId) + lrfWriter.append(p) + + pageContent = set() + self.stream = LrfTagStream(0) + for content in self.contents: + content.toLrfContainer(lrfWriter, self) + if hasattr(content, "getReferencedObjIds"): + pageContent.update(content.getReferencedObjIds()) + + + #print "page contents:", pageContent + # ObjectList not needed and causes slowdown in SONY LRF renderer + #p.appendLrfTag(LrfTag("ObjectList", pageContent)) + p.appendLrfTag(LrfTag("Link", self.pageStyle.objId)) + p.appendLrfTag(LrfTag("ParentPageTree", lrfWriter.getPageTreeId())) + p.appendTagDict(self.settings) + p.appendLrfTags(self.stream.getStreamTags(lrfWriter.getSourceEncoding())) + + + def toElement(self, sourceEncoding): + page = self.lrsObjectElement("Page") + page.set("pagestyle", self.pageStyle.getLabel()) + page.attrib.update(self.settings) + + for content in self.contents: + page.append(content.toElement(sourceEncoding)) + + return page + + + + + +class TextBlock(LrsObject, LrsContainer): + """ + TextBlocks are added to Pages. They hold Paragraphs or CRs. + + If a TextBlock is used in a header, it should be appended to + the Book, not to a specific Page. + """ + defaultTextStyle = TextStyle() + defaultBlockStyle = BlockStyle() + + def __init__(self, textStyle=defaultTextStyle, \ + blockStyle=defaultBlockStyle, \ + **settings): + ''' + Create TextBlock. + @param textStyle: The L{TextStyle} for this block. + @param blockStyle: The L{BlockStyle} for this block. + @param settings: C{dict} of extra settings to apply to this block. + ''' + LrsObject.__init__(self) + LrsContainer.__init__(self, [Paragraph, CR]) + + self.textSettings = {} + self.blockSettings = {} + + for name, value in settings.items(): + if name in TextStyle.validSettings: + self.textSettings[name] = value + elif name in BlockStyle.validSettings: + self.blockSettings[name] = value + elif name == 'toclabel': + self.tocLabel = value + else: + raise LrsError, "%s not a valid setting for TextBlock" % name + + self.textStyle = textStyle + self.blockStyle = blockStyle + + # create a textStyle with our current text settings (for Span to find) + self.currentTextStyle = textStyle.copy() if self.textSettings else textStyle + self.currentTextStyle.attrs.update(self.textSettings) + + + def appendReferencedObjects(self, parent): + if self.textStyle.parent is None: + parent.append(self.textStyle) + + if self.blockStyle.parent is None: + parent.append(self.blockStyle) + + LrsContainer.appendReferencedObjects(self, parent) + + + def Paragraph(self, *args, **kwargs): + """ + Create and append a Paragraph to this TextBlock. A CR is + automatically inserted after the Paragraph. To avoid this + behavior, create the Paragraph and append it to the TextBlock + in a separate call. + """ + p = Paragraph(*args, **kwargs) + self.append(p) + self.append(CR()) + return p + + + + def toElement(self, sourceEncoding): + tb = self.lrsObjectElement("TextBlock", labelName="Block") + tb.attrib.update(self.textSettings) + tb.attrib.update(self.blockSettings) + tb.set("textstyle", self.textStyle.getLabel()) + tb.set("blockstyle", self.blockStyle.getLabel()) + if hasattr(self, "tocLabel"): + tb.set("toclabel", self.tocLabel) + + for content in self.contents: + tb.append(content.toElement(sourceEncoding)) + + return tb + + def getReferencedObjIds(self): + ids = [self.objId, self.extraId, self.blockStyle.objId, + self.textStyle.objId] + for content in self.contents: + if hasattr(content, "getReferencedObjIds"): + ids.extend(content.getReferencedObjIds()) + + return ids + + + def toLrf(self, lrfWriter): + self.toLrfContainer(lrfWriter, lrfWriter) + + + def toLrfContainer(self, lrfWriter, container): + # id really belongs to the outer block + extraId = LrsObject.getNextObjId() + + b = LrfObject("Block", self.objId) + b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) + b.appendLrfTags( + LrfTagStream(0, [LrfTag("Link", extraId)]). \ + getStreamTags(lrfWriter.getSourceEncoding())) + b.appendTagDict(self.blockSettings) + container.addLrfObject(b.objId) + lrfWriter.append(b) + + tb = LrfObject("TextBlock", extraId) + tb.appendLrfTag(LrfTag("Link", self.textStyle.objId)) + tb.appendTagDict(self.textSettings) + + stream = LrfTagStream(STREAM_COMPRESSED) + for content in self.contents: + content.toLrfContainer(lrfWriter, stream) + + if lrfWriter.saveStreamTags: # true only if testing + tb.saveStreamTags = stream.tags + + tb.appendLrfTags( + stream.getStreamTags(lrfWriter.getSourceEncoding(), + optimizeTags=lrfWriter.optimizeTags, + optimizeCompression=lrfWriter.optimizeCompression)) + lrfWriter.append(tb) + + self.extraId = extraId + + +class Paragraph(LrsContainer): + """ + Note:

alone does not make a paragraph. Only a CR inserted + into a text block right after a

makes a real paragraph. + Two Paragraphs appended in a row act like a single Paragraph. + + Also note that there are few autoappenders for Paragraph (and + the things that can go in it.) It's less confusing (to me) to use + explicit .append methods to build up the text stream. + """ + def __init__(self, text=None): + LrsContainer.__init__(self, [Text, CR, DropCaps, CharButton, + LrsSimpleChar1, basestring]) + if text is not None: + if isinstance(text, basestring): + text = Text(text) + self.append(text) + + def CR(self): + # Okay, here's a single autoappender for this common operation + cr = CR() + self.append(cr) + return cr + + + def getReferencedObjIds(self): + ids = [] + for content in self.contents: + if hasattr(content, "getReferencedObjIds"): + ids.extend(content.getReferencedObjIds()) + + return ids + + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag("pstart", 0)) + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + parent.appendLrfTag(LrfTag("pend")) + + + def toElement(self, sourceEncoding): + p = Element("P") + appendTextElements(p, self.contents, sourceEncoding) + return p + + + +class LrsTextTag(LrsContainer): + def __init__(self, text, validContents): + LrsContainer.__init__(self, [Text, basestring] + validContents) + if text is not None: + self.append(text) + + + def toLrfContainer(self, lrfWriter, parent): + if hasattr(self, "tagName"): + tagName = self.tagName + else: + tagName = self.__class__.__name__ + + parent.appendLrfTag(LrfTag(tagName)) + + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + + parent.appendLrfTag(LrfTag(tagName + "End")) + + + def toElement(self, se): + if hasattr(self, "tagName"): + tagName = self.tagName + else: + tagName = self.__class__.__name__ + + p = Element(tagName) + appendTextElements(p, self.contents, se) + return p + + +class LrsSimpleChar1(object): + def isEmpty(self): + for content in self.contents: + if not content.isEmpty(): + return False + return True + + def hasFollowingContent(self): + foundSelf = False + for content in self.parent.contents: + if content == self: + foundSelf = True + elif foundSelf: + if not content.isEmpty(): + return True + return False + + +class DropCaps(LrsTextTag): + + def __init__(self, line=1): + LrsTextTag.__init__(self, None, [LrsSimpleChar1]) + if int(line) <= 0: + raise LrsError('A DrawChar must span at least one line.') + self.line = int(line) + + def isEmpty(self): + return self.text == None or not self.text.strip() + + def toElement(self, se): + elem = Element('DrawChar', line=str(self.line)) + appendTextElements(elem, self.contents, se) + return elem + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag('DrawChar', (int(self.line),))) + + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + + parent.appendLrfTag(LrfTag("DrawCharEnd")) + + + +class Button(LrsObject, LrsContainer): + def __init__(self, **settings): + LrsObject.__init__(self, **settings) + LrsContainer.__init__(self, [PushButton]) + + def findJumpToRefs(self): + for sub1 in self.contents: + if isinstance(sub1, PushButton): + for sub2 in sub1.contents: + if isinstance(sub2, JumpTo): + return (sub2.textBlock.objId, sub2.textBlock.parent.objId) + raise LrsError, "%s has no PushButton or JumpTo subs"%self.__class__.__name__ + + def toLrf(self, lrfWriter): + (refobj, refpage) = self.findJumpToRefs() + # print "Button writing JumpTo refobj=", jumpto.refobj, ", and refpage=", jumpto.refpage + button = LrfObject("Button", self.objId) + button.appendLrfTag(LrfTag("buttonflags", 0x10)) # pushbutton + button.appendLrfTag(LrfTag("PushButtonStart")) + button.appendLrfTag(LrfTag("buttonactions")) + button.appendLrfTag(LrfTag("jumpto", (int(refpage), int(refobj)))) + button.append(LrfTag("endbuttonactions")) + button.appendLrfTag(LrfTag("PushButtonEnd")) + lrfWriter.append(button) + + def toElement(self, se): + b = self.lrsObjectElement("Button") + + for content in self.contents: + b.append(content.toElement(se)) + + return b + +class ButtonBlock(Button): + pass + +class PushButton(LrsContainer): + + def __init__(self, **settings): + LrsContainer.__init__(self, [JumpTo]) + + def toElement(self, se): + b = Element("PushButton") + + for content in self.contents: + b.append(content.toElement(se)) + + return b + +class JumpTo(LrsContainer): + + def __init__(self, textBlock): + LrsContainer.__init__(self, []) + self.textBlock=textBlock + + def setTextBlock(self, textBlock): + self.textBlock = textBlock + + def toElement(self, se): + return Element("JumpTo", refpage=str(self.textBlock.parent.objId), refobj=str(self.textBlock.objId)) + + + + + +class Plot(LrsSimpleChar1, LrsContainer): + + ADJUSTMENT_VALUES = {'center':1, 'baseline':2, 'top':3, 'bottom':4} + + def __init__(self, obj, xsize=0, ysize=0, adjustment=None): + LrsContainer.__init__(self, []) + if obj != None: + self.setObj(obj) + if xsize < 0 or ysize < 0: + raise LrsError('Sizes must be positive semi-definite') + self.xsize = int(xsize) + self.ysize = int(ysize) + if adjustment and adjustment not in Plot.ADJUSTMENT_VALUES.keys(): + raise LrsError('adjustment must be one of' + Plot.ADJUSTMENT_VALUES.keys()) + self.adjustment = adjustment + + def setObj(self, obj): + if not isinstance(obj, (Image, Button)): + raise LrsError('Plot elements can only refer to Image or Button elements') + self.obj = obj + + def getReferencedObjIds(self): + return [self.obj.objId] + + def appendReferencedObjects(self, parent): + if self.obj.parent is None: + parent.append(self.obj) + + def toElement(self, se): + elem = Element('Plot', xsize=str(self.xsize), ysize=str(self.ysize), \ + refobj=str(self.obj.objId)) + if self.adjustment: + elem.set('adjustment', self.adjustment) + return elem + + def toLrfContainer(self, lrfWriter, parent): + adj = self.adjustment if self.adjustment else 'bottom' + params = (int(self.xsize), int(self.ysize), int(self.obj.objId), \ + Plot.ADJUSTMENT_VALUES[adj]) + parent.appendLrfTag(LrfTag("Plot", params)) + +class Text(LrsContainer): + """ A object that represents raw text. Does not have a toElement. """ + def __init__(self, text): + LrsContainer.__init__(self, []) + self.text = text + + def isEmpty(self): + return not self.text or not self.text.strip() + + def toLrfContainer(self, lrfWriter, parent): + if self.text: + if isinstance(self.text, str): + parent.appendLrfTag(LrfTag("rawtext", self.text)) + else: + parent.appendLrfTag(LrfTag("textstring", self.text)) + + +class CR(LrsSimpleChar1, LrsContainer): + """ + A line break (when appended to a Paragraph) or a paragraph break + (when appended to a TextBlock). + """ + def __init__(self): + LrsContainer.__init__(self, []) + + + def toElement(self, se): + return Element("CR") + + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag("CR")) + + + +class Italic(LrsSimpleChar1, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, [LrsSimpleChar1]) + +class Sub(LrsSimpleChar1, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, []) + + + +class Sup(LrsSimpleChar1, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, []) + + + +class NoBR(LrsSimpleChar1, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, [LrsSimpleChar1]) + + +class Space(LrsSimpleChar1, LrsContainer): + def __init__(self, xsize=0, x=0): + LrsContainer.__init__(self, []) + if xsize == 0 and x != 0: xsize = x + self.xsize = xsize + + + def toElement(self, se): + if self.xsize == 0: + return + + return Element("Space", xsize=str(self.xsize)) + + + def toLrfContainer(self, lrfWriter, container): + if self.xsize != 0: + container.appendLrfTag(LrfTag("Space", self.xsize)) + + +class Box(LrsSimpleChar1, LrsContainer): + """ + Draw a box around text. Unfortunately, does not seem to do + anything on the PRS-500. + """ + def __init__(self, linetype="solid"): + LrsContainer.__init__(self, [Text, basestring]) + if linetype not in LINE_TYPE_ENCODING: + raise LrsError, linetype + " is not a valid line type" + self.linetype = linetype + + + def toElement(self, se): + e = Element("Box", linetype=self.linetype) + appendTextElements(e, self.contents, se) + return e + + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("Box", self.linetype)) + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + container.appendLrfTag(LrfTag("BoxEnd")) + + + + +class Span(LrsSimpleChar1, LrsContainer): + def __init__(self, text=None, **attrs): + LrsContainer.__init__(self, [LrsSimpleChar1, Text, basestring]) + if text is not None: + if isinstance(text, basestring): + text = Text(text) + self.append(text) + + for attrname in attrs.keys(): + if attrname not in TextStyle.defaults and \ + attrname not in TextStyle.alsoAllow: + raise LrsError, "setting %s not allowed on Span" % attrname + self.attrs = attrs + + + def findCurrentTextStyle(self): + parent = self.parent + while 1: + if parent is None or hasattr(parent, "currentTextStyle"): + break + parent = parent.parent + + if parent is None: + raise LrsError, "no enclosing current TextStyle found" + + return parent.currentTextStyle + + + + def toLrfContainer(self, lrfWriter, container): + + # find the currentTextStyle + oldTextStyle = self.findCurrentTextStyle() + + # set the attributes we want changed + for (name, value) in self.attrs.items(): + if name in oldTextStyle.attrs and oldTextStyle.attrs[name] == self.attrs[name]: + self.attrs.pop(name) + else: + container.appendLrfTag(LrfTag(name, value)) + + # set a currentTextStyle so nested span can put things back + oldTextStyle = self.findCurrentTextStyle() + self.currentTextStyle = oldTextStyle.copy() + self.currentTextStyle.attrs.update(self.attrs) + + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + + # put the attributes back the way we found them + # the attributes persist beyond the next

+ # if self.hasFollowingContent(): + for name in self.attrs.keys(): + container.appendLrfTag(LrfTag(name, oldTextStyle.attrs[name])) + + + def toElement(self, se): + element = Element('Span') + for (key, value) in self.attrs.items(): + element.set(key, str(value)) + + appendTextElements(element, self.contents, se) + return element + +class EmpLine(LrsTextTag, LrsSimpleChar1): + emplinetypes = ['none', 'solid', 'dotted', 'dashed', 'double'] + emplinepositions = ['before', 'after'] + + def __init__(self, text=None, emplineposition='before', emplinetype='solid'): + LrsTextTag.__init__(self, text, [LrsSimpleChar1]) + if emplineposition not in self.__class__.emplinepositions: + raise LrsError('emplineposition for an EmpLine must be one of: '+str(self.__class__.emplinepositions)) + if emplinetype not in self.__class__.emplinetypes: + raise LrsError('emplinetype for an EmpLine must be one of: '+str(self.__class__.emplinetypes)) + + self.emplinetype = emplinetype + self.emplineposition = emplineposition + + + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag(self.__class__.__name__, (self.emplineposition, self.emplinetype))) + parent.appendLrfTag(LrfTag('emplineposition', self.emplineposition)) + parent.appendLrfTag(LrfTag('emplinetype', self.emplinetype)) + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + + parent.appendLrfTag(LrfTag(self.__class__.__name__ + "End")) + + def toElement(self, se): + element = Element(self.__class__.__name__) + element.set('emplineposition', self.emplineposition) + element.set('emplinetype', self.emplinetype) + + appendTextElements(element, self.contents, se) + return element + +class Bold(Span): + """ + There is no known "bold" lrf tag. Use Span with a fontweight in LRF, + but use the word Bold in the LRS. + """ + def __init__(self, text=None): + Span.__init__(self, text, fontweight=800) + + def toElement(self, se): + e = Element("Bold") + appendTextElements(e, self.contents, se) + return e + + +class BlockSpace(LrsContainer): + """ Can be appended to a page to move the text point. """ + def __init__(self, xspace=0, yspace=0, x=0, y=0): + LrsContainer.__init__(self, []) + if xspace == 0 and x != 0: + xspace = x + if yspace == 0 and y != 0: + yspace = y + self.xspace = xspace + self.yspace = yspace + + + def toLrfContainer(self, lrfWriter, container): + if self.xspace != 0: + container.appendLrfTag(LrfTag("xspace", self.xspace)) + if self.yspace != 0: + container.appendLrfTag(LrfTag("yspace", self.yspace)) + + + def toElement(self, se): + element = Element("BlockSpace") + + if self.xspace != 0: + element.attrib["xspace"] = str(self.xspace) + if self.yspace != 0: + element.attrib["yspace"] = str(self.yspace) + + return element + + + +class CharButton(LrsSimpleChar1, LrsContainer): + """ + Define the text and target of a CharButton. Must be passed a + JumpButton that is the destination of the CharButton. + + Only text or SimpleChars can be appended to the CharButton. + """ + def __init__(self, button, text=None): + LrsContainer.__init__(self, [basestring, Text, LrsSimpleChar1]) + self.button = None + if button != None: + self.setButton(button) + + if text is not None: + self.append(text) + + def setButton(self, button): + if not isinstance(button, (JumpButton, Button)): + raise LrsError, "CharButton button must be a JumpButton or Button" + + self.button = button + + + def appendReferencedObjects(self, parent): + if self.button.parent is None: + parent.append(self.button) + + + def getReferencedObjIds(self): + return [self.button.objId] + + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("CharButton", self.button.objId)) + + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + + container.appendLrfTag(LrfTag("CharButtonEnd")) + + + def toElement(self, se): + cb = Element("CharButton", refobj=str(self.button.objId)) + appendTextElements(cb, self.contents, se) + return cb + + + +class Objects(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [JumpButton, TextBlock, HeaderOrFooter, + ImageStream, Image, ImageBlock, Button, ButtonBlock]) + self.appendJumpButton = self.appendTextBlock = self.appendHeader = \ + self.appendFooter = self.appendImageStream = \ + self.appendImage = self.appendImageBlock = self.append + + + def getMethods(self): + return ["JumpButton", "appendJumpButton", "TextBlock", + "appendTextBlock", "Header", "appendHeader", + "Footer", "appendFooter", "ImageBlock", + "ImageStream", "appendImageStream", + 'Image','appendImage', 'appendImageBlock'] + + + def getSettings(self): + return [] + + + def ImageBlock(self, *args, **kwargs): + ib = ImageBlock(*args, **kwargs) + self.append(ib) + return ib + + def JumpButton(self, textBlock): + b = JumpButton(textBlock) + self.append(b) + return b + + + def TextBlock(self, *args, **kwargs): + tb = TextBlock(*args, **kwargs) + self.append(tb) + return tb + + + def Header(self, *args, **kwargs): + h = Header(*args, **kwargs) + self.append(h) + return h + + + def Footer(self, *args, **kwargs): + h = Footer(*args, **kwargs) + self.append(h) + return h + + + def ImageStream(self, *args, **kwargs): + i = ImageStream(*args, **kwargs) + self.append(i) + return i + + def Image(self, *args, **kwargs): + i = Image(*args, **kwargs) + self.append(i) + return i + + def toElement(self, se): + o = Element("Objects") + + for content in self.contents: + o.append(content.toElement(se)) + + return o + + + def toLrf(self, lrfWriter): + for content in self.contents: + content.toLrf(lrfWriter) + + +class JumpButton(LrsObject, LrsContainer): + """ + The target of a CharButton. Needs a parented TextBlock to jump to. + Actually creates several elements in the XML. JumpButtons must + be eventually appended to a Book (actually, an Object.) + """ + def __init__(self, textBlock): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + self.textBlock = textBlock + + def setTextBlock(self, textBlock): + self.textBlock = textBlock + + def toLrf(self, lrfWriter): + button = LrfObject("Button", self.objId) + button.appendLrfTag(LrfTag("buttonflags", 0x10)) # pushbutton + button.appendLrfTag(LrfTag("PushButtonStart")) + button.appendLrfTag(LrfTag("buttonactions")) + button.appendLrfTag(LrfTag("jumpto", + (self.textBlock.parent.objId, self.textBlock.objId))) + button.append(LrfTag("endbuttonactions")) + button.appendLrfTag(LrfTag("PushButtonEnd")) + lrfWriter.append(button) + + + def toElement(self, se): + b = self.lrsObjectElement("Button") + pb = SubElement(b, "PushButton") + jt = SubElement(pb, "JumpTo", + refpage=str(self.textBlock.parent.objId), + refobj=str(self.textBlock.objId)) + return b + + + +class RuledLine(LrsContainer, LrsAttributes, LrsObject): + """ A line. Default is 500 pixels long, 2 pixels wide. """ + + defaults = dict( + linelength="500", linetype="solid", linewidth="2", + linecolor="0x00000000") + + def __init__(self, **settings): + LrsContainer.__init__(self, []) + LrsAttributes.__init__(self, self.defaults, **settings) + LrsObject.__init__(self) + + + def toLrfContainer(self, lrfWriter, container): + a = self.attrs + container.appendLrfTag(LrfTag("RuledLine", + (a["linelength"], a["linetype"], a["linewidth"], a["linecolor"]))) + + + def toElement(self, se): + return Element("RuledLine", self.attrs) + + + +class HeaderOrFooter(LrsObject, LrsContainer, LrsAttributes): + """ + Creates empty header or footer objects. Append PutObj objects to + the header or footer to create the text. + + Note: it seems that adding multiple PutObjs to a header or footer + only shows the last one. + """ + defaults = dict(framemode="square", layout="LrTb", framewidth="0", + framecolor="0x00000000", bgcolor="0xFF000000") + + def __init__(self, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [PutObj]) + LrsAttributes.__init__(self, self.defaults, **settings) + + def put_object(self, obj, x1, y1): + self.append(PutObj(obj, x1, y1)) + + def PutObj(self, *args, **kwargs): + p = PutObj(*args, **kwargs) + self.append(p) + return p + + + def toLrf(self, lrfWriter): + hd = LrfObject(self.__class__.__name__, self.objId) + hd.appendTagDict(self.attrs) + + stream = LrfTagStream(0) + for content in self.contents: + content.toLrfContainer(lrfWriter, stream) + + hd.appendLrfTags(stream.getStreamTags(lrfWriter.getSourceEncoding())) + lrfWriter.append(hd) + + + def toElement(self, se): + name = self.__class__.__name__ + labelName = name.lower() + "label" + hd = self.lrsObjectElement(name, objlabel=labelName) + hd.attrib.update(self.attrs) + + for content in self.contents: + hd.append(content.toElement(se)) + + return hd + + +class Header(HeaderOrFooter): + pass + + + +class Footer(HeaderOrFooter): + pass + +class Canvas(LrsObject, LrsContainer, LrsAttributes): + defaults = dict(framemode="square", layout="LrTb", framewidth="0", + framecolor="0x00000000", bgcolor="0xFF000000", + canvasheight=0, canvaswidth=0, blockrule='block-adjustable') + + def __init__(self, width, height, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [PutObj]) + LrsAttributes.__init__(self, self.defaults, **settings) + + self.settings = self.defaults.copy() + self.settings.update(settings) + self.settings['canvasheight'] = int(height) + self.settings['canvaswidth'] = int(width) + + def put_object(self, obj, x1, y1): + self.append(PutObj(obj, x1, y1)) + + def toElement(self, source_encoding): + el = self.lrsObjectElement("Canvas", **self.settings) + for po in self.contents: + el.append(po.toElement(source_encoding)) + return el + + def toLrf(self, lrfWriter): + self.toLrfContainer(lrfWriter, lrfWriter) + + + def toLrfContainer(self, lrfWriter, container): + c = LrfObject("Canvas", self.objId) + c.appendTagDict(self.settings) + stream = LrfTagStream(STREAM_COMPRESSED) + for content in self.contents: + content.toLrfContainer(lrfWriter, stream) + if lrfWriter.saveStreamTags: # true only if testing + c.saveStreamTags = stream.tags + + c.appendLrfTags( + stream.getStreamTags(lrfWriter.getSourceEncoding(), + optimizeTags=lrfWriter.optimizeTags, + optimizeCompression=lrfWriter.optimizeCompression)) + container.addLrfObject(c.objId) + lrfWriter.append(c) + + def has_text(self): + return bool(self.contents) + + + +class PutObj(LrsContainer): + """ PutObj holds other objects that are drawn on a Canvas or Header. """ + + def __init__(self, content, x1=0, y1=0): + LrsContainer.__init__(self, [TextBlock, ImageBlock]) + self.content = content + self.x1 = int(x1) + self.y1 = int(y1) + + def setContent(self, content): + self.content = content + + def appendReferencedObjects(self, parent): + if self.content.parent is None: + parent.append(self.content) + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("PutObj", (self.x1, self.y1, + self.content.objId))) + + + def toElement(self, se): + el = Element("PutObj", x1=str(self.x1), y1=str(self.y1), + refobj=str(self.content.objId)) + return el + + + + +class ImageStream(LrsObject, LrsContainer): + """ + Embed an image file into an Lrf. + """ + + VALID_ENCODINGS = [ "JPEG", "GIF", "BMP", "PNG" ] + + def __init__(self, file=None, encoding=None, comment=None): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + _checkExists(file) + self.filename = file + self.comment = comment + # TODO: move encoding from extension to lrf module + if encoding is None: + extension = os.path.splitext(file)[1] + if not extension: + raise LrsError, \ + "file must have extension if encoding is not specified" + extension = extension[1:].upper() + + if extension == "JPG": + extension = "JPEG" + + encoding = extension + else: + encoding = encoding.upper() + + if encoding not in self.VALID_ENCODINGS: + raise LrsError, \ + "encoding or file extension not JPEG, GIF, BMP, or PNG" + + self.encoding = encoding + + + def toLrf(self, lrfWriter): + imageFile = file(self.filename, "rb") + imageData = imageFile.read() + imageFile.close() + + isObj = LrfObject("ImageStream", self.objId) + if self.comment is not None: + isObj.appendLrfTag(LrfTag("comment", self.comment)) + + streamFlags = IMAGE_TYPE_ENCODING[self.encoding] + stream = LrfStreamBase(streamFlags, imageData) + isObj.appendLrfTags(stream.getStreamTags()) + lrfWriter.append(isObj) + + + def toElement(self, se): + element = self.lrsObjectElement("ImageStream", + objlabel="imagestreamlabel", + encoding=self.encoding, file=self.filename) + element.text = self.comment + return element + +class Image(LrsObject, LrsContainer, LrsAttributes): + + defaults = dict() + + def __init__(self, refstream, x0=0, x1=0, \ + y0=0, y1=0, xsize=0, ysize=0, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + LrsAttributes.__init__(self, self.defaults, settings) + self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1) + self.xsize, self.ysize = int(xsize), int(ysize) + self.setRefstream(refstream) + + def setRefstream(self, refstream): + self.refstream = refstream + + def appendReferencedObjects(self, parent): + if self.refstream.parent is None: + parent.append(self.refstream) + + def getReferencedObjIds(self): + return [self.objId, self.refstream.objId] + + def toElement(self, se): + element = self.lrsObjectElement("Image", **self.attrs) + element.set("refstream", str(self.refstream.objId)) + for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]: + element.set(name, str(getattr(self, name))) + return element + + def toLrf(self, lrfWriter): + ib = LrfObject("Image", self.objId) + ib.appendLrfTag(LrfTag("ImageRect", + (self.x0, self.y0, self.x1, self.y1))) + ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize))) + ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId)) + lrfWriter.append(ib) + + + + + +class ImageBlock(LrsObject, LrsContainer, LrsAttributes): + """ Create an image on a page. """ + # TODO: allow other block attributes + + defaults = BlockStyle.baseDefaults.copy() + + def __init__(self, refstream, x0="0", y0="0", x1="600", y1="800", + xsize="600", ysize="800", + blockStyle=BlockStyle(blockrule='block-fixed'), + alttext=None, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [Text, Image]) + LrsAttributes.__init__(self, self.defaults, **settings) + self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1) + self.xsize, self.ysize = int(xsize), int(ysize) + self.setRefstream(refstream) + self.blockStyle = blockStyle + self.alttext = alttext + + def setRefstream(self, refstream): + self.refstream = refstream + + def appendReferencedObjects(self, parent): + if self.refstream.parent is None: + parent.append(self.refstream) + + if self.blockStyle is not None and self.blockStyle.parent is None: + parent.append(self.blockStyle) + + + def getReferencedObjIds(self): + objects = [self.objId, self.extraId, self.refstream.objId] + if self.blockStyle is not None: + objects.append(self.blockStyle.objId) + + return objects + + + def toLrf(self, lrfWriter): + self.toLrfContainer(lrfWriter, lrfWriter) + + + def toLrfContainer(self, lrfWriter, container): + # id really belongs to the outer block + + extraId = LrsObject.getNextObjId() + + b = LrfObject("Block", self.objId) + if self.blockStyle is not None: + b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) + b.appendTagDict(self.attrs) + + b.appendLrfTags( + LrfTagStream(0, + [LrfTag("Link", extraId)]).getStreamTags(lrfWriter.getSourceEncoding())) + container.addLrfObject(b.objId) + lrfWriter.append(b) + + ib = LrfObject("Image", extraId) + + ib.appendLrfTag(LrfTag("ImageRect", + (self.x0, self.y0, self.x1, self.y1))) + ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize))) + ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId)) + if self.alttext: + ib.appendLrfTag("Comment", self.alttext) + + + lrfWriter.append(ib) + self.extraId = extraId + + + def toElement(self, se): + element = self.lrsObjectElement("ImageBlock", **self.attrs) + element.set("refstream", str(self.refstream.objId)) + for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]: + element.set(name, str(getattr(self, name))) + element.text = self.alttext + return element + + + +class Font(LrsContainer): + """ Allows a TrueType file to be embedded in an Lrf. """ + def __init__(self, file=None, fontname=None, fontfilename=None, encoding=None): + LrsContainer.__init__(self, []) + try: + _checkExists(fontfilename) + self.truefile = fontfilename + except: + try: + _checkExists(file) + self.truefile = file + except: + raise LrsError, "neither '%s' nor '%s' exists"%(fontfilename, file) + + self.file = file + self.fontname = fontname + self.fontfilename = fontfilename + self.encoding = encoding + + + def toLrf(self, lrfWriter): + font = LrfObject("Font", LrsObject.getNextObjId()) + lrfWriter.registerFontId(font.objId) + font.appendLrfTag(LrfTag("FontFilename", + lrfWriter.toUnicode(self.truefile))) + font.appendLrfTag(LrfTag("FontFacename", + lrfWriter.toUnicode(self.fontname))) + + stream = LrfFileStream(STREAM_FORCE_COMPRESSED, self.truefile) + font.appendLrfTags(stream.getStreamTags()) + + lrfWriter.append(font) + + + def toElement(self, se): + element = Element("RegistFont", encoding="TTF", fontname=self.fontname, + file=self.file, fontfilename=self.file) + return element diff --git a/src/calibre/ebooks/markdown/__init__.py b/src/calibre/ebooks/markdown/__init__.py index 466ba03d7e..2676e91934 100644 --- a/src/calibre/ebooks/markdown/__init__.py +++ b/src/calibre/ebooks/markdown/__init__.py @@ -2,4 +2,6 @@ # Initialize extensions from calibre.ebooks.markdown import mdx_footnotes from calibre.ebooks.markdown import mdx_tables -from calibre.ebooks.markdown import mdx_toc \ No newline at end of file +from calibre.ebooks.markdown import mdx_toc + +mdx_footnotes, mdx_tables, mdx_toc diff --git a/src/calibre/ebooks/markdown/mdx_toc.py b/src/calibre/ebooks/markdown/mdx_toc.py index 66a34d90a0..a8c1db267d 100644 --- a/src/calibre/ebooks/markdown/mdx_toc.py +++ b/src/calibre/ebooks/markdown/mdx_toc.py @@ -8,8 +8,6 @@ My markdown extensions for adding: Table of Contents (aka toc) """ -import os -import sys import re import markdown @@ -18,7 +16,7 @@ DEFAULT_TITLE = None def extract_alphanumeric(in_str=None): """take alpha-numeric (7bit ascii) and return as a string """ - # I'm sure this is really inefficient and + # I'm sure this is really inefficient and # could be done with a lambda/map() #x.strip().title().replace(' ', "") out_str=[] @@ -42,7 +40,7 @@ class TocExtension (markdown.Extension): toc is returned in a div tag with class='toc' toc is either: appended to end of document - OR + OR replaces first string occurence of "///Table of Contents Goes Here///" """ @@ -75,7 +73,7 @@ class TocExtension (markdown.Extension): """ Creates Table Of Contents based on headers. - @returns: toc as a single as a dom element + @returns: toc as a single as a dom element in a
tag with class='toc' """ @@ -85,9 +83,9 @@ class TocExtension (markdown.Extension): if element.type=='element': if headers_compiled_re.match(element.nodeName): return True - + headers_doc_list = doc.find(findHeadersFn) - + # Insert anchor tags into dom generated_anchor_id=0 headers_list=[] @@ -99,19 +97,19 @@ class TocExtension (markdown.Extension): if heading_type == self.auto_toc_heading_type: min_header_size_found=min(min_header_size_found, heading_type) - + html_anchor_name= (extract_alphanumeric(heading_title) +'__MD_autoTOC_%d' % (generated_anchor_id)) - + # insert anchor tag inside header tags html_anchor = doc.createElement("a") html_anchor.setAttribute('name', html_anchor_name) element.appendChild(html_anchor) - + headers_list.append( (heading_type, heading_title, html_anchor_name) ) generated_anchor_id = generated_anchor_id + 1 - + # create dom for toc if headers_list != []: # Create list @@ -125,9 +123,9 @@ class TocExtension (markdown.Extension): toc_doc_link.appendChild(toc_doc_text) toc_doc_entry.appendChild(toc_doc_link) toc_doc_list.appendChild(toc_doc_entry) - - - # Put list into div + + + # Put list into div div = doc.createElement("div") div.setAttribute('class', 'toc') if self.TOC_TITLE: @@ -149,7 +147,7 @@ class TocPostprocessor (markdown.Postprocessor): def run(self, doc): tocPlaceholder = self.toc.findTocPlaceholder(doc) - + tocDiv = self.toc.createTocDiv(doc) if tocDiv: if tocPlaceholder : diff --git a/src/calibre/ebooks/metadata/imp.py b/src/calibre/ebooks/metadata/imp.py index e35fc848ef..e2a2b61f31 100644 --- a/src/calibre/ebooks/metadata/imp.py +++ b/src/calibre/ebooks/metadata/imp.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Ashish Kulkarni ' '''Read meta information from IMP files''' -import sys, os +import sys from calibre.ebooks.metadata import MetaInformation, string_to_authors @@ -17,7 +17,7 @@ def get_metadata(stream): if stream.read(10) not in MAGIC: print >>sys.stderr, u'Couldn\'t read IMP header from file' return mi - + def cString(skip=0): result = '' while 1: @@ -30,7 +30,7 @@ def get_metadata(stream): stream.read(38) # skip past some uninteresting headers _, category, title, author = cString(), cString(), cString(1), cString(2) - + if title: mi.title = title if author: diff --git a/src/calibre/ebooks/metadata/lrx.py b/src/calibre/ebooks/metadata/lrx.py index af0e53121e..82473e81d1 100644 --- a/src/calibre/ebooks/metadata/lrx.py +++ b/src/calibre/ebooks/metadata/lrx.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' Read metadata from LRX files ''' -import sys, struct +import struct from zlib import decompress from lxml import etree @@ -33,7 +33,7 @@ def short_be(buf): def get_metadata(f): read = lambda at, amount: _read(f, at, amount) f.seek(0) - buf = f.read(12) + buf = f.read(12) if buf[4:] == 'ftypLRX2': offset = 0 while True: @@ -74,9 +74,9 @@ def get_metadata(f): mi.tags = [x.text for x in bi.findall('Category')] mi.language = root.find('DocInfo').find('Language').text return mi - + elif buf[4:8] == 'LRX': raise ValueError('Librie LRX format not supported') else: raise ValueError('Not a LRX file') - + diff --git a/src/calibre/ebooks/metadata/odt.py b/src/calibre/ebooks/metadata/odt.py index f5b1805e8b..f4b0986295 100755 --- a/src/calibre/ebooks/metadata/odt.py +++ b/src/calibre/ebooks/metadata/odt.py @@ -17,7 +17,7 @@ # # Contributor(s): # -import zipfile, sys, re +import zipfile, re import xml.sax.saxutils from cStringIO import StringIO @@ -46,7 +46,7 @@ fields = { } def normalize(str): - """ + """ The normalize-space function returns the argument string with whitespace normalized by stripping leading and trailing whitespace and replacing sequences of whitespace characters by a single space. @@ -125,7 +125,7 @@ class odfmetaparser(xml.sax.saxutils.XMLGenerator): else: texttag = self._tag self.seenfields[texttag] = self.data() - + if field in self.deletefields: self.output.dowrite = True else: @@ -140,7 +140,7 @@ class odfmetaparser(xml.sax.saxutils.XMLGenerator): def data(self): return normalize(''.join(self._data)) - + def get_metadata(stream): zin = zipfile.ZipFile(stream, 'r') odfs = odfmetaparser() @@ -161,6 +161,6 @@ def get_metadata(stream): mi.language = data['language'] if data.get('keywords', ''): mi.tags = data['keywords'].split(',') - + return mi diff --git a/src/calibre/ebooks/metadata/zip.py b/src/calibre/ebooks/metadata/zip.py index 441aa7e3da..624e0fe73c 100644 --- a/src/calibre/ebooks/metadata/zip.py +++ b/src/calibre/ebooks/metadata/zip.py @@ -3,8 +3,8 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import os -from zipfile import ZipFile -from cStringIO import StringIO +from zipfile import ZipFile +from cStringIO import StringIO def get_metadata(stream): @@ -20,5 +20,5 @@ def get_metadata(stream): stream = StringIO(zf.read(f)) return get_metadata(stream, stream_type) raise ValueError('No ebook found in ZIP archive') - - \ No newline at end of file + + diff --git a/src/calibre/ebooks/pdb/palmdoc/writer.py b/src/calibre/ebooks/pdb/palmdoc/writer.py index 12c1c4aaa7..91a5eb3d97 100644 --- a/src/calibre/ebooks/pdb/palmdoc/writer.py +++ b/src/calibre/ebooks/pdb/palmdoc/writer.py @@ -3,7 +3,6 @@ ''' Writer content to palmdoc pdb file. ''' -import os __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' diff --git a/src/calibre/ebooks/pdb/ztxt/__init__.py b/src/calibre/ebooks/pdb/ztxt/__init__.py index 2c2028b74f..4dd1a954b0 100644 --- a/src/calibre/ebooks/pdb/ztxt/__init__.py +++ b/src/calibre/ebooks/pdb/ztxt/__init__.py @@ -4,7 +4,6 @@ __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' -import os class zTXTError(Exception): pass diff --git a/src/calibre/ebooks/pdf/manipulate/decrypt.py b/src/calibre/ebooks/pdf/manipulate/decrypt.py index 5f4265b5ed..ede12f15ee 100644 --- a/src/calibre/ebooks/pdf/manipulate/decrypt.py +++ b/src/calibre/ebooks/pdf/manipulate/decrypt.py @@ -12,8 +12,6 @@ Decrypt content of PDF. import os, sys from optparse import OptionGroup, Option -from calibre.ebooks.metadata.meta import metadata_from_formats -from calibre.ebooks.metadata import authors_to_string from calibre.utils.config import OptionParser from calibre.utils.logging import Log from calibre.constants import preferred_encoding @@ -36,8 +34,8 @@ OPTIONS = set([ class DecryptionError(Exception): def __init__(self, pdf_path): - self.value = 'Unable to decrypt file `%s`.' % value - + self.value = 'Unable to decrypt file `%s`.' % pdf_path + def __str__(self): return repr(self.value) @@ -62,20 +60,20 @@ def add_options(parser): group = OptionGroup(parser, _('Decrypt Options:'), _('Options to control the transformation of pdf')) parser.add_option_group(group) add_option = group.add_option - + for rec in OPTIONS: option_recommendation_to_cli_option(add_option, rec) def decrypt(pdf_path, out_path, password): pdf = PdfFileReader(open(os.path.abspath(pdf_path), 'rb')) - + if pdf.decrypt(str(password)) == 0: raise DecryptionError(pdf_path) - + title = pdf.documentInfo.title if pdf.documentInfo.title else _('Unknown') author = pdf.documentInfo.author if pdf.documentInfo.author else _('Unknown') out_pdf = PdfFileWriter(title=title, author=author) - + for page in pdf.pages: out_pdf.addPage(page) @@ -86,23 +84,23 @@ def main(args=sys.argv, name=''): log = Log() parser = option_parser(name) add_options(parser) - + opts, args = parser.parse_args(args) args = args[1:] - + if len(args) < 2: print 'Error: A PDF file and decryption password is required.\n' print_help(parser, log) return 1 - + if not is_valid_pdf(args[0]): print 'Error: Could not read file `%s`.' % args[0] return 1 - + if not is_encrypted(args[0]): print 'Error: file `%s` is not encrypted.' % args[0] return 1 - + try: decrypt(args[0], opts.output, args[1]) except DecryptionError, e: diff --git a/src/calibre/ebooks/pdf/manipulate/encrypt.py b/src/calibre/ebooks/pdf/manipulate/encrypt.py index 15600fb07c..ff3b47b11a 100644 --- a/src/calibre/ebooks/pdf/manipulate/encrypt.py +++ b/src/calibre/ebooks/pdf/manipulate/encrypt.py @@ -17,6 +17,8 @@ from calibre.utils.logging import Log from calibre.constants import preferred_encoding from calibre.customize.conversion import OptionRecommendation from calibre.ebooks.pdf.verify import is_valid_pdf, is_encrypted +from calibre.ebooks.metadata import authors_to_string +from calibre.ebooks.metadata.meta import metadata_from_formats from pyPdf import PdfFileWriter, PdfFileReader @@ -52,7 +54,7 @@ def add_options(parser): group = OptionGroup(parser, _('Encrypt Options:'), _('Options to control the transformation of pdf')) parser.add_option_group(group) add_option = group.add_option - + for rec in OPTIONS: option_recommendation_to_cli_option(add_option, rec) @@ -78,23 +80,23 @@ def main(args=sys.argv, name=''): log = Log() parser = option_parser(name) add_options(parser) - + opts, args = parser.parse_args(args) args = args[1:] - + if len(args) < 2: print 'Error: A PDF file and decryption password is required.\n' print_help(parser, log) return 1 - + if not is_valid_pdf(args[0]): print 'Error: Could not read file `%s`.' % args[0] return 1 - + if is_encrypted(args[0]): print 'Error: file `%s` is already encrypted.' % args[0] return 1 - + mi = metadata_from_formats([args[0]]) encrypt(args[0], opts.output, args[1], mi) diff --git a/src/calibre/ebooks/pdf/verify.py b/src/calibre/ebooks/pdf/verify.py index 3a8a8073ce..862cf00ee8 100644 --- a/src/calibre/ebooks/pdf/verify.py +++ b/src/calibre/ebooks/pdf/verify.py @@ -11,25 +11,25 @@ Verify PDF files. import os -from pyPdf import PdfFileWriter, PdfFileReader +from pyPdf import PdfFileReader def is_valid_pdf(pdf_path): ''' Returns True if the pdf file is valid. ''' - + try: with open(os.path.abspath(pdf_path), 'rb') as pdf_file: pdf = PdfFileReader(pdf_file) except: return False return True - + def is_valid_pdfs(pdf_paths): ''' Returns a list of invalid pdf files. ''' - + invalid = [] for pdf_path in pdf_paths: if not is_valid_pdf(pdf_path): diff --git a/src/calibre/ebooks/rb/writer.py b/src/calibre/ebooks/rb/writer.py index 515c95a6fe..c8908ee95f 100644 --- a/src/calibre/ebooks/rb/writer.py +++ b/src/calibre/ebooks/rb/writer.py @@ -4,7 +4,6 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' -import os import struct import zlib diff --git a/src/calibre/ebooks/rtf2xml/copy.py b/src/calibre/ebooks/rtf2xml/copy.py index 26ca300696..ff029c1841 100755 --- a/src/calibre/ebooks/rtf2xml/copy.py +++ b/src/calibre/ebooks/rtf2xml/copy.py @@ -15,7 +15,7 @@ # # # # ######################################################################### -import sys, os, shutil +import os, shutil class Copy: """Copy each changed file to a directory for debugging purposes""" @@ -66,6 +66,6 @@ class Copy: """ write_file = os.path.join(Copy.__dir,new_file) shutil.copyfile(file, write_file) - + def rename(self, source, dest): - shutil.copyfile(source, dest) \ No newline at end of file + shutil.copyfile(source, dest) diff --git a/src/calibre/ebooks/rtf2xml/options_trem.py b/src/calibre/ebooks/rtf2xml/options_trem.py index 12ab79b5b3..86c11a6e85 100755 --- a/src/calibre/ebooks/rtf2xml/options_trem.py +++ b/src/calibre/ebooks/rtf2xml/options_trem.py @@ -1,5 +1,4 @@ import sys -from calibre.ebooks import rtf2xml class ParseOptions: """ Requires: diff --git a/src/calibre/ebooks/rtf2xml/output.py b/src/calibre/ebooks/rtf2xml/output.py index bb17228fce..f193d2376e 100755 --- a/src/calibre/ebooks/rtf2xml/output.py +++ b/src/calibre/ebooks/rtf2xml/output.py @@ -16,7 +16,6 @@ # # ######################################################################### import sys, os, codecs -from calibre.ebooks import rtf2xml class Output: """ Output file diff --git a/src/calibre/ebooks/rtf2xml/override_table.py b/src/calibre/ebooks/rtf2xml/override_table.py index 6186e7ec55..146c73397a 100755 --- a/src/calibre/ebooks/rtf2xml/override_table.py +++ b/src/calibre/ebooks/rtf2xml/override_table.py @@ -15,8 +15,6 @@ # # # # ######################################################################### -import sys,os -from calibre.ebooks import rtf2xml class OverrideTable: """ Parse a line of text to make the override table. Return a string diff --git a/src/calibre/gui2/dialogs/choose_format.py b/src/calibre/gui2/dialogs/choose_format.py index 809b636690..e0fcb0868b 100644 --- a/src/calibre/gui2/dialogs/choose_format.py +++ b/src/calibre/gui2/dialogs/choose_format.py @@ -7,21 +7,19 @@ from calibre.gui2 import file_icon_provider from calibre.gui2.dialogs.choose_format_ui import Ui_ChooseFormatDialog class ChooseFormatDialog(QDialog, Ui_ChooseFormatDialog): - + def __init__(self, window, msg, formats): QDialog.__init__(self, window) Ui_ChooseFormatDialog.__init__(self) self.setupUi(self) self.connect(self.formats, SIGNAL('activated(QModelIndex)'), lambda i: self.accept()) - + self.msg.setText(msg) for format in formats: self.formats.addItem(QListWidgetItem(file_icon_provider().icon_from_ext(format.lower()), format.upper())) self._formats = formats self.formats.setCurrentRow(0) - + def format(self): return self._formats[self.formats.currentRow()] - - \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/conversion_error.py b/src/calibre/gui2/dialogs/conversion_error.py index cfa573e371..7b47c59d5a 100644 --- a/src/calibre/gui2/dialogs/conversion_error.py +++ b/src/calibre/gui2/dialogs/conversion_error.py @@ -5,7 +5,7 @@ from PyQt4.QtGui import QDialog from calibre.gui2.dialogs.conversion_error_ui import Ui_ConversionErrorDialog class ConversionErrorDialog(QDialog, Ui_ConversionErrorDialog): - + def __init__(self, window, title, html, show=False): QDialog.__init__(self, window) Ui_ConversionErrorDialog.__init__(self) @@ -14,7 +14,7 @@ class ConversionErrorDialog(QDialog, Ui_ConversionErrorDialog): self.set_message(html) if show: self.show() - + def set_message(self, html): self.text.setHtml('%s%s' % content) - + css = dom_tree.findAll('link') for c in css: c.extract() - + print_css = Tag(BeautifulSoup(), 'style', [('type', 'text/css'), ('title', 'override_css')]) print_css.insert(0, PRINTCSS) dom_tree.findAll('head')[0].insert(0, print_css) - + return unicode(dom_tree) def print_preview(self, ok): printer = QPrinter(QPrinter.HighResolution) printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) - + previewDialog = QPrintPreviewDialog(printer) - + self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_) previewDialog.exec_() self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_) - + self.loop.quit() - + def print_book(self, ok): printer = QPrinter(QPrinter.HighResolution) printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) - + printDialog = QPrintDialog(printer) printDialog.setWindowTitle(_("Print eBook")) - + printDialog.exec_() if printDialog.result() == QDialog.Accepted: self.view.print_(printer) - + self.loop.quit() def main(): diff --git a/src/calibre/manual/conf.py b/src/calibre/manual/conf.py index 1000d5c5f2..8bea871349 100644 --- a/src/calibre/manual/conf.py +++ b/src/calibre/manual/conf.py @@ -18,7 +18,7 @@ sys.path.append(os.path.abspath('../../../')) sys.path.append(os.path.abspath('.')) from calibre import __appname__, __version__ import custom - +custom # General configuration # --------------------- diff --git a/src/calibre/path.py b/src/calibre/path.py deleted file mode 100644 index 44f07a1455..0000000000 --- a/src/calibre/path.py +++ /dev/null @@ -1,970 +0,0 @@ -""" path.py - An object representing a path to a file or directory. - -Example: - -from path import path -d = path('/home/guido/bin') -for f in d.files('*.py'): - f.chmod(0755) - -This module requires Python 2.2 or later. - - -URL: http://www.jorendorff.com/articles/python/path -Author: Jason Orendorff (and others - see the url!) -Date: 9 Mar 2007 -""" - - -# TODO -# - Tree-walking functions don't avoid symlink loops. Matt Harrison -# sent me a patch for this. -# - Bug in write_text(). It doesn't support Universal newline mode. -# - Better error message in listdir() when self isn't a -# directory. (On Windows, the error message really sucks.) -# - Make sure everything has a good docstring. -# - Add methods for regex find and replace. -# - guess_content_type() method? -# - Perhaps support arguments to touch(). - -from __future__ import generators - -import sys, warnings, os, fnmatch, glob, shutil, codecs, hashlib - -__version__ = '2.2' -__all__ = ['path'] - -# Platform-specific support for path.owner -if os.name == 'nt': - try: - import win32security - except ImportError: - win32security = None -else: - try: - import pwd - except ImportError: - pwd = None - -# Pre-2.3 support. Are unicode filenames supported? -_base = str -_getcwd = os.getcwd -try: - if os.path.supports_unicode_filenames: - _base = unicode - _getcwd = os.getcwdu -except AttributeError: - pass - -# Pre-2.3 workaround for booleans -try: - True, False -except NameError: - True, False = 1, 0 - -# Pre-2.3 workaround for basestring. -try: - basestring -except NameError: - basestring = (str, unicode) - -# Universal newline support -_textmode = 'r' -if hasattr(file, 'newlines'): - _textmode = 'U' - - -class TreeWalkWarning(Warning): - pass - -class path(_base): - """ Represents a filesystem path. - - For documentation on individual methods, consult their - counterparts in os.path. - """ - - # --- Special Python methods. - - def __repr__(self): - return 'path(%s)' % _base.__repr__(self) - - # Adding a path and a string yields a path. - def __add__(self, more): - try: - resultStr = _base.__add__(self, more) - except TypeError: #Python bug - resultStr = NotImplemented - if resultStr is NotImplemented: - return resultStr - return self.__class__(resultStr) - - def __radd__(self, other): - if isinstance(other, basestring): - return self.__class__(other.__add__(self)) - else: - return NotImplemented - - # The / operator joins paths. - def __div__(self, rel): - """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) - - Join two path components, adding a separator character if - needed. - """ - return self.__class__(os.path.join(self, rel)) - - # Make the / operator work even when true division is enabled. - __truediv__ = __div__ - - def getcwd(cls): - """ Return the current working directory as a path object. """ - return cls(_getcwd()) - getcwd = classmethod(getcwd) - - - # --- Operations on path strings. - - isabs = os.path.isabs - def abspath(self): return self.__class__(os.path.abspath(self)) - def normcase(self): return self.__class__(os.path.normcase(self)) - def normpath(self): return self.__class__(os.path.normpath(self)) - def realpath(self): return self.__class__(os.path.realpath(self)) - def expanduser(self): return self.__class__(os.path.expanduser(self)) - def expandvars(self): return self.__class__(os.path.expandvars(self)) - def dirname(self): return self.__class__(os.path.dirname(self)) - basename = os.path.basename - - def expand(self): - """ Clean up a filename by calling expandvars(), - expanduser(), and normpath() on it. - - This is commonly everything needed to clean up a filename - read from a configuration file, for example. - """ - return self.expandvars().expanduser().normpath() - - def _get_namebase(self): - base, ext = os.path.splitext(self.name) - return base - - def _get_ext(self): - f, ext = os.path.splitext(_base(self)) - return ext - - def _get_drive(self): - drive, r = os.path.splitdrive(self) - return self.__class__(drive) - - parent = property( - dirname, None, None, - """ This path's parent directory, as a new path object. - - For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib') - """) - - name = property( - basename, None, None, - """ The name of this file or directory without the full path. - - For example, path('/usr/local/lib/libpython.so').name == 'libpython.so' - """) - - namebase = property( - _get_namebase, None, None, - """ The same as path.name, but with one file extension stripped off. - - For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz', - but path('/home/guido/python.tar.gz').namebase == 'python.tar' - """) - - ext = property( - _get_ext, None, None, - """ The file extension, for example '.py'. """) - - drive = property( - _get_drive, None, None, - """ The drive specifier, for example 'C:'. - This is always empty on systems that don't use drive specifiers. - """) - - def splitpath(self): - """ p.splitpath() -> Return (p.parent, p.name). """ - parent, child = os.path.split(self) - return self.__class__(parent), child - - def splitdrive(self): - """ p.splitdrive() -> Return (p.drive, ). - - Split the drive specifier from this path. If there is - no drive specifier, p.drive is empty, so the return value - is simply (path(''), p). This is always the case on Unix. - """ - drive, rel = os.path.splitdrive(self) - return self.__class__(drive), rel - - def splitext(self): - """ p.splitext() -> Return (p.stripext(), p.ext). - - Split the filename extension from this path and return - the two parts. Either part may be empty. - - The extension is everything from '.' to the end of the - last path segment. This has the property that if - (a, b) == p.splitext(), then a + b == p. - """ - filename, ext = os.path.splitext(self) - return self.__class__(filename), ext - - def stripext(self): - """ p.stripext() -> Remove one file extension from the path. - - For example, path('/home/guido/python.tar.gz').stripext() - returns path('/home/guido/python.tar'). - """ - return self.splitext()[0] - - if hasattr(os.path, 'splitunc'): - def splitunc(self): - unc, rest = os.path.splitunc(self) - return self.__class__(unc), rest - - def _get_uncshare(self): - unc, r = os.path.splitunc(self) - return self.__class__(unc) - - uncshare = property( - _get_uncshare, None, None, - """ The UNC mount point for this path. - This is empty for paths on local drives. """) - - def joinpath(self, *args): - """ Join two or more path components, adding a separator - character (os.sep) if needed. Returns a new path - object. - """ - return self.__class__(os.path.join(self, *args)) - - def splitall(self): - r""" Return a list of the path components in this path. - - The first item in the list will be a path. Its value will be - either os.curdir, os.pardir, empty, or the root directory of - this path (for example, '/' or 'C:\\'). The other items in - the list will be strings. - - path.path.joinpath(*result) will yield the original path. - """ - parts = [] - loc = self - while loc != os.curdir and loc != os.pardir: - prev = loc - loc, child = prev.splitpath() - if loc == prev: - break - parts.append(child) - parts.append(loc) - parts.reverse() - return parts - - def relpath(self): - """ Return this path as a relative path, - based from the current working directory. - """ - cwd = self.__class__(os.getcwd()) - return cwd.relpathto(self) - - def relpathto(self, dest): - """ Return a relative path from self to dest. - - If there is no relative path from self to dest, for example if - they reside on different drives in Windows, then this returns - dest.abspath(). - """ - origin = self.abspath() - dest = self.__class__(dest).abspath() - - orig_list = origin.normcase().splitall() - # Don't normcase dest! We want to preserve the case. - dest_list = dest.splitall() - - if orig_list[0] != os.path.normcase(dest_list[0]): - # Can't get here from there. - return dest - - # Find the location where the two paths start to differ. - i = 0 - for start_seg, dest_seg in zip(orig_list, dest_list): - if start_seg != os.path.normcase(dest_seg): - break - i += 1 - - # Now i is the point where the two paths diverge. - # Need a certain number of "os.pardir"s to work up - # from the origin to the point of divergence. - segments = [os.pardir] * (len(orig_list) - i) - # Need to add the diverging part of dest_list. - segments += dest_list[i:] - if len(segments) == 0: - # If they happen to be identical, use os.curdir. - relpath = os.curdir - else: - relpath = os.path.join(*segments) - return self.__class__(relpath) - - # --- Listing, searching, walking, and matching - - def listdir(self, pattern=None): - """ D.listdir() -> List of items in this directory. - - Use D.files() or D.dirs() instead if you want a listing - of just files or just subdirectories. - - The elements of the list are path objects. - - With the optional 'pattern' argument, this only lists - items whose names match the given pattern. - """ - names = os.listdir(self) - if pattern is not None: - names = fnmatch.filter(names, pattern) - return [self / child for child in names] - - def dirs(self, pattern=None): - """ D.dirs() -> List of this directory's subdirectories. - - The elements of the list are path objects. - This does not walk recursively into subdirectories - (but see path.walkdirs). - - With the optional 'pattern' argument, this only lists - directories whose names match the given pattern. For - example, d.dirs('build-*'). - """ - return [p for p in self.listdir(pattern) if p.isdir()] - - def files(self, pattern=None): - """ D.files() -> List of the files in this directory. - - The elements of the list are path objects. - This does not walk into subdirectories (see path.walkfiles). - - With the optional 'pattern' argument, this only lists files - whose names match the given pattern. For example, - d.files('*.pyc'). - """ - - return [p for p in self.listdir(pattern) if p.isfile()] - - def walk(self, pattern=None, errors='strict'): - """ D.walk() -> iterator over files and subdirs, recursively. - - The iterator yields path objects naming each child item of - this directory and its descendants. This requires that - D.isdir(). - - This performs a depth-first traversal of the directory tree. - Each directory is returned just before all its children. - - The errors= keyword argument controls behavior when an - error occurs. The default is 'strict', which causes an - exception. The other allowed values are 'warn', which - reports the error via warnings.warn(), and 'ignore'. - """ - if errors not in ('strict', 'warn', 'ignore'): - raise ValueError("invalid errors parameter") - - try: - childList = self.listdir() - except Exception: - if errors == 'ignore': - return - elif errors == 'warn': - warnings.warn( - "Unable to list directory '%s': %s" - % (self, sys.exc_info()[1]), - TreeWalkWarning) - return - else: - raise - - for child in childList: - if pattern is None or child.fnmatch(pattern): - yield child - try: - isdir = child.isdir() - except Exception: - if errors == 'ignore': - isdir = False - elif errors == 'warn': - warnings.warn( - "Unable to access '%s': %s" - % (child, sys.exc_info()[1]), - TreeWalkWarning) - isdir = False - else: - raise - - if isdir: - for item in child.walk(pattern, errors): - yield item - - def walkdirs(self, pattern=None, errors='strict'): - """ D.walkdirs() -> iterator over subdirs, recursively. - - With the optional 'pattern' argument, this yields only - directories whose names match the given pattern. For - example, mydir.walkdirs('*test') yields only directories - with names ending in 'test'. - - The errors= keyword argument controls behavior when an - error occurs. The default is 'strict', which causes an - exception. The other allowed values are 'warn', which - reports the error via warnings.warn(), and 'ignore'. - """ - if errors not in ('strict', 'warn', 'ignore'): - raise ValueError("invalid errors parameter") - - try: - dirs = self.dirs() - except Exception: - if errors == 'ignore': - return - elif errors == 'warn': - warnings.warn( - "Unable to list directory '%s': %s" - % (self, sys.exc_info()[1]), - TreeWalkWarning) - return - else: - raise - - for child in dirs: - if pattern is None or child.fnmatch(pattern): - yield child - for subsubdir in child.walkdirs(pattern, errors): - yield subsubdir - - def walkfiles(self, pattern=None, errors='strict'): - """ D.walkfiles() -> iterator over files in D, recursively. - - The optional argument, pattern, limits the results to files - with names that match the pattern. For example, - mydir.walkfiles('*.tmp') yields only files with the .tmp - extension. - """ - if errors not in ('strict', 'warn', 'ignore'): - raise ValueError("invalid errors parameter") - - try: - childList = self.listdir() - except Exception: - if errors == 'ignore': - return - elif errors == 'warn': - warnings.warn( - "Unable to list directory '%s': %s" - % (self, sys.exc_info()[1]), - TreeWalkWarning) - return - else: - raise - - for child in childList: - try: - isfile = child.isfile() - isdir = not isfile and child.isdir() - except: - if errors == 'ignore': - continue - elif errors == 'warn': - warnings.warn( - "Unable to access '%s': %s" - % (self, sys.exc_info()[1]), - TreeWalkWarning) - continue - else: - raise - - if isfile: - if pattern is None or child.fnmatch(pattern): - yield child - elif isdir: - for f in child.walkfiles(pattern, errors): - yield f - - def fnmatch(self, pattern): - """ Return True if self.name matches the given pattern. - - pattern - A filename pattern with wildcards, - for example '*.py'. - """ - return fnmatch.fnmatch(self.name, pattern) - - def glob(self, pattern): - """ Return a list of path objects that match the pattern. - - pattern - a path relative to this directory, with wildcards. - - For example, path('/users').glob('*/bin/*') returns a list - of all the files users have in their bin directories. - """ - cls = self.__class__ - return [cls(s) for s in glob.glob(_base(self / pattern))] - - - # --- Reading or writing an entire file at once. - - def open(self, mode='r'): - """ Open this file. Return a file object. """ - return file(self, mode) - - def bytes(self): - """ Open this file, read all bytes, return them as a string. """ - f = self.open('rb') - try: - return f.read() - finally: - f.close() - - def write_bytes(self, bytes, append=False): - """ Open this file and write the given bytes to it. - - Default behavior is to overwrite any existing file. - Call p.write_bytes(bytes, append=True) to append instead. - """ - if append: - mode = 'ab' - else: - mode = 'wb' - f = self.open(mode) - try: - f.write(bytes) - finally: - f.close() - - def text(self, encoding=None, errors='strict'): - r""" Open this file, read it in, return the content as a string. - - This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r' - are automatically translated to '\n'. - - Optional arguments: - - encoding - The Unicode encoding (or character set) of - the file. If present, the content of the file is - decoded and returned as a unicode object; otherwise - it is returned as an 8-bit str. - errors - How to handle Unicode errors; see help(str.decode) - for the options. Default is 'strict'. - """ - if encoding is None: - # 8-bit - f = self.open(_textmode) - try: - return f.read() - finally: - f.close() - else: - # Unicode - f = codecs.open(self, 'r', encoding, errors) - # (Note - Can't use 'U' mode here, since codecs.open - # doesn't support 'U' mode, even in Python 2.3.) - try: - t = f.read() - finally: - f.close() - return (t.replace(u'\r\n', u'\n') - .replace(u'\r\x85', u'\n') - .replace(u'\r', u'\n') - .replace(u'\x85', u'\n') - .replace(u'\u2028', u'\n')) - - def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False): - r""" Write the given text to this file. - - The default behavior is to overwrite any existing file; - to append instead, use the 'append=True' keyword argument. - - There are two differences between path.write_text() and - path.write_bytes(): newline handling and Unicode handling. - See below. - - Parameters: - - - text - str/unicode - The text to be written. - - - encoding - str - The Unicode encoding that will be used. - This is ignored if 'text' isn't a Unicode string. - - - errors - str - How to handle Unicode encoding errors. - Default is 'strict'. See help(unicode.encode) for the - options. This is ignored if 'text' isn't a Unicode - string. - - - linesep - keyword argument - str/unicode - The sequence of - characters to be used to mark end-of-line. The default is - os.linesep. You can also specify None; this means to - leave all newlines as they are in 'text'. - - - append - keyword argument - bool - Specifies what to do if - the file already exists (True: append to the end of it; - False: overwrite it.) The default is False. - - - --- Newline handling. - - write_text() converts all standard end-of-line sequences - ('\n', '\r', and '\r\n') to your platform's default end-of-line - sequence (see os.linesep; on Windows, for example, the - end-of-line marker is '\r\n'). - - If you don't like your platform's default, you can override it - using the 'linesep=' keyword argument. If you specifically want - write_text() to preserve the newlines as-is, use 'linesep=None'. - - This applies to Unicode text the same as to 8-bit text, except - there are three additional standard Unicode end-of-line sequences: - u'\x85', u'\r\x85', and u'\u2028'. - - (This is slightly different from when you open a file for - writing with fopen(filename, "w") in C or file(filename, 'w') - in Python.) - - - --- Unicode - - If 'text' isn't Unicode, then apart from newline handling, the - bytes are written verbatim to the file. The 'encoding' and - 'errors' arguments are not used and must be omitted. - - If 'text' is Unicode, it is first converted to bytes using the - specified 'encoding' (or the default encoding if 'encoding' - isn't specified). The 'errors' argument applies only to this - conversion. - - """ - if isinstance(text, unicode): - if linesep is not None: - # Convert all standard end-of-line sequences to - # ordinary newline characters. - text = (text.replace(u'\r\n', u'\n') - .replace(u'\r\x85', u'\n') - .replace(u'\r', u'\n') - .replace(u'\x85', u'\n') - .replace(u'\u2028', u'\n')) - text = text.replace(u'\n', linesep) - if encoding is None: - encoding = sys.getdefaultencoding() - bytes = text.encode(encoding, errors) - else: - # It is an error to specify an encoding if 'text' is - # an 8-bit string. - assert encoding is None - - if linesep is not None: - text = (text.replace('\r\n', '\n') - .replace('\r', '\n')) - bytes = text.replace('\n', linesep) - - self.write_bytes(bytes, append) - - def lines(self, encoding=None, errors='strict', retain=True): - r""" Open this file, read all lines, return them in a list. - - Optional arguments: - encoding - The Unicode encoding (or character set) of - the file. The default is None, meaning the content - of the file is read as 8-bit characters and returned - as a list of (non-Unicode) str objects. - errors - How to handle Unicode errors; see help(str.decode) - for the options. Default is 'strict' - retain - If true, retain newline characters; but all newline - character combinations ('\r', '\n', '\r\n') are - translated to '\n'. If false, newline characters are - stripped off. Default is True. - - This uses 'U' mode in Python 2.3 and later. - """ - if encoding is None and retain: - f = self.open(_textmode) - try: - return f.readlines() - finally: - f.close() - else: - return self.text(encoding, errors).splitlines(retain) - - def write_lines(self, lines, encoding=None, errors='strict', - linesep=os.linesep, append=False): - r""" Write the given lines of text to this file. - - By default this overwrites any existing file at this path. - - This puts a platform-specific newline sequence on every line. - See 'linesep' below. - - lines - A list of strings. - - encoding - A Unicode encoding to use. This applies only if - 'lines' contains any Unicode strings. - - errors - How to handle errors in Unicode encoding. This - also applies only to Unicode strings. - - linesep - The desired line-ending. This line-ending is - applied to every line. If a line already has any - standard line ending ('\r', '\n', '\r\n', u'\x85', - u'\r\x85', u'\u2028'), that will be stripped off and - this will be used instead. The default is os.linesep, - which is platform-dependent ('\r\n' on Windows, '\n' on - Unix, etc.) Specify None to write the lines as-is, - like file.writelines(). - - Use the keyword argument append=True to append lines to the - file. The default is to overwrite the file. Warning: - When you use this with Unicode data, if the encoding of the - existing data in the file is different from the encoding - you specify with the encoding= parameter, the result is - mixed-encoding data, which can really confuse someone trying - to read the file later. - """ - if append: - mode = 'ab' - else: - mode = 'wb' - f = self.open(mode) - try: - for line in lines: - isUnicode = isinstance(line, unicode) - if linesep is not None: - # Strip off any existing line-end and add the - # specified linesep string. - if isUnicode: - if line[-2:] in (u'\r\n', u'\x0d\x85'): - line = line[:-2] - elif line[-1:] in (u'\r', u'\n', - u'\x85', u'\u2028'): - line = line[:-1] - else: - if line[-2:] == '\r\n': - line = line[:-2] - elif line[-1:] in ('\r', '\n'): - line = line[:-1] - line += linesep - if isUnicode: - if encoding is None: - encoding = sys.getdefaultencoding() - line = line.encode(encoding, errors) - f.write(line) - finally: - f.close() - - def read_md5(self): - """ Calculate the md5 hash for this file. - - This reads through the entire file. - """ - f = self.open('rb') - try: - m = hashlib.md5() - while True: - d = f.read(8192) - if not d: - break - m.update(d) - finally: - f.close() - return m.digest() - - # --- Methods for querying the filesystem. - - exists = os.path.exists - isdir = os.path.isdir - isfile = os.path.isfile - islink = os.path.islink - ismount = os.path.ismount - - if hasattr(os.path, 'samefile'): - samefile = os.path.samefile - - getatime = os.path.getatime - atime = property( - getatime, None, None, - """ Last access time of the file. """) - - getmtime = os.path.getmtime - mtime = property( - getmtime, None, None, - """ Last-modified time of the file. """) - - if hasattr(os.path, 'getctime'): - getctime = os.path.getctime - ctime = property( - getctime, None, None, - """ Creation time of the file. """) - - getsize = os.path.getsize - size = property( - getsize, None, None, - """ Size of the file, in bytes. """) - - if hasattr(os, 'access'): - def access(self, mode): - """ Return true if current user has access to this path. - - mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK - """ - return os.access(self, mode) - - def stat(self): - """ Perform a stat() system call on this path. """ - return os.stat(self) - - def lstat(self): - """ Like path.stat(), but do not follow symbolic links. """ - return os.lstat(self) - - def get_owner(self): - r""" Return the name of the owner of this file or directory. - - This follows symbolic links. - - On Windows, this returns a name of the form ur'DOMAIN\User Name'. - On Windows, a group can own a file or directory. - """ - if os.name == 'nt': - if win32security is None: - raise Exception("path.owner requires win32all to be installed") - desc = win32security.GetFileSecurity( - self, win32security.OWNER_SECURITY_INFORMATION) - sid = desc.GetSecurityDescriptorOwner() - account, domain, typecode = win32security.LookupAccountSid(None, sid) - return domain + u'\\' + account - else: - if pwd is None: - raise NotImplementedError("path.owner is not implemented on this platform.") - st = self.stat() - return pwd.getpwuid(st.st_uid).pw_name - - owner = property( - get_owner, None, None, - """ Name of the owner of this file or directory. """) - - if hasattr(os, 'statvfs'): - def statvfs(self): - """ Perform a statvfs() system call on this path. """ - return os.statvfs(self) - - if hasattr(os, 'pathconf'): - def pathconf(self, name): - return os.pathconf(self, name) - - - # --- Modifying operations on files and directories - - def utime(self, times): - """ Set the access and modified times of this file. """ - os.utime(self, times) - - def chmod(self, mode): - os.chmod(self, mode) - - if hasattr(os, 'chown'): - def chown(self, uid, gid): - os.chown(self, uid, gid) - - def rename(self, new): - os.rename(self, new) - - def renames(self, new): - os.renames(self, new) - - - # --- Create/delete operations on directories - - def mkdir(self, mode=0777): - os.mkdir(self, mode) - - def makedirs(self, mode=0777): - os.makedirs(self, mode) - - def rmdir(self): - os.rmdir(self) - - def removedirs(self): - os.removedirs(self) - - - # --- Modifying operations on files - - def touch(self): - """ Set the access/modified times of this file to the current time. - Create the file if it does not exist. - """ - fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666) - os.close(fd) - os.utime(self, None) - - def remove(self): - os.remove(self) - - def unlink(self): - os.unlink(self) - - - # --- Links - - if hasattr(os, 'link'): - def link(self, newpath): - """ Create a hard link at 'newpath', pointing to this file. """ - os.link(self, newpath) - - if hasattr(os, 'symlink'): - def symlink(self, newlink): - """ Create a symbolic link at 'newlink', pointing here. """ - os.symlink(self, newlink) - - if hasattr(os, 'readlink'): - def readlink(self): - """ Return the path to which this symbolic link points. - - The result may be an absolute or a relative path. - """ - return self.__class__(os.readlink(self)) - - def readlinkabs(self): - """ Return the path to which this symbolic link points. - - The result is always an absolute path. - """ - p = self.readlink() - if p.isabs(): - return p - else: - return (self.parent / p).abspath() - - - # --- High-level functions from shutil - - copyfile = shutil.copyfile - copymode = shutil.copymode - copystat = shutil.copystat - copy = shutil.copy - copy2 = shutil.copy2 - copytree = shutil.copytree - if hasattr(shutil, 'move'): - move = shutil.move - rmtree = shutil.rmtree - - - # --- Special stuff from os - - if hasattr(os, 'chroot'): - def chroot(self): - os.chroot(self) - - if hasattr(os, 'startfile'): - def startfile(self): - os.startfile(self) - diff --git a/src/calibre/translations/automatic.py b/src/calibre/translations/automatic.py deleted file mode 100644 index 0ef1553061..0000000000 --- a/src/calibre/translations/automatic.py +++ /dev/null @@ -1,121 +0,0 @@ - -import sys, glob, re - -import mechanize - -URL = 'http://translate.google.com/translate_t?text=%(text)s&langpair=en|%(lang)s&oe=UTF8' - -def browser(): - opener = mechanize.Browser() - opener.set_handle_refresh(True) - opener.set_handle_robots(False) - opener.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; i686 Linux; en_US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4')] - return opener - - -class PoFile(object): - - SANITIZE = re.compile(r'&|<[^<>]+>|\%') - STRING = re.compile(r'"(.*)"') - - def __init__(self, po_file): - self.po_file = open(po_file, 'r+b') - self.browser = browser() - self.entries = [] - self.read() - - def sanitize_line(self, line): - return self.SANITIZE.sub(line) - - def read(self): - translated_lines = [] - self.po_file.seek(0) - - ID = 0 - STR = 1 - WHR = 2 - - mode = None - where, msgid, msgstr, fuzzy = [], [], [], False - - for line in self.po_file.readlines(): - prev_mode = mode - if line.startswith('#:'): - mode = WHR - elif line.startswith('msgid'): - mode = ID - elif line.startswith('msgstr'): - mode = STR - elif line.startswith('#,'): - fuzzy = True - continue - elif line.startswith('#') or not line.strip(): - mode = None - - - if mode != prev_mode: - if prev_mode == STR: - self.add_entry(where, fuzzy, msgid, msgstr) - where, msgid, msgstr, fuzzy = [], [], [], False - - if mode == WHR: - where.append(line[2:].strip()) - elif mode == ID: - msgid.append(self.get_string(line)) - elif mode == STR: - msgstr.append(self.get_string(line)) - elif mode == None: - self.add_line(line) - - def get_string(self, line): - return self.STRING.search(line).group(1) - - def add_line(self, line): - self.entries.append(line.strip()) - - def add_entry(self, where, fuzzy, msgid, msgstr): - self.entries.append(Entry(where, fuzzy, msgid, msgstr)) - - def __str__(self): - return '\n'.join([str(i) for i in self.entries]) + '\n' - - -class Entry(object): - - def __init__(self, where, fuzzy, msgid, msgstr, encoding='utf-8'): - self.fuzzy = fuzzy - self.where = [i.decode(encoding) for i in where] - self.msgid = [i.decode(encoding) for i in msgid] - self.msgstr = [i.decode(encoding) for i in msgstr] - self.encoding = encoding - - def __str__(self): - ans = [] - for line in self.where: - ans.append('#: ' + line.encode(self.encoding)) - if self.fuzzy: - ans.append('#, fuzzy') - first = True - for line in self.msgid: - prefix = 'msgid ' if first else '' - ans.append(prefix + '"%s"'%line.encode(self.encoding)) - first = False - first = True - for line in self.msgstr: - prefix = 'msgstr ' if first else '' - ans.append(prefix + '"%s"'%line.encode(self.encoding)) - first = False - return '\n'.join(ans) - - - -def main(): - po_files = glob.glob('*.po') - for po_file in po_files: - PoFile(po_file) - pass - -if __name__ == '__main__': - pof = PoFile('de.po') - open('/tmp/de.po', 'wb').write(str(pof)) - #sys.exit(main()) \ No newline at end of file diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index b4323e0a65..115bb81e4c 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -15,7 +15,10 @@ def available_translations(): global _available_translations if _available_translations is None: stats = P('localization/stats.pickle') - stats = cPickle.load(open(stats, 'rb')) + if os.path.exists(stats): + stats = cPickle.load(open(stats, 'rb')) + else: + stats = {} _available_translations = [x for x in stats if stats[x] > 0.1] return _available_translations diff --git a/src/calibre/utils/pyparsing.py b/src/calibre/utils/pyparsing.py index 5404758186..9d12066e7f 100644 --- a/src/calibre/utils/pyparsing.py +++ b/src/calibre/utils/pyparsing.py @@ -85,7 +85,7 @@ __all__ = [ 'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', 'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', 'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', 'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', 'indentedBlock', 'originalTextFor', @@ -425,7 +425,7 @@ class ParseResults(object): self[k] = v if isinstance(v[0],ParseResults): v[0].__parent = wkref(self) - + self.__toklist += other.__toklist self.__accumNames.update( other.__accumNames ) del other @@ -3231,12 +3231,12 @@ def originalTextFor(expr, asString=True): restore the parsed fields of an HTML start tag into the raw tag text itself, or to revert separate tokens with intervening whitespace back to the original matching input text. Simpler to use than the parse action keepOriginalText, and does not - require the inspect module to chase up the call stack. By default, returns a - string containing the original parsed text. - - If the optional asString argument is passed as False, then the return value is a - ParseResults containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if + require the inspect module to chase up the call stack. By default, returns a + string containing the original parsed text. + + If the optional asString argument is passed as False, then the return value is a + ParseResults containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if the expression passed to originalTextFor contains expressions with defined results names, you must set asString to False if you want to preserve those results name values.""" @@ -3252,7 +3252,7 @@ def originalTextFor(expr, asString=True): del t["_original_end"] matchExpr.setParseAction(extractText) return matchExpr - + # convenience constants for positional expressions empty = Empty().setName("empty") lineStart = LineStart().setName("lineStart") @@ -3532,7 +3532,7 @@ def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString): ).setParseAction(lambda t:t[0].strip())) else: if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + + content = (Combine(OneOrMore(~ignoreExpr + ~Literal(opener) + ~Literal(closer) + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) ).setParseAction(lambda t:t[0].strip())) diff --git a/src/calibre/utils/rss_gen.py b/src/calibre/utils/rss_gen.py index fc1f1cf245..125b6d4eca 100644 --- a/src/calibre/utils/rss_gen.py +++ b/src/calibre/utils/rss_gen.py @@ -20,6 +20,7 @@ class WriteXmlMixin: def to_xml(self, encoding = "iso-8859-1"): try: import cStringIO as StringIO + StringIO except ImportError: import StringIO f = StringIO.StringIO() @@ -64,7 +65,7 @@ def _format_date(dt): "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1], dt.year, dt.hour, dt.minute, dt.second) - + ## # A couple simple wrapper objects for the fields which # take a simple value other than a string. @@ -72,7 +73,7 @@ class IntElement: """implements the 'publish' API for integers Takes the tag name and the integer value to publish. - + (Could be used for anything which uses str() to be published to text for XML.) """ @@ -138,7 +139,7 @@ class Image: self.width = width self.height = height self.description = description - + def publish(self, handler): handler.startElement("image", self.element_attrs) @@ -150,7 +151,7 @@ class Image: if isinstance(width, int): width = IntElement("width", width) _opt_element(handler, "width", width) - + height = self.height if isinstance(height, int): height = IntElement("height", height) @@ -196,7 +197,7 @@ class TextInput: _element(handler, "name", self.name) _element(handler, "link", self.link) handler.endElement("textInput") - + class Enclosure: """Publish an enclosure""" @@ -255,7 +256,7 @@ class RSS2(WriteXmlMixin): Stores the channel attributes, with the "category" elements under ".categories" and the RSS items under ".items". """ - + rss_attrs = {"version": "2.0"} element_attrs = {} def __init__(self, @@ -269,7 +270,7 @@ class RSS2(WriteXmlMixin): webMaster = None, pubDate = None, # a datetime, *in* *GMT* lastBuildDate = None, # a datetime - + categories = None, # list of strings or Category generator = _generator_name, docs = "http://blogs.law.harvard.edu/tech/rss", @@ -294,7 +295,7 @@ class RSS2(WriteXmlMixin): self.webMaster = webMaster self.pubDate = pubDate self.lastBuildDate = lastBuildDate - + if categories is None: categories = [] self.categories = categories @@ -320,7 +321,7 @@ class RSS2(WriteXmlMixin): _element(handler, "description", self.description) self.publish_extensions(handler) - + _opt_element(handler, "language", self.language) _opt_element(handler, "copyright", self.copyright) _opt_element(handler, "managingEditor", self.managingEditor) @@ -374,8 +375,8 @@ class RSS2(WriteXmlMixin): # output after the three required fields. pass - - + + class RSSItem(WriteXmlMixin): """Publish an RSS Item""" element_attrs = {} @@ -391,7 +392,7 @@ class RSSItem(WriteXmlMixin): pubDate = None, # a datetime source = None, # a Source ): - + if title is None and description is None: raise TypeError( "must define at least one of 'title' or 'description'") @@ -421,7 +422,7 @@ class RSSItem(WriteXmlMixin): if isinstance(category, basestring): category = Category(category) category.publish(handler) - + _opt_element(handler, "comments", self.comments) if self.enclosure is not None: self.enclosure.publish(handler) @@ -434,7 +435,7 @@ class RSSItem(WriteXmlMixin): if self.source is not None: self.source.publish(handler) - + handler.endElement("item") def publish_extensions(self, handler): diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index adbc69c4e1..1513948bed 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -57,13 +57,13 @@ recipe_modules = ['recipe_' + r for r in ( 'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti', 'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga', 'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem', + 'the_new_republic', )] import re, imp, inspect, time, os from calibre.web.feeds.news import BasicNewsRecipe, CustomIndexRecipe, AutomaticNewsRecipe from calibre.ebooks.BeautifulSoup import BeautifulSoup -from calibre.path import path from calibre.ptempfile import PersistentTemporaryDirectory from calibre import __appname__, english_sort @@ -102,8 +102,8 @@ def compile_recipe(src): ''' global _tdir, _crep if _tdir is None or not os.path.exists(_tdir): - _tdir = path(PersistentTemporaryDirectory('_recipes')) - temp = _tdir/('recipe%d.py'%_crep) + _tdir = PersistentTemporaryDirectory('_recipes') + temp = os.path.join(_tdir, 'recipe%d.py'%_crep) _crep += 1 if not isinstance(src, unicode): match = re.search(r'coding[:=]\s*([-\w.]+)', src[:200]) @@ -118,8 +118,9 @@ def compile_recipe(src): src = src.replace('from libprs500', 'from calibre').encode('utf-8') f.write(src) f.close() - module = imp.find_module(temp.namebase, [temp.dirname()]) - module = imp.load_module(temp.namebase, *module) + module = imp.find_module(os.path.splitext(os.path.basename(temp))[0], + [os.path.dirname(temp)]) + module = imp.load_module(os.path.splitext(os.path.basename(temp))[0], *module) classes = inspect.getmembers(module, lambda x : inspect.isclass(x) and \ issubclass(x, (BasicNewsRecipe,)) and \ @@ -148,6 +149,7 @@ _titles.sort(cmp=english_sort) titles = _titles def migrate_automatic_profile_to_automatic_recipe(profile): + BeautifulSoup oprofile = profile profile = compile_recipe(profile) if 'BasicUserProfile' not in profile.__name__: @@ -165,3 +167,4 @@ class BasicUserRecipe%d(AutomaticNewsRecipe): '''%(int(time.time()), repr(profile.title), profile.oldest_article, profile.max_articles_per_feed, profile.summary_length, repr(profile.feeds)) + diff --git a/src/calibre/web/feeds/recipes/recipe_24sata.py b/src/calibre/web/feeds/recipes/recipe_24sata.py index 637d0ce626..5fdc405950 100644 --- a/src/calibre/web/feeds/recipes/recipe_24sata.py +++ b/src/calibre/web/feeds/recipes/recipe_24sata.py @@ -1,61 +1,61 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' - -''' -24sata.hr -''' - -import re -from calibre.web.feeds.recipes import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag - -class Cro24Sata(BasicNewsRecipe): - title = '24 Sata - Hr' - __author__ = 'Darko Miletic' - description = "News Portal from Croatia" - publisher = '24sata.hr' - category = 'news, politics, Croatia' - oldest_article = 2 - max_articles_per_feed = 100 - delay = 4 - no_stylesheets = True - encoding = 'utf-8' - use_embedded_content = False +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' + +''' +24sata.hr +''' + +import re +from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag + +class Cro24Sata(BasicNewsRecipe): + title = '24 Sata - Hr' + __author__ = 'Darko Miletic' + description = "News Portal from Croatia" + publisher = '24sata.hr' + category = 'news, politics, Croatia' + oldest_article = 2 + max_articles_per_feed = 100 + delay = 4 + no_stylesheets = True + encoding = 'utf-8' + use_embedded_content = False language = 'hr' - - lang = 'hr-HR' - - extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' - - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : lang - , 'pretty_print' : True - } - - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - remove_tags = [ - dict(name=['object','link','embed']) - ,dict(name='table', attrs={'class':'enumbox'}) - ] - - feeds = [(u'Najnovije Vijesti', u'http://www.24sata.hr/index.php?cmd=show_rss&action=novo')] - - def preprocess_html(self, soup): - soup.html['lang'] = self.lang - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")]) - soup.head.insert(0,mlang) - soup.head.insert(1,mcharset) - for item in soup.findAll(style=True): - del item['style'] - return soup - - def print_version(self, url): - return url + '&action=ispis' - \ No newline at end of file + + lang = 'hr-HR' + + extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : lang + , 'pretty_print' : True + } + + preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + + remove_tags = [ + dict(name=['object','link','embed']) + ,dict(name='table', attrs={'class':'enumbox'}) + ] + + feeds = [(u'Najnovije Vijesti', u'http://www.24sata.hr/index.php?cmd=show_rss&action=novo')] + + def preprocess_html(self, soup): + soup.html['lang'] = self.lang + mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) + mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")]) + soup.head.insert(0,mlang) + soup.head.insert(1,mcharset) + for item in soup.findAll(style=True): + del item['style'] + return soup + + def print_version(self, url): + return url + '&action=ispis' + diff --git a/src/calibre/web/feeds/recipes/recipe_24sata_rs.py b/src/calibre/web/feeds/recipes/recipe_24sata_rs.py index 9c14527a8b..b306c3ee6c 100644 --- a/src/calibre/web/feeds/recipes/recipe_24sata_rs.py +++ b/src/calibre/web/feeds/recipes/recipe_24sata_rs.py @@ -1,68 +1,68 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' - -''' -24sata.rs -''' - -import re -from calibre.web.feeds.recipes import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag - -class Ser24Sata(BasicNewsRecipe): - title = '24 Sata - Sr' - __author__ = 'Darko Miletic' - description = '24 sata portal vesti iz Srbije' - publisher = 'Ringier d.o.o.' - category = 'news, politics, entertainment, Serbia' - oldest_article = 7 - max_articles_per_feed = 100 - no_stylesheets = True - encoding = 'utf-8' - use_embedded_content = False +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' + +''' +24sata.rs +''' + +import re +from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag + +class Ser24Sata(BasicNewsRecipe): + title = '24 Sata - Sr' + __author__ = 'Darko Miletic' + description = '24 sata portal vesti iz Srbije' + publisher = 'Ringier d.o.o.' + category = 'news, politics, entertainment, Serbia' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + encoding = 'utf-8' + use_embedded_content = False language = 'sr' - - lang = 'sr-Latn-RS' - extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' - - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : lang - , 'pretty_print' : True - } - - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - feeds = [(u'Vesti Dana', u'http://www.24sata.rs/rss.php')] - - def preprocess_html(self, soup): - soup.html['xml:lang'] = self.lang - soup.html['lang'] = self.lang - - attribs = [ 'style','font','valign' - ,'colspan','width','height' - ,'rowspan','summary','align' - ,'cellspacing','cellpadding' - ,'frames','rules','border' - ] - for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']): - item.name = 'div' - for attrib in attribs: - if item.has_key(attrib): - del item[attrib] - - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")]) - soup.head.insert(0,mlang) - soup.head.insert(1,mcharset) - return self.adeify_images(soup) - - def print_version(self, url): - article = url.partition('#')[0] - article_id = article.partition('id=')[2] - return 'http://www.24sata.rs/_print.php?id=' + article_id - + + lang = 'sr-Latn-RS' + extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : lang + , 'pretty_print' : True + } + + preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + + feeds = [(u'Vesti Dana', u'http://www.24sata.rs/rss.php')] + + def preprocess_html(self, soup): + soup.html['xml:lang'] = self.lang + soup.html['lang'] = self.lang + + attribs = [ 'style','font','valign' + ,'colspan','width','height' + ,'rowspan','summary','align' + ,'cellspacing','cellpadding' + ,'frames','rules','border' + ] + for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']): + item.name = 'div' + for attrib in attribs: + if item.has_key(attrib): + del item[attrib] + + mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) + mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")]) + soup.head.insert(0,mlang) + soup.head.insert(1,mcharset) + return self.adeify_images(soup) + + def print_version(self, url): + article = url.partition('#')[0] + article_id = article.partition('id=')[2] + return 'http://www.24sata.rs/_print.php?id=' + article_id + diff --git a/src/calibre/web/feeds/recipes/recipe_7dias.py b/src/calibre/web/feeds/recipes/recipe_7dias.py index 2507687677..e111617b8d 100644 --- a/src/calibre/web/feeds/recipes/recipe_7dias.py +++ b/src/calibre/web/feeds/recipes/recipe_7dias.py @@ -1,72 +1,72 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' -''' -elargentino.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import Tag - -class SieteDias(BasicNewsRecipe): - title = '7 dias' - __author__ = 'Darko Miletic' - description = 'Revista Argentina' - publisher = 'ElArgentino.com' - category = 'news, politics, show, Argentina' - oldest_article = 7 - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - encoding = 'utf-8' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +elargentino.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag + +class SieteDias(BasicNewsRecipe): + title = '7 dias' + __author__ = 'Darko Miletic' + description = 'Revista Argentina' + publisher = 'ElArgentino.com' + category = 'news, politics, show, Argentina' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' language = 'es' - - lang = 'es-AR' - direction = 'ltr' - INDEX = 'http://www.elargentino.com/medios/125/7-Dias.html' - extra_css = ' .titulo{font-size: x-large; font-weight: bold} .volantaImp{font-size: small; font-weight: bold} ' - - html2lrf_options = [ - '--comment' , description - , '--category' , category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0cm; margin-top: 0em; margin-bottom: 0.5em} "' - - keep_only_tags = [dict(name='div', attrs={'class':'ContainerPop'})] - - remove_tags = [dict(name='link')] - - feeds = [(u'Articulos', u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=125&Content-Type=text/xml&ChannelDesc=7%20D%C3%ADas')] - - def print_version(self, url): - main, sep, article_part = url.partition('/nota-') - article_id, rsep, rrest = article_part.partition('-') - return u'http://www.elargentino.com/Impresion.aspx?Id=' + article_id - - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - soup.html['lang'] = self.lang - soup.html['dir' ] = self.direction - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")]) - soup.head.insert(0,mlang) - soup.head.insert(1,mcharset) - return soup - - def get_cover_url(self): - cover_url = None - soup = self.index_to_soup(self.INDEX) - cover_item = soup.find('div',attrs={'class':'colder'}) - if cover_item: - clean_url = self.image_url_processor(None,cover_item.div.img['src']) - cover_url = 'http://www.elargentino.com' + clean_url + '&height=600' - return cover_url - - def image_url_processor(self, baseurl, url): - base, sep, rest = url.rpartition('?Id=') - img, sep2, rrest = rest.partition('&') - return base + sep + img + + lang = 'es-AR' + direction = 'ltr' + INDEX = 'http://www.elargentino.com/medios/125/7-Dias.html' + extra_css = ' .titulo{font-size: x-large; font-weight: bold} .volantaImp{font-size: small; font-weight: bold} ' + + html2lrf_options = [ + '--comment' , description + , '--category' , category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0cm; margin-top: 0em; margin-bottom: 0.5em} "' + + keep_only_tags = [dict(name='div', attrs={'class':'ContainerPop'})] + + remove_tags = [dict(name='link')] + + feeds = [(u'Articulos', u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=125&Content-Type=text/xml&ChannelDesc=7%20D%C3%ADas')] + + def print_version(self, url): + main, sep, article_part = url.partition('/nota-') + article_id, rsep, rrest = article_part.partition('-') + return u'http://www.elargentino.com/Impresion.aspx?Id=' + article_id + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + soup.html['lang'] = self.lang + soup.html['dir' ] = self.direction + mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) + mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")]) + soup.head.insert(0,mlang) + soup.head.insert(1,mcharset) + return soup + + def get_cover_url(self): + cover_url = None + soup = self.index_to_soup(self.INDEX) + cover_item = soup.find('div',attrs={'class':'colder'}) + if cover_item: + clean_url = self.image_url_processor(None,cover_item.div.img['src']) + cover_url = 'http://www.elargentino.com' + clean_url + '&height=600' + return cover_url + + def image_url_processor(self, baseurl, url): + base, sep, rest = url.rpartition('?Id=') + img, sep2, rrest = rest.partition('&') + return base + sep + img diff --git a/src/calibre/web/feeds/recipes/recipe_accountancyage.py b/src/calibre/web/feeds/recipes/recipe_accountancyage.py index b6be176083..a7264499c1 100644 --- a/src/calibre/web/feeds/recipes/recipe_accountancyage.py +++ b/src/calibre/web/feeds/recipes/recipe_accountancyage.py @@ -1,59 +1,59 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' -''' -www.accountancyage.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import Tag - -class AccountancyAge(BasicNewsRecipe): - title = 'Accountancy Age' - __author__ = 'Darko Miletic' - description = 'business news' - publisher = 'accountancyage.com' - category = 'news, politics, finances' - oldest_article = 2 - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - simultaneous_downloads = 1 - encoding = 'utf-8' - lang = 'en' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008-2009, Darko Miletic ' +''' +www.accountancyage.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag + +class AccountancyAge(BasicNewsRecipe): + title = 'Accountancy Age' + __author__ = 'Darko Miletic' + description = 'business news' + publisher = 'accountancyage.com' + category = 'news, politics, finances' + oldest_article = 2 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + simultaneous_downloads = 1 + encoding = 'utf-8' + lang = 'en' language = 'en' - - - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - keep_only_tags = [dict(name='div', attrs={'class':'bodycol'})] - remove_tags = [dict(name=['embed','object'])] - remove_tags_after = dict(name='div', attrs={'id':'permalink'}) - remove_tags_before = dict(name='div', attrs={'class':'gap6'}) - - feeds = [(u'All News', u'http://feeds.accountancyage.com/rss/latest/accountancyage/all')] - - def print_version(self, url): - rest, sep, miss = url.rpartition('/') - rr, ssep, artid = rest.rpartition('/') - return u'http://www.accountancyage.com/articles/print/' + artid - - def get_article_url(self, article): - return article.get('guid', None) - - def preprocess_html(self, soup): - soup.html['xml:lang'] = self.lang - soup.html['lang'] = self.lang - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")]) - soup.head.insert(0,mlang) - soup.head.insert(1,mcharset) - return self.adeify_images(soup) - + + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + keep_only_tags = [dict(name='div', attrs={'class':'bodycol'})] + remove_tags = [dict(name=['embed','object'])] + remove_tags_after = dict(name='div', attrs={'id':'permalink'}) + remove_tags_before = dict(name='div', attrs={'class':'gap6'}) + + feeds = [(u'All News', u'http://feeds.accountancyage.com/rss/latest/accountancyage/all')] + + def print_version(self, url): + rest, sep, miss = url.rpartition('/') + rr, ssep, artid = rest.rpartition('/') + return u'http://www.accountancyage.com/articles/print/' + artid + + def get_article_url(self, article): + return article.get('guid', None) + + def preprocess_html(self, soup): + soup.html['xml:lang'] = self.lang + soup.html['lang'] = self.lang + mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) + mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")]) + soup.head.insert(0,mlang) + soup.head.insert(1,mcharset) + return self.adeify_images(soup) + diff --git a/src/calibre/web/feeds/recipes/recipe_adventuregamers.py b/src/calibre/web/feeds/recipes/recipe_adventuregamers.py index 86e741c441..1cde045953 100644 --- a/src/calibre/web/feeds/recipes/recipe_adventuregamers.py +++ b/src/calibre/web/feeds/recipes/recipe_adventuregamers.py @@ -1,77 +1,77 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' -''' -www.adventuregamers.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class AdventureGamers(BasicNewsRecipe): - title = u'Adventure Gamers' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.adventuregamers.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class AdventureGamers(BasicNewsRecipe): + title = u'Adventure Gamers' language = 'en' - - __author__ = 'Darko Miletic' - description = 'Adventure games portal' - publisher = 'Adventure Gamers' - category = 'news, games, adventure, technology' + + __author__ = 'Darko Miletic' + description = 'Adventure games portal' + publisher = 'Adventure Gamers' + category = 'news, games, adventure, technology' language = 'en' - - oldest_article = 10 - delay = 10 - max_articles_per_feed = 100 - no_stylesheets = True - encoding = 'cp1252' - remove_javascript = True - use_embedded_content = False - INDEX = u'http://www.adventuregamers.com' - - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - keep_only_tags = [ - dict(name='div', attrs={'class':'content_middle'}) - ] - - remove_tags = [ - dict(name=['object','link','embed','form']) - ,dict(name='div', attrs={'class':['related-stories','article_leadout','prev','next','both']}) - ] - - remove_tags_after = [dict(name='div', attrs={'class':'toolbar_fat'})] - - feeds = [(u'Articles', u'http://feeds2.feedburner.com/AdventureGamers')] - - def get_article_url(self, article): - return article.get('guid', None) - - def append_page(self, soup, appendtag, position): - pager = soup.find('div',attrs={'class':'toolbar_fat_next'}) - if pager: - nexturl = self.INDEX + pager.a['href'] - soup2 = self.index_to_soup(nexturl) - texttag = soup2.find('div', attrs={'class':'bodytext'}) - for it in texttag.findAll(style=True): - del it['style'] - newpos = len(texttag.contents) - self.append_page(soup2,texttag,newpos) - texttag.extract() - appendtag.insert(position,texttag) - - - def preprocess_html(self, soup): - mtag = '\n' - soup.head.insert(0,mtag) - for item in soup.findAll(style=True): - del item['style'] - self.append_page(soup, soup.body, 3) - pager = soup.find('div',attrs={'class':'toolbar_fat'}) - if pager: - pager.extract() - return soup + + oldest_article = 10 + delay = 10 + max_articles_per_feed = 100 + no_stylesheets = True + encoding = 'cp1252' + remove_javascript = True + use_embedded_content = False + INDEX = u'http://www.adventuregamers.com' + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + keep_only_tags = [ + dict(name='div', attrs={'class':'content_middle'}) + ] + + remove_tags = [ + dict(name=['object','link','embed','form']) + ,dict(name='div', attrs={'class':['related-stories','article_leadout','prev','next','both']}) + ] + + remove_tags_after = [dict(name='div', attrs={'class':'toolbar_fat'})] + + feeds = [(u'Articles', u'http://feeds2.feedburner.com/AdventureGamers')] + + def get_article_url(self, article): + return article.get('guid', None) + + def append_page(self, soup, appendtag, position): + pager = soup.find('div',attrs={'class':'toolbar_fat_next'}) + if pager: + nexturl = self.INDEX + pager.a['href'] + soup2 = self.index_to_soup(nexturl) + texttag = soup2.find('div', attrs={'class':'bodytext'}) + for it in texttag.findAll(style=True): + del it['style'] + newpos = len(texttag.contents) + self.append_page(soup2,texttag,newpos) + texttag.extract() + appendtag.insert(position,texttag) + + + def preprocess_html(self, soup): + mtag = '\n' + soup.head.insert(0,mtag) + for item in soup.findAll(style=True): + del item['style'] + self.append_page(soup, soup.body, 3) + pager = soup.find('div',attrs={'class':'toolbar_fat'}) + if pager: + pager.extract() + return soup diff --git a/src/calibre/web/feeds/recipes/recipe_ambito.py b/src/calibre/web/feeds/recipes/recipe_ambito.py index f0fb73e873..7074463e34 100644 --- a/src/calibre/web/feeds/recipes/recipe_ambito.py +++ b/src/calibre/web/feeds/recipes/recipe_ambito.py @@ -1,62 +1,61 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' -''' -ambito.com -''' - +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008-2009, Darko Miletic ' +''' +ambito.com +''' + from calibre.web.feeds.news import BasicNewsRecipe -class Ambito(BasicNewsRecipe): - title = 'Ambito.com' - __author__ = 'Darko Miletic' - description = 'Informacion Libre las 24 horas' - publisher = 'Ambito.com' - category = 'news, politics, Argentina' - oldest_article = 2 - max_articles_per_feed = 100 - no_stylesheets = True - encoding = 'iso-8859-1' - cover_url = 'http://www.ambito.com/img/logo_.jpg' - remove_javascript = True - use_embedded_content = False - - html2lrf_options = [ +class Ambito(BasicNewsRecipe): + title = 'Ambito.com' + __author__ = 'Darko Miletic' + description = 'Informacion Libre las 24 horas' + publisher = 'Ambito.com' + category = 'news, politics, Argentina' + oldest_article = 2 + max_articles_per_feed = 100 + no_stylesheets = True + encoding = 'iso-8859-1' + cover_url = 'http://www.ambito.com/img/logo_.jpg' + remove_javascript = True + use_embedded_content = False + + html2lrf_options = [ '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - keep_only_tags = [dict(name='div', attrs={'align':'justify'})] - - remove_tags = [dict(name=['object','link'])] - - feeds = [ - (u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' ) - ,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' ) - ,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' ) - ,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General') - ,(u'Agro' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' ) - ,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' ) - ,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' ) - ,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' ) - ,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnologia' ) - ,(u'Salud' , u'http://www.ambito.com/rss/noticias.asp?S=Salud' ) - ,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' ) - ] - - def print_version(self, url): - return url.replace('http://www.ambito.com/noticia.asp?','http://www.ambito.com/noticias/imprimir.asp?') - - def preprocess_html(self, soup): - mtag = '' - soup.head.insert(0,mtag) - for item in soup.findAll(style=True): - del item['style'] - return soup - + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + keep_only_tags = [dict(name='div', attrs={'align':'justify'})] + + remove_tags = [dict(name=['object','link'])] + + feeds = [ + (u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' ) + ,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' ) + ,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' ) + ,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General') + ,(u'Agro' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' ) + ,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' ) + ,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' ) + ,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' ) + ,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnologia' ) + ,(u'Salud' , u'http://www.ambito.com/rss/noticias.asp?S=Salud' ) + ,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' ) + ] + + def print_version(self, url): + return url.replace('http://www.ambito.com/noticia.asp?','http://www.ambito.com/noticias/imprimir.asp?') + + def preprocess_html(self, soup): + mtag = '' + soup.head.insert(0,mtag) + for item in soup.findAll(style=True): + del item['style'] + return soup + language = 'es' - \ No newline at end of file diff --git a/src/calibre/web/feeds/recipes/recipe_amspec.py b/src/calibre/web/feeds/recipes/recipe_amspec.py index 967e4a542a..62bec5ae18 100644 --- a/src/calibre/web/feeds/recipes/recipe_amspec.py +++ b/src/calibre/web/feeds/recipes/recipe_amspec.py @@ -1,55 +1,55 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' -''' -spectator.org -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class TheAmericanSpectator(BasicNewsRecipe): - title = 'The American Spectator' - __author__ = 'Darko Miletic' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +spectator.org +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheAmericanSpectator(BasicNewsRecipe): + title = 'The American Spectator' + __author__ = 'Darko Miletic' language = 'en' - - description = 'News from USA' - oldest_article = 7 - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - INDEX = 'http://spectator.org' - - html2lrf_options = [ - '--comment' , description - , '--category' , 'news, politics, USA' - , '--publisher' , title - ] - - keep_only_tags = [ - dict(name='div', attrs={'class':'post inner'}) - ,dict(name='div', attrs={'class':'author-bio'}) - ] - - remove_tags = [ - dict(name='object') - ,dict(name='div', attrs={'class':'col3' }) - ,dict(name='div', attrs={'class':'post-options' }) - ,dict(name='p' , attrs={'class':'letter-editor'}) - ,dict(name='div', attrs={'class':'social' }) - ] - - feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')] - - def get_cover_url(self): - cover_url = None - soup = self.index_to_soup(self.INDEX) - link_item = soup.find('a',attrs={'class':'cover'}) - if link_item: - soup2 = self.index_to_soup(link_item['href']) - link_item2 = soup2.find('div',attrs={'class':'post inner issues'}) - cover_url = self.INDEX + link_item2.img['src'] - return cover_url - - def print_version(self, url): - return url + '/print' + + description = 'News from USA' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + INDEX = 'http://spectator.org' + + html2lrf_options = [ + '--comment' , description + , '--category' , 'news, politics, USA' + , '--publisher' , title + ] + + keep_only_tags = [ + dict(name='div', attrs={'class':'post inner'}) + ,dict(name='div', attrs={'class':'author-bio'}) + ] + + remove_tags = [ + dict(name='object') + ,dict(name='div', attrs={'class':'col3' }) + ,dict(name='div', attrs={'class':'post-options' }) + ,dict(name='p' , attrs={'class':'letter-editor'}) + ,dict(name='div', attrs={'class':'social' }) + ] + + feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')] + + def get_cover_url(self): + cover_url = None + soup = self.index_to_soup(self.INDEX) + link_item = soup.find('a',attrs={'class':'cover'}) + if link_item: + soup2 = self.index_to_soup(link_item['href']) + link_item2 = soup2.find('div',attrs={'class':'post inner issues'}) + cover_url = self.INDEX + link_item2.img['src'] + return cover_url + + def print_version(self, url): + return url + '/print' diff --git a/src/calibre/web/feeds/recipes/recipe_axxon_news.py b/src/calibre/web/feeds/recipes/recipe_axxon_news.py index ec5d260aed..a9a99e1de1 100644 --- a/src/calibre/web/feeds/recipes/recipe_axxon_news.py +++ b/src/calibre/web/feeds/recipes/recipe_axxon_news.py @@ -1,62 +1,62 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' -''' -axxon.com.ar -''' -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import Tag - -class Axxon_news(BasicNewsRecipe): - title = 'Axxon noticias' - __author__ = 'Darko Miletic' - description = 'Axxon, Ciencia Ficcion en Bits' - publisher = 'Axxon' - category = 'news, SF, Argentina, science, movies' - oldest_article = 7 - max_articles_per_feed = 100 - no_stylesheets = False - use_embedded_content = False +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +axxon.com.ar +''' +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag + +class Axxon_news(BasicNewsRecipe): + title = 'Axxon noticias' + __author__ = 'Darko Miletic' + description = 'Axxon, Ciencia Ficcion en Bits' + publisher = 'Axxon' + category = 'news, SF, Argentina, science, movies' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = False + use_embedded_content = False language = 'es' - - lang = 'es-AR' - - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : lang - , 'pretty_print' : True - } - - - keep_only_tags = [dict(name='div', attrs={'class':'post'})] - - remove_tags = [dict(name=['object','link','iframe','embed'])] - - feeds = [(u'Noticias', u'http://axxon.com.ar/noticias/feed/')] - - remove_attributes = ['style','width','height','font','border','align'] - - - def adeify_images2(cls, soup): - for item in soup.findAll('img'): - for attrib in ['height','width','border','align','style']: - if item.has_key(attrib): - del item[attrib] - oldParent = item.parent - if oldParent.name == 'a': - oldParent.name == 'p' - myIndex = oldParent.contents.index(item) - brtag = Tag(soup,'br') - oldParent.insert(myIndex+1,brtag) - return soup - - def preprocess_html(self, soup): - soup.html['xml:lang'] = self.lang - soup.html['lang'] = self.lang - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - soup.html.insert(0,mlang) - return self.adeify_images2(soup) - + + lang = 'es-AR' + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : lang + , 'pretty_print' : True + } + + + keep_only_tags = [dict(name='div', attrs={'class':'post'})] + + remove_tags = [dict(name=['object','link','iframe','embed'])] + + feeds = [(u'Noticias', u'http://axxon.com.ar/noticias/feed/')] + + remove_attributes = ['style','width','height','font','border','align'] + + + def adeify_images2(cls, soup): + for item in soup.findAll('img'): + for attrib in ['height','width','border','align','style']: + if item.has_key(attrib): + del item[attrib] + oldParent = item.parent + if oldParent.name == 'a': + oldParent.name == 'p' + myIndex = oldParent.contents.index(item) + brtag = Tag(soup,'br') + oldParent.insert(myIndex+1,brtag) + return soup + + def preprocess_html(self, soup): + soup.html['xml:lang'] = self.lang + soup.html['lang'] = self.lang + mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) + soup.html.insert(0,mlang) + return self.adeify_images2(soup) + diff --git a/src/calibre/web/feeds/recipes/recipe_azstarnet.py b/src/calibre/web/feeds/recipes/recipe_azstarnet.py index 391f21ef56..9b18081598 100644 --- a/src/calibre/web/feeds/recipes/recipe_azstarnet.py +++ b/src/calibre/web/feeds/recipes/recipe_azstarnet.py @@ -1,65 +1,65 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' -''' -www.azstarnet.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class Azstarnet(BasicNewsRecipe): - title = 'Arizona Daily Star' - __author__ = 'Darko Miletic' - description = 'news from Arizona' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.azstarnet.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Azstarnet(BasicNewsRecipe): + title = 'Arizona Daily Star' + __author__ = 'Darko Miletic' + description = 'news from Arizona' language = 'en' - - publisher = 'azstarnet.com' - category = 'news, politics, Arizona, USA' - delay = 1 - oldest_article = 1 - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - encoding = 'utf-8' - needs_subscription = True - remove_javascript = True - - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - def get_browser(self): - br = BasicNewsRecipe.get_browser() - if self.username is not None and self.password is not None: - br.open('http://azstarnet.com/registration/retro.php') - br.select_form(nr=1) - br['email'] = self.username - br['pass' ] = self.password - br.submit() - return br - - - keep_only_tags = [dict(name='div', attrs={'id':'storycontent'})] - - remove_tags = [ - dict(name=['object','link','iframe','base','img']) - ,dict(name='div',attrs={'class':'bannerinstory'}) - ] - - - feeds = [(u'Tucson Region', u'http://rss.azstarnet.com/index.php?site=metro')] - - def preprocess_html(self, soup): - soup.html['dir' ] = 'ltr' - soup.html['lang'] = 'en-US' - mtag = '\n\n\n' - soup.head.insert(0,mtag) - for item in soup.findAll(style=True): - del item['style'] - return soup - + + publisher = 'azstarnet.com' + category = 'news, politics, Arizona, USA' + delay = 1 + oldest_article = 1 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + needs_subscription = True + remove_javascript = True + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://azstarnet.com/registration/retro.php') + br.select_form(nr=1) + br['email'] = self.username + br['pass' ] = self.password + br.submit() + return br + + + keep_only_tags = [dict(name='div', attrs={'id':'storycontent'})] + + remove_tags = [ + dict(name=['object','link','iframe','base','img']) + ,dict(name='div',attrs={'class':'bannerinstory'}) + ] + + + feeds = [(u'Tucson Region', u'http://rss.azstarnet.com/index.php?site=metro')] + + def preprocess_html(self, soup): + soup.html['dir' ] = 'ltr' + soup.html['lang'] = 'en-US' + mtag = '\n\n\n' + soup.head.insert(0,mtag) + for item in soup.findAll(style=True): + del item['style'] + return soup + diff --git a/src/calibre/web/feeds/recipes/recipe_b92.py b/src/calibre/web/feeds/recipes/recipe_b92.py index decb5d898b..612aee4d67 100644 --- a/src/calibre/web/feeds/recipes/recipe_b92.py +++ b/src/calibre/web/feeds/recipes/recipe_b92.py @@ -1,69 +1,69 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' -''' -b92.net -''' -import re -from calibre.web.feeds.news import BasicNewsRecipe - -class B92(BasicNewsRecipe): - title = 'B92' - __author__ = 'Darko Miletic' - description = 'Dnevne vesti iz Srbije i sveta' - publisher = 'B92' - category = 'news, politics, Serbia' - oldest_article = 2 - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - encoding = 'cp1250' +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008-2009, Darko Miletic ' +''' +b92.net +''' +import re +from calibre.web.feeds.news import BasicNewsRecipe + +class B92(BasicNewsRecipe): + title = 'B92' + __author__ = 'Darko Miletic' + description = 'Dnevne vesti iz Srbije i sveta' + publisher = 'B92' + category = 'news, politics, Serbia' + oldest_article = 2 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + encoding = 'cp1250' language = 'sr' - - lang = 'sr-Latn-RS' - extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' - - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : lang - } - - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - keep_only_tags = [dict(name='table', attrs={'class':'maindocument'})] - - remove_tags = [ - dict(name='ul', attrs={'class':'comment-nav'}) - ,dict(name=['embed','link','base'] ) - ,dict(name='div', attrs={'class':'udokum'} ) - ] - - feeds = [ - (u'Vesti', u'http://www.b92.net/info/rss/vesti.xml') - ,(u'Biz' , u'http://www.b92.net/info/rss/biz.xml' ) - ] - - def print_version(self, url): - return url + '&version=print' - - def preprocess_html(self, soup): - del soup.body['onload'] - for item in soup.findAll('font'): - item.name='div' - if item.has_key('size'): - del item['size'] - attribs = [ 'style','font','valign' - ,'colspan','width','height' - ,'rowspan','summary','align' - ,'cellspacing','cellpadding' - ,'frames','rules','border' - ] - for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']): - item.name = 'div' - for attrib in attribs: - if item.has_key(attrib): - del item[attrib] - return soup + + lang = 'sr-Latn-RS' + extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}' + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : lang + } + + preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + + keep_only_tags = [dict(name='table', attrs={'class':'maindocument'})] + + remove_tags = [ + dict(name='ul', attrs={'class':'comment-nav'}) + ,dict(name=['embed','link','base'] ) + ,dict(name='div', attrs={'class':'udokum'} ) + ] + + feeds = [ + (u'Vesti', u'http://www.b92.net/info/rss/vesti.xml') + ,(u'Biz' , u'http://www.b92.net/info/rss/biz.xml' ) + ] + + def print_version(self, url): + return url + '&version=print' + + def preprocess_html(self, soup): + del soup.body['onload'] + for item in soup.findAll('font'): + item.name='div' + if item.has_key('size'): + del item['size'] + attribs = [ 'style','font','valign' + ,'colspan','width','height' + ,'rowspan','summary','align' + ,'cellspacing','cellpadding' + ,'frames','rules','border' + ] + for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']): + item.name = 'div' + for attrib in attribs: + if item.has_key(attrib): + del item[attrib] + return soup diff --git a/src/calibre/web/feeds/recipes/recipe_barrons.py b/src/calibre/web/feeds/recipes/recipe_barrons.py index 3e0e4a64ca..8040fcc11f 100644 --- a/src/calibre/web/feeds/recipes/recipe_barrons.py +++ b/src/calibre/web/feeds/recipes/recipe_barrons.py @@ -1,93 +1,93 @@ -## -## web2lrf profile to download articles from Barrons.com -## can download subscriber-only content if username and -## password are supplied. -## -''' -''' - -import re - -from calibre.web.feeds.news import BasicNewsRecipe - -class Barrons(BasicNewsRecipe): - - title = 'Barron\'s' - max_articles_per_feed = 50 - needs_subscription = True +## +## web2lrf profile to download articles from Barrons.com +## can download subscriber-only content if username and +## password are supplied. +## +''' +''' + +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class Barrons(BasicNewsRecipe): + + title = 'Barron\'s' + max_articles_per_feed = 50 + needs_subscription = True language = 'en' - - __author__ = 'Kovid Goyal' - description = 'Weekly publication for investors from the publisher of the Wall Street Journal' - timefmt = ' [%a, %b %d, %Y]' - use_embedded_content = False - no_stylesheets = False - match_regexps = ['http://online.barrons.com/.*?html\?mod=.*?|file:.*'] - conversion_options = {'linearize_tables': True} - ##delay = 1 - - ## Don't grab articles more than 7 days old - oldest_article = 7 - - - preprocess_regexps = [(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in - [ - ## Remove anything before the body of the article. - (r'))', lambda match: '
'), - - ## Remove any links/ads/comments/cruft from the end of the body of the article. - (r'(()|(
)|(

©)|(