diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index a09a764703..3d48f42535 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -19,12 +19,12 @@ class Plugin(object): Methods that should be overridden in sub classes: - * :method:`initialize` - * :method:`customization_help` + * :meth:`initialize` + * :meth:`customization_help` Useful methods: - * :method:`temporary_file` + * :meth:`temporary_file` ''' #: List of platforms this plugin works on @@ -93,6 +93,7 @@ class Plugin(object): a needed binary on the user's computer. :param gui: If True return HTML help, otherwise return plain text help. + ''' raise NotImplementedError @@ -157,9 +158,10 @@ class FileTypePlugin(Plugin): simply return the path to the original ebook. The modified ebook file should be created with the - :method:`temporary_file` method. + :meth:`temporary_file` method. :param path_to_ebook: Absolute path to the ebook. + :return: Absolute path to the modified ebook. ''' # Default implementation does nothing @@ -186,7 +188,8 @@ class MetadataReaderPlugin(Plugin): with the input data. :param type: The type of file. Guaranteed to be one of the entries - in :member:`file_types`. + in :attr:`file_types`. + :return: A :class:`calibre.ebooks.metadata.MetaInformation` object ''' return None @@ -212,8 +215,9 @@ class MetadataWriterPlugin(Plugin): with the input data. :param type: The type of file. Guaranteed to be one of the entries - in :member:`file_types`. + in :attr:`file_types`. :param mi: A :class:`calibre.ebooks.metadata.MetaInformation` object + ''' pass - \ No newline at end of file + diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index a9f24b9447..f8df1a52d0 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -112,7 +112,8 @@ def get_file_type_metadata(stream, ftype): try: plugin = _metadata_readers[ftype.lower().strip()] if not is_disabled(plugin): - mi = plugin.get_metadata(stream, ftype.lower().strip()) + with plugin: + mi = plugin.get_metadata(stream, ftype.lower().strip()) except: pass return mi @@ -121,7 +122,8 @@ def set_file_type_metadata(stream, mi, ftype): try: plugin = _metadata_writers[ftype.lower().strip()] if not is_disabled(plugin): - plugin.set_metadata(stream, mi, ftype.lower().strip()) + with plugin: + plugin.set_metadata(stream, mi, ftype.lower().strip()) except: traceback.print_exc() @@ -288,4 +290,4 @@ def main(args=sys.argv): return 0 if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index 6d8647c4b9..05b66cac88 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -72,7 +72,7 @@ - 4 + 0 diff --git a/src/calibre/gui2/images/news/endgadget.png b/src/calibre/gui2/images/news/endgadget.png new file mode 100644 index 0000000000..94e8f1219c Binary files /dev/null and b/src/calibre/gui2/images/news/endgadget.png differ diff --git a/src/calibre/gui2/images/news/fudzilla.png b/src/calibre/gui2/images/news/fudzilla.png new file mode 100644 index 0000000000..ad1e67335d Binary files /dev/null and b/src/calibre/gui2/images/news/fudzilla.png differ diff --git a/src/calibre/manual/customize.rst b/src/calibre/manual/customize.rst new file mode 100644 index 0000000000..e12d786208 --- /dev/null +++ b/src/calibre/manual/customize.rst @@ -0,0 +1,112 @@ +.. include:: global.rst + +.. currentmodule:: calibre.customize.__init__ + +.. _customize: + +Customizing |app| +================================== + +|app| has a highly modular design. Various parts of it can be customized. You can learn how to create +*recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn how to +use *plugins* to customize and control various aspects of |app|'s behavior. + +Theer are different kinds of plugins, corresponding to different aspects of |app|. As more and more aspects of |app| +are modularized, new plugin types will be added. + +.. contents:: + :depth: 2 + :local: + +A Hello World plugin +------------------------ + +Suppose you have an installation of |app| that you are using to self publish various e-documents in EPUB and LRF +format. You would like all file generated by |app| to have their publisher set as "Hello world", here's how to do it. +Create a file name :file:`my_plugin.py` (the file name must end with plugin.py) and enter the following Python code into it: + +.. code-block:: python + + import os + from calibre.customize import FileTypePlugin + + class HelloWorld(FileTypePlugin): + + name = 'Hello World Plugin' # Name of the plugin + description = 'Set the publisher to Hello World for all new conversions' + supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on + author = 'Acme Inc.' # The author of this plugin + version = (1, 0, 0) # The version number of this plugin + file_types = set(['epub', 'lrf']) # The file types that this plugin will be applied to + on_postprocess = True # Run this plugin after conversion is complete + + def run(self, path_to_ebook): + from calibre.ebooks.metadata.meta import get_metadata, set_metadata + file = open(path_to_ebook, 'r+b') + ext = os.path.splitext(path_to_ebook)[-1][1:].lower() + mi = get_metadata(file, ext) + mi.publisher = 'Hello World' + set_metadata(file, ext, mi) + return path_to_ebook + +That's all. To add this code to |app| as a plugin, simply create a zip file with:: + + zip plugin.zip my_plugin.py + +Now either use the configuration dialog in |app| GUI to add this zip file as a plugin, or +use the command:: + + calibre-customize -a plugin.zip + +Every time you use calibre to convert a book, the plugin's :meth:`run` method will be called and the +converted book will have its publisher set to "Hello World". For more information about +|app|'s plugin system, read on... + +The Plugin base class +------------------------ + +As you may have noticed above, all |app| plugins are classes. The Plugin classes are organized in a hierarchy at the top of which +is :class:`calibre.customize.Plugin`. The has excellent in source documentation for its various features, here I will discuss a +few of the important ones. + +First, all plugins must supply a list of platforms they have been tested on by setting the ``supported_platforms`` member as in the +example above. + +If the plugin needs to do any initialization, it should implement the :meth:`initialize` method. The path to the plugin zip file +is available as ``self.plugin_path``. The initialization method could be used to load any needed resources from the zip file. + +If the plugin needs to be customized (i.e. it needs some information from the user), it should implement the :meth:`customization_help` +method, to indicate to |app| that it needs user input. This can be useful, for example, to ask the user to input the path to a needed system +binary or the URL of a website, etc. When |app| asks the user for the customization information, the string retuned by the :meth:`customization_help` +method is used as help text to le thte user know what information is needed. + +Another useful method is :meth:`temporary_file`, which returns a file handle to an opened temporary file. If your plugin needs to make use +of temporary files, it should use this method. Temporary file cleanup is then taken care of automatically. + +In addition, whenever plugins are run, their zip files are automatically added to the start of ``sys.path``, so you can directly import +any python files you bundle in the zip files. Note that this is not available when the plugin is being initialized, only when it is being run. + +Finally, plugins can have a priority (a positive integer). Higher priority plugins are run in preference tolower priority ones in a given context. +By default all plugins have priority 1. You can change that by setting the member :attr:'priority` in your subclass. + +See :ref:`pluginsPlugin` for details. + +File type plugins +------------------- + +File type plugins are intended to be associated with specific file types (as identified by extension). They can be run on several different occassions. + + * When books/formats are added ot the |app| database (if :attr:`on_import` is set to True). + * Just before an any2whatever converter is run on an input file (if :attr:`on_preprocess` is set to True). + * After an any2whatever converter has run, on the output file (if :attr:`on_postprocess` is set to True). + +File type plugins specify which file types they are associated with by specifying the :attr:`file_types` member as in the above example. +the actual work should be done in the :meth:`run` method, which must return the path to the modified ebook (it can be the same as the original +if the modifcations are done in place). + +See :ref:`pluginsFTPlugin` for details. + +Metadata plugins +------------------- + +Metadata plugins add the ability to read/write metadata from ebook files to |app|. See :ref:`pluginsMetadataPlugin` for details. \ No newline at end of file diff --git a/src/calibre/manual/index.rst b/src/calibre/manual/index.rst index cfda23762a..376145873e 100644 --- a/src/calibre/manual/index.rst +++ b/src/calibre/manual/index.rst @@ -30,6 +30,7 @@ Sections metadata faq xpath + customize glossary Convenience diff --git a/src/calibre/manual/plugins.rst b/src/calibre/manual/plugins.rst new file mode 100644 index 0000000000..706dc281d3 --- /dev/null +++ b/src/calibre/manual/plugins.rst @@ -0,0 +1,100 @@ +.. include:: global.rst + +.. _plugins: + +API Documentation for plugins +=============================== + +.. module:: calibre.customize.__init__ + :synopsis: Defines various abstract base classes that can be subclassed to create plugins. + +Defines various abstract base classes that can be subclassed to create powerful plugins. The useful +classes are: + +.. contents:: + :depth: 1 + :local: + +.. _pluginsPlugin: + +Plugin +----------------- + +.. class:: Plugin + + Abstract base class that contains a number of members and methods to create your plugin. All + plugins must inherit from this class or a subclass of it. + + The members and methods are: + +.. automember:: Plugin.name + +.. automember:: Plugin.author + +.. automember:: Plugin.description + +.. automember:: Plugin.version + +.. automember:: Plugin.supported_platforms + +.. automember:: Plugin.priority + +.. automember:: Plugin.minimum_calibre_version + +.. automember:: Plugin.can_be_disabled + +.. automethod:: Plugin.initialize + +.. automethod:: Plugin.customization_help + +.. automethod:: Plugin.temporary_file + +.. _pluginsFTPlugin: + +FileTypePlugin +----------------- + +.. class:: Plugin + + Abstract base class that contains a number of members and methods to create your file type plugin. All file type + plugins must inherit from this class or a subclass of it. + + The members and methods are: + +.. automember:: FileTypePlugin.file_types + +.. automember:: FileTypePlugin.on_import + +.. automember:: FileTypePlugin.on_preprocess + +.. automember:: FileTypePlugin.on_postprocess + +.. automethod:: FileTypePlugin.run + +.. _pluginsMetadataPlugin: + +Metadata plugins +------------------- + +.. class:: MetadataReaderPlugin + + Abstract base class that contains a number of members and methods to create your metadata reader plugin. All metadata + reader plugins must inherit from this class or a subclass of it. + + The members and methods are: + +.. automember:: MetadataReaderPlugin.file_types + +.. automethod:: MetadataReaderPlugin.get_metadata + + +.. class:: MetadataWriterPlugin + + Abstract base class that contains a number of members and methods to create your metadata writer plugin. All metadata + writer plugins must inherit from this class or a subclass of it. + + The members and methods are: + +.. automember:: MetadataWriterPlugin.file_types + +.. automethod:: MetadataWriterPlugin.set_metadata diff --git a/src/calibre/web/feeds/recipes/endgadget.py b/src/calibre/web/feeds/recipes/endgadget.py new file mode 100644 index 0000000000..7593d2578e --- /dev/null +++ b/src/calibre/web/feeds/recipes/endgadget.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008, Darko Miletic ' +''' +engadget.com +''' + +import string,re +from calibre.web.feeds.news import BasicNewsRecipe + +class Engadget(BasicNewsRecipe): + title = u'Engadget' + __author__ = 'Darko Miletic' + description = 'Tech news' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + + keep_only_tags = [ dict(name='div', attrs={'class':'post'}) ] + remove_tags = [ + dict(name='object') + ,dict(name='div', attrs={'class':'postmeta'}) + ,dict(name='div', attrs={'class':'quigoads'}) + ] + + + feeds = [ (u'Posts', u'http://www.engadget.com/rss.xml')] + diff --git a/src/calibre/web/feeds/recipes/fudzilla.py b/src/calibre/web/feeds/recipes/fudzilla.py new file mode 100644 index 0000000000..eda7b6451a --- /dev/null +++ b/src/calibre/web/feeds/recipes/fudzilla.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2008, Darko Miletic ' +''' +fudzilla.com +''' + +import string,re +from calibre.web.feeds.news import BasicNewsRecipe + +class Fudzilla(BasicNewsRecipe): + title = u'Fudzilla' + __author__ = 'Darko Miletic' + description = 'Tech news' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + + feeds = [ (u'Posts', u'http://www.fudzilla.com/index.php?option=com_rss&feed=RSS2.0&no_html=1')] + + def print_version(self, url): + nurl = url.replace('http://www.fudzilla.com/index.php','http://www.fudzilla.com/index2.php') + nmain, nsep, nrest = nurl.partition('&Itemid=') + return nmain + '&pop=1&page=0&Itemid=1'