forked from getnikola/nikola
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathextending.txt
498 lines (374 loc) · 16.2 KB
/
extending.txt
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
.. title: Extending Nikola
.. slug: extending
.. date: 2012-03-30 23:00:00 UTC-03:00
.. tags:
.. link:
.. description:
Extending Nikola
================
:Version: 7.0.1
:Author: Roberto Alsina <[email protected]>
.. class:: alert alert-info pull-right
.. contents::
.. class:: lead
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 ``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 CommandServe(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.
MarkdownExtension Plugins
-------------------------
Implement Markdown extensions, see ``mdx_nikola.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``
When all tasks are loaded.
``configured``
When all the configuration file is processed. Note that plugins are activated before this is emitted.
``scanned``
After posts are scanned.
``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]
Template Hooks
==============
Plugins can use a hook system for adding stuff into templates. In order to use
it, a plugin must register itself. The following hooks currently exist:
* ``extra_head`` (not equal to the config option!)
* ``body_end`` (not equal to the config option!)
* ``page_header``
* ``menu``
* ``menu_alt`` (right-side menu in bootstrap, after ``menu`` in base)
* ``page_footer``
For example, in order to register a script into ``extra_head``:
.. code-block:: python
# In set_site
site.template_hooks['extra_head'].append('<script src="/assets/js/fancyplugin.js">')
There is also another API available. It allows use of dynamically generated
HTML:
.. code-block:: python
# In set_site
def generate_html_bit(name, ftype='js'):
return '<script src="/assets/{t}/{n}.{t}">'.format(n=name, t=ftype)
site.template_hooks['extra_head'].append(generate_html_bit, False, 'fancyplugin', type='js')
The second argument to ``append()`` is used to determine whether the function
needs access to the current template context and the site. If it it set to
``True``, the function will also receive ``site`` and ``context`` keyword
arguments. Example use:
.. code-block:: python
# In set_site
def greeting(addr, endswith='', site=None, context=None):
if context['lang'] == 'en':
greet = u'Hello'
elif context['lang'] == 'es':
greet = u'¡Hola'
t = u' BLOG_TITLE = {0}'.format(site.config['BLOG_TITLE'](context['lang']))
return u'<h3>{greet} {addr}{endswith}</h3>'.format(greet=greet, addr=addr,
endswith=endswith) + t
site.template_hooks['page_header'].append(greeting, True, u'Nikola Tesla', endswith=u'!')