Implement a --add-simple-plugin option for calibre-debug that makes it easy to add calibre plugins distributed as .py files

This commit is contained in:
Kovid Goyal 2009-04-12 10:25:17 -07:00
parent ffa6c73c8d
commit 2708065870
2 changed files with 60 additions and 30 deletions

View File

@ -2,7 +2,7 @@ from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, shutil, traceback, functools, sys import os, shutil, traceback, functools, sys, re
from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \ from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \
MetadataWriterPlugin MetadataWriterPlugin
@ -29,7 +29,7 @@ def _config():
c.add_opt('filetype_mapping', default={}, help=_('Mapping for filetype plugins')) c.add_opt('filetype_mapping', default={}, help=_('Mapping for filetype plugins'))
c.add_opt('plugin_customization', default={}, help=_('Local plugin customization')) c.add_opt('plugin_customization', default={}, help=_('Local plugin customization'))
c.add_opt('disabled_plugins', default=set([]), help=_('Disabled plugins')) c.add_opt('disabled_plugins', default=set([]), help=_('Disabled plugins'))
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()
@ -44,7 +44,7 @@ class PluginNotFound(ValueError):
def load_plugin(path_to_zip_file): def load_plugin(path_to_zip_file):
''' '''
Load plugin from zip file or raise InvalidPlugin error Load plugin from zip file or raise InvalidPlugin error
:return: A :class:`Plugin` instance. :return: A :class:`Plugin` instance.
''' '''
print 'Loading plugin from', path_to_zip_file print 'Loading plugin from', path_to_zip_file
@ -54,15 +54,22 @@ def load_plugin(path_to_zip_file):
for name in zf.namelist(): for name in zf.namelist():
if name.lower().endswith('plugin.py'): if name.lower().endswith('plugin.py'):
locals = {} locals = {}
exec zf.read(name) in locals raw = zf.read(name)
match = re.search(r'coding[:=]\s*([-\w.]+)', raw[:300])
encoding = 'utf-8'
if match is not None:
encoding = match.group(1)
raw = raw.decode(encoding)
raw = re.sub('\r\n', '\n', raw)
exec raw in locals
for x in locals.values(): for x in locals.values():
if isinstance(x, type) and issubclass(x, Plugin): if isinstance(x, type) and issubclass(x, Plugin):
if x.minimum_calibre_version > version or \ if x.minimum_calibre_version > version or \
platform not in x.supported_platforms: platform not in x.supported_platforms:
continue continue
return x return x
raise InvalidPlugin(_('No valid plugin found in ')+path_to_zip_file) raise InvalidPlugin(_('No valid plugin found in ')+path_to_zip_file)
_initialized_plugins = [] _initialized_plugins = []
@ -112,10 +119,10 @@ def reread_metadata_plugins():
for ft in plugin.file_types: for ft in plugin.file_types:
if not _metadata_writers.has_key(ft): if not _metadata_writers.has_key(ft):
_metadata_writers[ft] = [] _metadata_writers[ft] = []
_metadata_writers[ft].append(plugin) _metadata_writers[ft].append(plugin)
def get_file_type_metadata(stream, ftype): def get_file_type_metadata(stream, ftype):
mi = MetaInformation(None, None) mi = MetaInformation(None, None)
ftype = ftype.lower().strip() ftype = ftype.lower().strip()
@ -141,21 +148,21 @@ def set_file_type_metadata(stream, mi, ftype):
plugin.set_metadata(stream, mi, ftype.lower().strip()) plugin.set_metadata(stream, mi, ftype.lower().strip())
break break
except: except:
print 'Failed to set metadata for', repr(getattr(mi, 'title', '')) print 'Failed to set metadata for', repr(getattr(mi, 'title', ''))
traceback.print_exc() traceback.print_exc()
def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'): def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'):
occasion = {'import':_on_import, 'preprocess':_on_preprocess, occasion = {'import':_on_import, 'preprocess':_on_preprocess,
'postprocess':_on_postprocess}[occasion] 'postprocess':_on_postprocess}[occasion]
customization = config['plugin_customization'] customization = config['plugin_customization']
if ft is None: if ft is None:
ft = os.path.splitext(path_to_file)[-1].lower().replace('.', '') ft = os.path.splitext(path_to_file)[-1].lower().replace('.', '')
nfp = path_to_file nfp = path_to_file
for plugin in occasion.get(ft, []): for plugin in occasion.get(ft, []):
if is_disabled(plugin): if is_disabled(plugin):
continue continue
plugin.site_customization = customization.get(plugin.name, '') plugin.site_customization = customization.get(plugin.name, '')
with plugin: with plugin:
try: try:
nfp = plugin.run(path_to_file) nfp = plugin.run(path_to_file)
@ -168,13 +175,13 @@ def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'):
nfp = path_to_file nfp = path_to_file
return nfp return nfp
run_plugins_on_import = functools.partial(_run_filetype_plugins, run_plugins_on_import = functools.partial(_run_filetype_plugins,
occasion='import') occasion='import')
run_plugins_on_preprocess = functools.partial(_run_filetype_plugins, run_plugins_on_preprocess = functools.partial(_run_filetype_plugins,
occasion='preprocess') occasion='preprocess')
run_plugins_on_postprocess = functools.partial(_run_filetype_plugins, run_plugins_on_postprocess = functools.partial(_run_filetype_plugins,
occasion='postprocess') occasion='postprocess')
def initialize_plugin(plugin, path_to_zip_file): def initialize_plugin(plugin, path_to_zip_file):
try: try:
@ -184,7 +191,7 @@ def initialize_plugin(plugin, path_to_zip_file):
tb = traceback.format_exc() tb = traceback.format_exc()
raise InvalidPlugin((_('Initialization of plugin %s failed with traceback:') raise InvalidPlugin((_('Initialization of plugin %s failed with traceback:')
%tb) + '\n'+tb) %tb) + '\n'+tb)
def add_plugin(path_to_zip_file): def add_plugin(path_to_zip_file):
make_config_dir() make_config_dir()
@ -252,21 +259,21 @@ def initialize_plugins():
except: except:
print 'Failed to initialize plugin...' print 'Failed to initialize plugin...'
traceback.print_exc() traceback.print_exc()
_initialized_plugins.sort(cmp=lambda x,y:cmp(x.priority, y.priority), reverse=True) _initialized_plugins.sort(cmp=lambda x,y:cmp(x.priority, y.priority), reverse=True)
reread_filetype_plugins() reread_filetype_plugins()
reread_metadata_plugins() reread_metadata_plugins()
initialize_plugins() initialize_plugins()
def option_parser(): def option_parser():
parser = OptionParser(usage=_('''\ parser = OptionParser(usage=_('''\
%prog options %prog options
Customize calibre by loading external plugins. Customize calibre by loading external plugins.
''')) '''))
parser.add_option('-a', '--add-plugin', default=None, parser.add_option('-a', '--add-plugin', default=None,
help=_('Add a plugin by specifying the path to the zip file containing it.')) help=_('Add a plugin by specifying the path to the zip file containing it.'))
parser.add_option('-r', '--remove-plugin', default=None, parser.add_option('-r', '--remove-plugin', default=None,
help=_('Remove a custom plugin by name. Has no effect on builtin plugins')) help=_('Remove a custom plugin by name. Has no effect on builtin plugins'))
parser.add_option('--customize-plugin', default=None, parser.add_option('--customize-plugin', default=None,
help=_('Customize plugin. Specify name of plugin and customization string separated by a comma.')) help=_('Customize plugin. Specify name of plugin and customization string separated by a comma.'))
@ -320,16 +327,16 @@ def main(args=sys.argv):
print print
for plugin in initialized_plugins(): for plugin in initialized_plugins():
print fmt%( print fmt%(
plugin.type, plugin.name, plugin.type, plugin.name,
plugin.version, is_disabled(plugin), plugin.version, is_disabled(plugin),
plugin_customization(plugin) plugin_customization(plugin)
) )
print '\t', plugin.description print '\t', plugin.description
if plugin.is_customizable(): if plugin.is_customizable():
print '\t', plugin.customization_help() print '\t', plugin.customization_help()
print print
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

