Rationale

I have always been a fan of the Sphinx Python documentation generator and it has, I think, a nice feature where you can check out the raw source of a generated piece of documentation - a Show Source link. I decided that developing a Pelican plugin to imitate that feature would be a great way of getting a little deeper into the Pelican code itself.

This first post explores the Pelican plugin architecture and the basics of building a plugin.

The Pelican Plugin Architecture

Firstly, and mostly for my own benefit, I would like to make a few notes about the Pelican plugin architecture. Now that I have delved into it a little I have found that it is quite simple and gives you great ease of access to the entire build process. Pretty much all the important objects in the process can be accessed and modified.

The Pelican blog system uses the blinker Python library to emit a series of signals during build time, that a series of interested parties can subscribe to. Any plugin developed for Pelican is designed to hook into this series of signals, and there are a lot! Here is a list of signals and the objects that are available (as from pelican/pelican/signals.py) at commit 8993c55:

Signals
    |-Run-level signals
    |   |-initialized                       (pelican object)
    |   |-get_generators                    (pelican object)
    |   |-all_generators_finalized          (generators)
    |   |-get_writer                        (pelican object)
    |   `-finalized                         (pelican object)
    |-Reader-level signals
    |   `-readers_init                      (readers)
    |-Generator-level signals
    |   |-generator_init                    (generator)
    |   |-article_generator_init            (article_generator)
    |   |-article_generator_pretaxonomy     (article_generator)
    |   |-article_generator_finalized       (article_generator)
    |   |-article_generator_write_article   (article_generator, content)
    |   |-article_writer_finalized          (article_generator, writer)
    |   |-page_generator_init               (page_generator)
    |   |-page_generator_finalized          (page_generator)
    |   |-page_writer_finalized             (page_generator, writer)
    |   |-static_generator_init             (static_generator)
    |   `-static_generator_finalized        (static_generator)
    |-Page-level signals
    |   |-article_generator_preread         (article_generator)
    |   |-article_generator_context         (article_generator, metadata)
    |   |-page_generator_preread            (page_generator)
    |   |-page_generator_context            (page_generator, metadata)
    |   |-static_generator_preread          (static_generator)
    |   |-static_generator_context          (static_generator, metadata)
    |    `-content_object_init              (content_object)
    `-Writers signals
        |-content_written                   (path, context)
        `-feed_written                      (path, context, feed)

Plugin File Layout

In order to build a plugin, you need at least a couple of files in your plugin directory: an __init__.py and a plugin file, say, myplugin.py, looking something like:

myplugin
  |-__init__.py
  `-myplugin.py

The contents of __init__.py should be the following:

from from .myplugin import *

The contents of myplugin.py could be something like:

from pelican import signals

def plugin_function(article_generator, content):
    """Your plugin function for article_generator_write_article"""
    # You now have access to the article_generator & content objects
    pass

def plugin_function_2(path, context):
    """Your plugin function for content_written"""
    # You now have access to the path & context objects
    pass

def register():
    """
    The register function is a requirement for the plugin to work. You have
    to call your plugin functions by `signals.x.connect(function)` with x
    being the required signal(s) and main_function being the function for
    that signal.
    You can have as many signal.x.connect() as you want here.
    """
    # When the signals below are emitted, functions above will be called
    signals.article_generator_write_article.connect(plugin_function)
    signals.content_written.connect(plugin_function_2)

That's It!

So you can see that developing a simple plugin is really not a lot of work if you understand the basics. In order to gain a deeper knowledge of what you can do with plugins, it is definitely worth spending some time having a look through the Pelican plugin repo to see what others have done.

Whilst you are there, have a look at how the objects that are made available in the functions are used.

There are more helpful hints regarding plugin development here in the Pelican docs, as well as a handy page on contributing a plugin once again in the Pelican plugin repo

Have fun!


Comments

comments powered by Disqus