Skip to content

Plugin Development

Egbert edited this page Jul 1, 2024 · 24 revisions

First Plugin Source

Create the __init__.py file

$ cd ~/work/my_plugin/pelican/plugins/my_plugin
$ vi __init__.py

and fill it with:

# coding: utf-8
""" Tableize adapt to a Pelican's plugin """
__title__ = 'my_plugin'
__version__ = '0.0.0'
__author__ = 'John Doe'
__email__ = "[email protected]"
__credits__ = ["John Doe, Jane Doe, Don Doe"]
__maintainer__ = "Jill Doe"
__status__ = "Stable"
__license__ = 'GPLv2'
__copyright__ = 'Copyright 2024'

from .my_plugin import register  # NOQA

Second Plugin Source

Create a primary plugin Python source file in the same ~/work/my_plugin/pelican/plugins/my_plugin directory:

vi my_plugin.py

And fill it with the following code template:

from copy import copy
import pprint
import logging
from pelican import signals, logger
from pelican.readers import BaseReader
from pelican.contents import Article, Page
from pelican.settings import DEFAULT_CONFIG

logger = logging.getLogger('my_plugin')  # log with my plugin name
# if you used `__name__` instead, you will get a much longer string
# like `pelican.plugins.my_plugin.my_plugin`, which is not desirable.

# These fills in settings but never overwrite any pre-exist settings
# In short, supply any missing settings.
def set_default_settings(settings):
    """ If the pelican.settings is missing any of our plugin settings,
        fill those settings as well."""
    settings.setdefault(
        'MY_PLUGIN_SETTINGS,
        {
            'max_line': 80,
            'header': False,
            'separator': ',',
            'auto_index': 1
        }
    )

def my_plugin_pelican_initialized_all(pelican):
    mp_pelican = pelican  # NOQA
    if mp_pelican is None:
        logger.fatal('No pelican access; my_plugin plugin is disabled')
        return
    mp_settings = pelican.settings.get('MY_PLUGIN_SETTINGS')
    if mp_settings is None:
        logger.warning('%s does not exist in pelican.settings; ' +
                       'plugin\'s built-in defaults used',
                       'MY_PLUGIN_SETTINGS')
        # this is the part where we must implicitly declare our defaults
        set_default_settings(pelican.settings)
    else:
        # explicit settings for this plugin are found in
        #     pelican.settings['MY_PLUGIN_SETTINGS']
        logger.debug('{0!s} exists in pelican.settings'.format(
             'MY_PLUGIN_SETTINGS'))
        mp_settings = copy(
            mp_pelican_settings['MY_PLUGIN_SETTINGS'])
        check_plugin_settings(mp_pelican)

def mp_article_init(articles_generator):
    # arg1 : articles_generator:ArticlesGenerator
    #
    # articles_generator provides the following variable member items:
    #   articles:list, authors:dict, categories:dict, context:dict, dates:dict,
    #   drafts:list, drafts_translation:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives_dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   translations:list.
    #
    # 1st article-related signal.
    # generator processing Class (i.e., ArticlesGenerator, PagesGenerator,
    #     StaticGenerator).
    # no content, no articles, but lots of other stuffs.
    # first appearance of article_generator.settings.
    # Callstack:
    #     ArticlesGenerator.__init__()
    #
    #
    # Hooked by signals.article_generator_init.connect(mp_article_init)
    logger.info('mp_article_init called: path is ' + articles_generator.path)
    return


def mp_article_preread(articles_generator):
    # Description:
    #     useful for modifying file-related variables before reading
    #     this is the only article-related signal BEFORE any Markdown pre-processing
    #
    # arg1 : articles_generator:ArticlesGenerator
    #
    # articles_generator:Articles_generator provides the following
    # variable member items:
    #   articles:list, authors:dict, categories:dict, context:dict, dates:dict,
    #   drafts:list, drafts_translations:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives:dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   and translations:list.  (No content here, yet)
    #
    # 2nd article-related signal
    # First signal for entire ArticleGenerator.generate_context()
    # First signal within Readers.read_file().
    # Callstack:
    #                 signals.article_generator_preread.send()
    #                 ArticlesGenerator.generate_context()
    #                 Pelican.run()
    #
    # Hooked by signals.article_generator_preread.connect(mp_article_preread).
    #
    print('mp_article_preread called')
    return


