.. title: Extending Nikola
.. slug: extending
.. date: 2012/03/30 23:00
.. tags:
.. link:
.. description:

Extending Nikola
================

:Version: 6.2.1
:Author: Roberto Alsina <ralsina@netmanagers.com.ar>

.. class:: alert alert-info pull-right

.. contents::


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
    Nikola is a tool to create static websites and blogs. For full documentation and more
    information, please visit http://getnikola.com


    Available commands:
    nikola auto                 automatically detect site changes, rebuild
                                and optionally refresh a browser
    nikola bootswatch_theme     given a swatch name from bootswatch.com and a
                                parent theme, creates a custom theme
    nikola build                run tasks
    nikola check                check links and files in the generated site
    nikola clean                clean action / remove targets
    nikola console              start an interactive python console with access to
                                your site and configuration
    nikola deploy               deploy the site
    nikola dumpdb               dump dependency DB
    nikola forget               clear successful run status from internal DB
    nikola help                 show help
    nikola ignore               ignore task (skip) on subsequent runs
    nikola import_blogger       import a blogger dump
    nikola import_feed          import a RSS/Atom dump
    nikola import_wordpress     import a WordPress dump
    nikola init                 create a Nikola site in the specified folder
    nikola install_theme        install theme into current site
    nikola list                 list tasks from dodo file
    nikola mincss               apply mincss to the generated site
    nikola new_post             create a new blog post or site page
    nikola run                  run tasks
    nikola serve                start the test webserver
    nikola strace               use strace to list file_deps and targets
    nikola version              print the Nikola version number

    nikola help                 show help / reference
    nikola help <command>       show command usage
    nikola help <task-name>     show task usage

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:: ini

    [Core]
    Name = serve
    Module = serve

    [Documentation]
    Author = Roberto Alsina
    Version = 0.1
    Website = http://getnikola.com
    Description = Start test server.

.. note:: If you want to publish your plugin on the Plugin Index, `read
          the docs for the Index
          <https://github.com/getnikola/plugins/blob/master/README.md>`__
          (and the .plugin file examples and explanations).

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
``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."""

        name = "serve"
        doc_usage = "[options]"
        doc_purpose = "start the test webserver"

        cmd_options = (
            {
                'name': 'port',
                'short': 'p',
                'long': 'port',
                'default': 8000,
                'type': int,
                'help': 'Port nummber (default: 8000)',
            },
            {
                'name': 'address',
                'short': 'a',
                'long': '--address',
                'type': str,
                'default': '127.0.0.1',
                'help': 'Address to bind (default: 127.0.0.1)',
            },
        )

        def _execute(self, options, args):
            """Start test server."""
            out_dir = self.site.config['OUTPUT_FOLDER']
            if not os.path.isdir(out_dir):
                print("Error: Missing '{0}' folder?".format(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 help command`` and can later use, for example::

    $ nikola help serve
    Purpose: start the test webserver
    Usage:   nikola serve [options]

    Options:
    -p ARG, --port=ARG        Port nummber (default: 8000)
    -a ARG, ----address=ARG   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 = mako

    [Documentation]
    Author = Roberto Alsina
    Version = 0.1
    Website = http://getnikola.com
    Description = Support for Mako templates.

.. note:: If you want to publish your plugin on the Plugin Index, `read
          the docs for the Index
          <https://github.com/getnikola/plugins/blob/master/README.md>`__
          (and the .plugin file examples and explanations).

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):
            """Create a 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:

.. sidebar:: Other Tasks

    There are also ``LateTask`` plugins, which are executed later,
    and ``TaskMultiplier`` plugins that take a task and create
    more tasks out of it.

::

    $ nikola list
    Scanning posts....done!
    build_bundles
    build_less
    copy_assets
    copy_files
    post_render
    redirect
    render_archive
    render_galleries
    render_galleries_clean
    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://pydoit.org/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://getnikola.com
    Description = Copy theme assets into output.


.. note:: If you want to publish your plugin on the Plugin Index, `read
          the docs for the Index
          <https://github.com/getnikola/plugins/blob/master/README.md>`_
          (and the .plugin file examples and explanations).

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'])

PageCompiler Plugins
--------------------

These plugins implement markup languages, they take sources for posts or pages and
create HTML or other output files. A good example is the ``misaka`` plugin.

They must provide:

``compile_html``
    Function that builds a file.

``create_post``
    Function that creates an empty file with some metadata in it.

If the compiler produces something other than HTML files, it should also implement ``extension`` which
returns the preferred extension for the output file.

RestExtension Plugins
---------------------

Implement directives for reStructuredText, see ``media.py`` for a simple example.

SignalHandler Plugins
---------------------

These plugins extend the ``SignalHandler`` class and connect to one or more
signals via `blinker <http://pythonhosted.org/blinker/>`_

The easiest way to do this is to reimplement ``set_site()`` and just connect to
whatever signals you want there.

Currently Nikola emits the following signals:

``sighandlers_loaded``
    Right after SignalHandler plugin activation.
``initialized``
    Right after plugin activation
``configured``
    When all the configuration file is processed. Note that plugins are activated before this is emitted.
``new_post``
    When a new post is created, using the ``nikola new_post`` command.  The signal
    data contains the path of the file, and the metadata file (if there is one).
``deployed``
    When the ``nikola deploy`` command is run, and there is at least one new
    entry/post since ``last_deploy``.  The signal data is of the form ::

        {
         'last_deploy: # datetime object for the last deployed time,
         'new_deploy': # datetime object for the current deployed time,
         'clean': # whether there was a record of a last deployment,
         'deployed': # all files deployed after the last deploy,
         'undeployed': # all files not deployed since they are either future posts/drafts
        }


Plugin Index
============

There is a `plugin index <http://plugins.getnikola.com/>`__, which stores all
of the plugins for Nikola people wanted to share with the world.

You may want to read the `README for the Index
<https://github.com/getnikola/plugins/blob/master/README.md>`_ if you want to
publish your package there.

Path/Link Resolution Mechanism
==============================

Any plugin can register a function using ``Nikola.register_path_handler`` to
allow resolution of paths and links. These are useful for templates, which
can access them via _link.

For example, you can always get a link to the path for the feed of the "foo" tag
by using ``_link('tag_rss', 'foo')`` or the ``link://tag_rss/foo`` URL.

Here's the relevant code from the tag plugin.

.. code-block:: python

    # In set_site
    site.register_path_handler('tag_rss', self.tag_rss_path)

    # And these always take name and lang as arguments and returl a list of
    # path elements.
    def tag_rss_path(self, name, lang):
        return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
                              self.site.config['TAG_PATH'], self.slugify_name(name) + ".xml"] if
                _f]

