summaryrefslogtreecommitdiffstats
path: root/docs/extending.txt
blob: 750ec98b8b4526e884f3f1150822bada9dbd0ce8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
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:: ini

    [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 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://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://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'])