aboutsummaryrefslogtreecommitdiffstats
path: root/docs/extending.txt
diff options
context:
space:
mode:
Diffstat (limited to 'docs/extending.txt')
-rw-r--r--docs/extending.txt301
1 files changed, 301 insertions, 0 deletions
diff --git a/docs/extending.txt b/docs/extending.txt
new file mode 100644
index 0000000..4bbc2ea
--- /dev/null
+++ b/docs/extending.txt
@@ -0,0 +1,301 @@
+Extending Nikola
+================
+
+:Version: 5
+:Author: Roberto Alsina <ralsina@netmanagers.com.ar>
+
+.. class:: alert alert-info pull-right
+
+.. contents::
+
+
+.. note:: This is a draft
+
+ I am not sure of the best way to do some things, including how
+ to document this. Suggestions are welcome.
+
+Nikola is extensible. Almost all its functionality is based on plugins,
+and you can add your own or replace the provided ones.
+
+Plugins consist of a metadata file (with ``.plugin`` extension) and
+a python module (a ``.py`` file) or package (a folder containing
+a ``__init__.py`` file.
+
+To use a plugin in your site, you just have to put it in a ``plugins``
+folder in your site.
+
+Plugins come in various flavours, aimed at extending different aspects
+of Nikola.
+
+Command Plugins
+---------------
+
+When you run ``nikola --help`` you will see something like this::
+
+ $ nikola --help
+ Usage: nikola command [options]
+
+ Available commands:
+
+ nikola bootswatch_theme: Given a swatch name and a parent theme, creates a custom theme.
+ nikola build: Build the site.
+ nikola import_wordpress: Import a wordpress site from a XML dump.
+ nikola init: Create a new site.
+ nikola install_theme: Install a theme into the current site.
+ nikola new_post: Create a new post.
+ nikola serve: Start test server.
+
+ For detailed help for a command, use nikola command --help
+
+That will give you a list of all available commands in your version of Nikola.
+Each and every one of those is a plugin. Let's look at a typical example:
+
+First, the ``command_serve.plugin`` file:
+
+.. code_block:: init
+
+ [Core]
+ Name = serve
+ Module = command_serve
+
+ [Documentation]
+ Author = Roberto Alsina
+ Version = 0.1
+ Website = http://nikola.ralsina.com.ar
+ Description = Start test server.
+
+For your own plugin, just change the values in a sensible way. The
+``Module`` will be used to find the matching python module, in this case
+``command_serve.py``, from which this is the interesting bit:
+
+.. code_block:: python
+
+ from nikola.plugin_categories import Command
+
+ # You have to inherit Command for this to be a
+ # command plugin:
+
+ class CommandBuild(Command):
+ """Start test server."""
+
+ # This has to match the Name option in the .plugin file
+
+ name = "serve"
+
+ # This is the function that does stuff
+
+ def run(self, *args):
+ """Start test server."""
+
+ # use OptionParser if you want your command to have options
+
+ parser = OptionParser(usage="nikola %s [options]" % self.name)
+ parser.add_option("-p", "--port", dest="port",
+ help="Port numer (default: 8000)", default=8000,
+ type="int")
+ parser.add_option("-a", "--address", dest="address",
+ help="Address to bind (default: 127.0.0.1)",
+ default='127.0.0.1')
+ (options, args) = parser.parse_args(list(args))
+
+ # You can use self.site.config to access your
+ # configuration options. self.site is an instance
+ # of the Nikola class and contains all your site's
+ # data.
+
+ out_dir = self.site.config['OUTPUT_FOLDER']
+
+ # Then do something interesting. In this case,
+ # it starts a webserver
+
+ if not os.path.isdir(out_dir):
+ print "Error: Missing '%s' folder?" % out_dir
+ else:
+ os.chdir(out_dir)
+ httpd = HTTPServer((options.address, options.port),
+ OurHTTPRequestHandler)
+ sa = httpd.socket.getsockname()
+ print "Serving HTTP on", sa[0], "port", sa[1], "..."
+ httpd.serve_forever()
+
+As mentioned above, a plugin can have options, which the user can see by doing
+``nikola command --help`` and can later use as ``nikola command --option``::
+
+ $ nikola serve --help
+ Usage: nikola serve [options]
+
+ Options:
+ -h, --help show this help message and exit
+ -p PORT, --port=PORT Port numer (default: 8000)
+ -a ADDRESS, --address=ADDRESS
+ Address to bind (default: 127.0.0.1)
+
+ $ nikola serve -p 9000
+ Serving HTTP on 127.0.0.1 port 9000 ...
+
+So, what can you do with commands? Well, anything you want, really. I have implemented
+a sort of planet using it. So, be creative, and if you do something interesting,
+let me know ;-)
+
+TemplateSystem Plugins
+----------------------
+
+Nikola supports Mako and Jinja2. If you prefer some other templating
+system, then you will have to write a TemplateSystem plugin. Here's how they work.
+First, you have to create a .plugin file. Here's the one for the Mako plugin:
+
+.. code_block:: ini
+
+ [Core]
+ Name = mako
+ Module = template_mako
+
+ [Documentation]
+ Author = Roberto Alsina
+ Version = 0.1
+ Website = http://nikola.ralsina.com.ar
+ Description = Support for Mako templates.
+
+You will have to replace "mako" with your template system's name, and other data
+in the obvious ways.
+
+The "Module" option is the name of the module, which has to look something like this,
+a stub for a hypothetical system called "Templater":
+
+.. code_block:: python
+
+ from nikola.plugin_categories import TemplateSystem
+
+ # You have to inherit TemplateSystem
+
+ class TemplaterTemplates(TemplateSystem):
+ """Wrapper for Templater templates."""
+
+ # name has to match Name in the .plugin file
+ name = "templater"
+
+ # You *must* implement this, even if to return []
+ # It should return a list of all the files that,
+ # when changed, may affect the template's output.
+ # usually this involves template inheritance and
+ # inclusion.
+ def get_deps(self, filename):
+ return []
+
+ # A list of directories where the templates will be
+ # located. Most template systems have some sort of
+ # template loading tool that can use this.
+
+ def set_directories(self, directories):
+ """Createa template lookup."""
+ pass
+
+ # The method that does the actual rendering.
+ # template_name is the name of the template file,
+ # output_name is the file for the output, context
+ # is a dictionary containing the data the template
+ # uses for rendering.
+
+ def render_template(self, template_name, output_name,
+ context, global_context):
+ """Render the template into output_name using context."""
+ pass
+
+
+Task Plugins
+------------
+
+If you want to do something that depends on the data in your site, you
+probably want to do a Task plugin, which will make it be part of the
+``nikola build`` command. There are the currently available tasks, all
+provided by plugins::
+
+ $ nikola build list
+
+ build_bundles
+ copy_assets
+ copy_files
+ deploy
+ redirect
+ render_archive
+ render_galleries
+ render_indexes
+ render_listings
+ render_pages
+ render_posts
+ render_rss
+ render_site
+ render_sources
+ render_tags
+ sitemap
+
+These have access to the ``site`` object which contains your timeline and
+your configuration.
+
+The critical bit of Task plugins is their ``gen_tasks`` method, which ``yields``
+`doit tasks <http://python-doit.sourceforge.net/tasks.html>`_
+
+The details of how to handle dependencies, etc. are a bit too much for this
+document, so I'll just leave you with an example, the ``copy_assets`` task.
+First the ``task_copy_assets.plugin`` file, which you should copy and edit
+in the logical ways:
+
+.. code_block:: ini
+
+ [Core]
+ Name = copy_assets
+ Module = task_copy_assets
+
+ [Documentation]
+ Author = Roberto Alsina
+ Version = 0.1
+ Website = http://nikola.ralsina.com.ar
+ Description = Copy theme assets into output.
+
+And the ``task_copy_assets.py`` file, in its entirety:
+
+.. code_block:: python
+
+ import os
+
+ from nikola.plugin_categories import Task
+ from nikola import utils
+
+ # Have to inherit Task to be a task plugin
+ class CopyAssets(Task):
+ """Copy theme assets into output."""
+
+ name = "copy_assets"
+
+ # This yields the tasks
+ def gen_tasks(self):
+ """Create tasks to copy the assets of the whole theme chain.
+
+ If a file is present on two themes, use the version
+ from the "youngest" theme.
+ """
+
+ # I put all the configurations and data the plugin uses
+ # in a dictionary because utils.config_changed will
+ # make it so that if these change, this task will be
+ # marked out of date, and run again.
+
+ kw = {
+ "themes": self.site.THEMES,
+ "output_folder": self.site.config['OUTPUT_FOLDER'],
+ "filters": self.site.config['FILTERS'],
+ }
+
+ tasks = {}
+ for theme_name in kw['themes']:
+ src = os.path.join(utils.get_theme_path(theme_name), 'assets')
+ dst = os.path.join(kw['output_folder'], 'assets')
+ for task in utils.copy_tree(src, dst):
+ if task['name'] in tasks:
+ continue
+ tasks[task['name']] = task
+ task['uptodate'] = task.get('uptodate', []) + \
+ [utils.config_changed(kw)]
+ task['basename'] = self.name
+ # If your task generates files, please do this.
+ yield utils.apply_filters(task, kw['filters'])