def mp_article_context(articles_generator, metadata=[]):
    # Description:
    #     context and metadata are added here the first time for
    #     ArticlesGenerator but still no content yet.
    #
    # arg1 : articles_generator:ArticlesGenerator
    # arg2 : metadata:dict
    #
    # articles_generator of ArticlesGenerator class provides the
    # following variable member items:
    #   articles:list, authors:dict, categories:dict, context:dict,
    #   dates:dict, drafts:list,
    #   drafts_translations:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives:dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   and translations:list.  (No content here, yet)
    #
    # metadata of dict type provides the following variable member items:
    #   status:str, category:Category, reader:str, title:str, data:SafeDatetime,
    #   tags:list, summary:str, lang:str, private:str, and __len__:int.
    #
    # 3rd article-related signal.
    # 2nd signal for entire ArticleGenerator.generate_context().
    # 1st param is Generator type (ArticlesGenerator/PagesGenerator/StaticGenerator).
    # 2nd param is metadata dict object (what you see in first few lines of an
    #     article/page/static file).
    #
    # Hooked by signals.article_generator_context.connect(mp_article_context).
    #
    print('mp_article_context called')
    return


def mp_content_object_init(content_class: object):
    # Description:
    #   First signal handler to provide the actual content of any article/page/static
    #   file.
    #
    # arg1 : content_class:Content, could be article:Article, page:Page, static:Static
    #
    # article of Article(Content) subclass provides the following variable member items:
    #   allowed_statuses:tuple, author:Author, authors:list, category:Category,
    #   content:str, date:SafeDatetime, date_format:str, default_status:str,
    #   default_template:str, filename:str, get_content:partial, get_summary:partial,
    #   in_default_lang:bool, lang:str, locale_date:str, mandatory_properties:tuple,
    #   metadata:dict, private:str, reader:str, relative_dir:str,
    #   relative_source_path:str, save_as:str, settings:dict, slug:str,
    #   source_path:str, status:str, summary:str, tags:list, template:str,
    #   timezone:Zoneinfo, title:str, translations:list, url:str, url_format:dict
    #
    # Callstack:
    #     signals.content_object_init.send()
    #     Content.__init__()
    #     Article.__init__()
    #     Readers.read_file()
    #     ArticlesGenerator.generate_context()
    #     Pelican.run()
    #
    # 4th article-related signal.
    # 3rd signal in ArticlesGenerator.generate_context().
    # Still inside read_file().
    # First signal appearance having a content provided by Markdown.read_file().
    #
    # Hooked using signals.content_object_init.connect(mp_content_object_init).
    #
    print('mp_content_object_init called')
    if content_class is None:
        return
    content = content_class.content

    print('mp_content_object_init: content: {0!s}'.format(content))

    # Only process Article or Page subclass contents
    if not (isinstance(content_class, Article) or isinstance(content_class, Page)):
        return

    return


def mp_article_pretaxonomy(articles_generator):
    # Description:
    #   Now you can first tell if it came from ArticlesGenerator or StaticGenerator
    #      using self variable.
    #   But the content is now gone.
    #   After all translations are done, after a complete list of all document file
    #       is made for each generator type.
    #   All about indexing, cross-linking and translation, plugins who want the
    #   metadata after such translation/index/lists updating;
    #   absolutely no content (anymore).
    #
    # arg1: articles_generator:ArticlesGenerator
    #
    # articles_generator of ArticlesGenerator class provides the
    # following variable member items:
    #   articles:list, authors:dict, categories:dict, content:dict,
    #   dates:dict, drafts:list,
    #   drafts_translations:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives:dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   and translations:list.
    #
    # 5th article-related signal.
    # 4th signal in ArticlesGenerator.generate_context().
    #
    # Hooked by signals.article_generator_pretaxonomy.connect(mp_article_pretaxonomy).
    #
    print('mp_article_pretaxonomy called')
    return


def mp_article_finalized(articles_generator):
    # Description:
    #
    # arg1 : articles_generators:ArticlesGenerator
    #
    # articles_generator of ArticlesGenerator class provides the
    # following variable member items:
    #   articles:list, authors:dict, categories:dict, context:dict
    #   dates:dict, drafts:list,
    #   drafts_translations:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives:dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   and translations:list.
    #
    # 5th article-related signal.
    # The last signal in ArticlesGenerator.generate_context().
    # all the caches are saved.
    #
    # Hooked by signals.article_generator_finalized.connect(tp_article_finalized).
    #
    print('mp_article_finalized called')
    return