View File

@ -31,6 +31,11 @@ Run an embedded python interpreter.
parser.add_option('--migrate', action='store_true', default=False, parser.add_option('--migrate', action='store_true', default=False,
help='Migrate old database. Needs two arguments. Path ' help='Migrate old database. Needs two arguments. Path '
'to library1.db and path to new library folder.') 'to library1.db and path to new library folder.')
parser.add_option('--add-simple-plugin', default=None,
help='Add a simple plugin (i.e. a plugin that consists of only a '
'.py file), by specifying the path to the py file containing the '
'plugin code.')
return parser return parser
def update_zipfile(zipfile, mod, path): def update_zipfile(zipfile, mod, path):
@ -115,6 +120,22 @@ def debug_device_driver():
print 'Total space:', d.total_space() print 'Total space:', d.total_space()
break break
def add_simple_plugin(path_to_plugin):
import tempfile, zipfile, shutil
tdir = tempfile.mkdtemp()
open(os.path.join(tdir, 'custom_plugin.py'),
'wb').write(open(path_to_plugin, 'rb').read())
odir = os.getcwd()
os.chdir(tdir)
zf = zipfile.ZipFile('plugin.zip', 'w')
zf.write('custom_plugin.py')
zf.close()
from calibre.customize.ui import main
main(['calibre-customize', '-a', 'plugin.zip'])
os.chdir(odir)
shutil.rmtree(tdir)
def main(args=sys.argv): def main(args=sys.argv):
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)
@ -137,6 +158,8 @@ def main(args=sys.argv):
print 'You must specify the path to library1.db and the path to the new library folder' print 'You must specify the path to library1.db and the path to the new library folder'
return 1 return 1
migrate(args[1], args[2]) migrate(args[1], args[2])
elif opts.add_simple_plugin is not None:
add_simple_plugin(opts.add_simple_plugin)
else: else:
from IPython.Shell import IPShellEmbed from IPython.Shell import IPShellEmbed
ipshell = IPShellEmbed() ipshell = IPShellEmbed()