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'