def mp_article_write(articles_generator, content=[]):
    #
    # arg1 : articles_generator:ArticlesGenerator
    # arg2 : content:Article
    #
    # articles_generator of ArticlesGenerator class provides the
    # following variable member items:
    #   articles:list, authors:dict, categories:dict, context:dict
    #   dates:dict, drafts:list,
    #   drafts_translations:list, env:Environment, hidden_articles:list,
    #   hidden_translations:list, output_path:str, path:str, period_archives:dict,
    #   readers:Readers, related_posts:list, settings:dict, tags:dict, theme:str,
    #   and translations:list.
    #
    # content of Article class provides the following variable member items:
    #   allowed_statuses:tuple, author:Author, authors:list, category:Category,
    #   content:str, date:SafeDatetime, date_format:str, default_status:str,
    #   default_template:str, filename:str, get_content:partial, get_summary:partial,
    #   in_default_lang:bool, lang:str, locale_date:str, mandatory_properties:tuple,
    #   metadata:dict, private:str, reader:str, relative_dir:str,
    #   relative_source_path:str, save_as:str, settings:dict, slug:str,
    #   source_path:str, status:str, summary:str, tags:list, template:str,
    #   timezone:Zoneinfo, title:str, translations:list, url:str, url_format:dict
    #
    # 6th article-related signal.
    # 5th signal in ArticlesGenerator.generate_context().
    # Article.content is back.
    # Article.metadata is back.
    #
    # Hooked by signals.article_generator_write_article.connect(mp_article_write).
    #
    print('mp_article_write called')
    return


# This is how pelican plugin works.
# register() is a well-established function name used by Pelican plugin
# handler for this plugin to get recognized, inserted, initialized, and
# its processors added into and by the Pelican app.
def register():

    signals.initialized.connect(my_plugin_pelican_initialized_all)

    # Different version of Pelican behave differently.
    # By using 'try', we ensure that all signals are available before
    # our plugin processing.
    try:
        # All signals are listed here as of Pelican v4.9.1
        # signals.get_generators.connect()
        # signals.readers_init()
        # signals.generator_init()
        signals.article_generator_init.connect(mp_article_init)
        # signals.readers_init()
        # signals.readers_init()
        # signals.generator_init()
        # signals.page_generator_init()
        # signals.readers_init()
        # signals.generator_init()
        # signals.readers_init()
        # signals.generator_init()
        # signals.static_generator_init()
        signals.article_generator_preread.connect(mp_article_preread)
        signals.article_generator_context.connect(mp_article_context)
        signals.content_object_init.connect(mp_content_object_init)
        signals.article_generator_pretaxonomy.connect(mp_article_pretaxonomy)
        signals.article_generator_finalized.connect(mp_article_finalized)
        # signals.page_generator_preread.connect(mp_page_preread)
        # signals.page_generator_context.connect(mp_page_context)
        # signals.content_object_init.connect(mp_content_object_init)
        # signals.page_generator_finalized.connect(mp_page_finalized)
        # signals.static_generator_preread.connect(mp_static_preread)
        # signals.static_generator_context.connect(mp_static_context)
        # signals.content_object_init.connect(mp_content_object_init)
        # signals.static_generator_finalized.connect(mp_static_finalized)
        # signals.all_generators_finalized.connect(mp_all_generators_finalized)
        # signals.get_writers()
        # signals.feed_generated()
        # signals.feed_written()
        signals.article_generator_write_article.connect(mp_article_write)
        # signals.content_written()
        # signals.article_writer_finalized.connect(mp_article_write)
        # signals.page_generator_write_page.connect(mp_article_write)
        # signals.content_written()
        # signals.page_writer_finalized()
        # signals.content_written()
        # signals.pelican_finalized()
    except Exception as e:
        logger.exception('Plugin failed to execute: {}'.format(pprint.pformat(e)))

    logger.info(
        'my_plug plugin registered for Pelican, using new 4.0 plugin variant')
Clone this wiki locally