diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 736ee2a940..21a4868f77 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -48,7 +48,7 @@ class Plugin(object): #: the plugins are run in order of decreasing priority #: i.e. plugins with higher priority will be run first. #: The highest possible priority is ``sys.maxint``. - #: Default pririty is 1. + #: Default priority is 1. priority = 1 #: The earliest version of calibre this plugin requires @@ -226,4 +226,55 @@ class MetadataWriterPlugin(Plugin): ''' pass +class CatalogPlugin(Plugin): + ''' + A plugin that implements a catalog generator. + ''' + + #: Output file type for which this plugin should be run + #: For example: 'epub' or 'xml' + file_types = set([]) + type = _('Catalog generator') + + #: CLI parser options specific to this plugin, declared as namedtuple Option + #: + #: from collections import namedtuple + #: Option = namedtuple('Option', 'option, default, dest, help') + #: cli_options = [Option('--catalog-title', + #: default = 'My Catalog', + #: dest = 'catalog_title', + #: help = (_('Title of generated catalog. \nDefault:') + " '" + + #: '%default' + "'"))] + + cli_options = [] + + def run(self, path_to_output, opts, log): + ''' + Run the plugin. Must be implemented in subclasses. + It should generate the catalog in the format specified + in file_types, returning the absolute path to the + generated catalog file. If an error is encountered + it should raise an Exception and return None. The default + implementation simply returns None. + + The generated catalog file should be created with the + :meth:`temporary_file` method. + + :param path_to_output: Absolute path to the generated catalog file. + :param opts: A dictionary of keyword arguments + + :return: Absolute path to the modified ebook. + + Access the opts object as a dictionary with this code: + opts_dict = vars(opts) + keys = opts_dict.keys() + keys.sort() + print "opts:" + for key in keys: + print "\t%s: %s" % (key, opts_dict[key]) + + ''' + # Default implementation does nothing + print "CatalogPlugin.generate_catalog() default method, should be overridden in subclass" + return None diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 9627ac4853..65a574c3e4 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal ' import os, shutil, traceback, functools, sys, re from contextlib import closing -from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \ - MetadataWriterPlugin +from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \ + MetadataReaderPlugin, MetadataWriterPlugin from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin from calibre.customize.profiles import InputProfile, OutputProfile from calibre.customize.builtins import plugins as builtin_plugins @@ -300,6 +300,7 @@ def find_plugin(name): if plugin.name == name: return plugin + def input_format_plugins(): for plugin in _initialized_plugins: if isinstance(plugin, InputFormatPlugin): @@ -328,6 +329,7 @@ def available_input_formats(): formats.add('zip'), formats.add('rar') return formats + def output_format_plugins(): for plugin in _initialized_plugins: if isinstance(plugin, OutputFormatPlugin): @@ -347,6 +349,27 @@ def available_output_formats(): formats.add(plugin.file_type) return formats + +def catalog_plugins(): + for plugin in _initialized_plugins: + if isinstance(plugin, CatalogPlugin): + yield plugin + +def available_catalog_formats(): + formats = set([]) + for plugin in catalog_plugins(): + if not is_disabled(plugin): + for format in plugin.file_types: + formats.add(format) + return formats + +def plugin_for_catalog_format(fmt): + for plugin in catalog_plugins(): + if fmt.lower() in plugin.file_types: + return plugin + else: + return None + def device_plugins(): for plugin in _initialized_plugins: if isinstance(plugin, DevicePlugin): diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 22f0542879..76ae55d16a 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -583,8 +583,121 @@ def command_export(args, dbpath): do_export(get_db(dbpath, opts), ids, dir, opts) return 0 + +# GR additions + +def catalog_option_parser(args): + from calibre.customize.ui import available_catalog_formats, plugin_for_catalog_format + from calibre.utils.logging import Log + + def add_plugin_parser_options(fmt, parser, log): + + # Fetch the extension-specific CLI options from the plugin + plugin = plugin_for_catalog_format(fmt) + for option in plugin.cli_options: + parser.add_option(option.option, + default=option.default, + dest=option.dest, + help=option.help) + + # print_help(parser, log) + return plugin + + def print_help(parser, log): + help = parser.format_help().encode(preferred_encoding, 'replace') + log(help) + + def validate_command_line(parser, args, log): + # calibredb catalog path/to/destination.[epub|csv|xml|...] [options] + + # Validate form + if not len(args) or args[0].startswith('-'): + print_help(parser, log) + log.error("\n\nYou must specify a catalog output file of the form 'path/to/destination.extension'\n" + "To review options for an output format, type 'calibredb catalog <.extension> --help'\n" + "For example, 'calibredb catalog .xml --help'\n") + raise SystemExit(1) + + # Validate plugin exists for specified output format + output = os.path.abspath(args[0]) + file_extension = output[output.rfind('.') + 1:] + + if not file_extension in available_catalog_formats(): + print_help(parser, log) + log.error("No catalog plugin available for extension '%s'.\n" % file_extension + + "Catalog plugins available for %s\n" % ', '.join(available_catalog_formats()) ) + raise SystemExit(1) + + return output, file_extension + + # Entry point + log = Log() + parser = get_parser(_( + ''' + %prog catalog /path/to/destination.(epub|csv|xml|...) [options] + + Export a catalog in format specified by path/to/destination extension. + Options control how entries are displayed in the generated catalog ouput. + ''')) + + # Confirm that a plugin handler exists for specified output file extension + # Will raise SystemExit(1) if no plugin matching file_extension + output, fmt = validate_command_line(parser, args, log) + + # Add options common to all catalog plugins + parser.add_option('-s', '--search', default=None, dest='search_text', + help=_("Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n"+ + "Default: no filtering")) + parser.add_option('-v','--verbose', default=False, action='store_true', + dest='verbose', + help=_('Show detailed output information. Useful for debugging')) + + # Add options specific to fmt plugin + plugin = add_plugin_parser_options(fmt, parser, log) + + # Merge options from GUI Preferences + ''' + from calibre.library.save_to_disk import config + c = config() + for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']: + opt = c.get_option(pref) + switch = '--dont-'+pref.replace('_', '-') + parser.add_option(switch, default=True, action='store_false', + help=opt.help+' '+_('Specifying this switch will turn ' + 'this behavior off.'), dest=pref) + + for pref in ['timefmt', 'template', 'formats']: + opt = c.get_option(pref) + switch = '--'+pref + parser.add_option(switch, default=opt.default, + help=opt.help, dest=pref) + + for pref in ('replace_whitespace', 'to_lowercase'): + opt = c.get_option(pref) + switch = '--'+pref.replace('_', '-') + parser.add_option(switch, default=False, action='store_true', + help=opt.help) + ''' + + return parser, plugin, log + +def command_catalog(args, dbpath): + parser, plugin, log = catalog_option_parser(args) + opts, args = parser.parse_args(sys.argv[1:]) + if len(args) < 2: + parser.print_help() + print + print >>sys.stderr, _('Error: You must specify a catalog output file') + return 1 + if opts.verbose: + log("library.cli:command_catalog dispatching to plugin %s" % plugin.name) + plugin.run(args[1], opts) + return 0 + +# end of GR additions + COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format', - 'show_metadata', 'set_metadata', 'export') + 'show_metadata', 'set_metadata', 'export', 'catalog') def option_parser():