插件

Pelican从3.0版本开始支持插件。通过插件,不必直接修改Pelican的核心代码就可以给Pelican添加新功能。

如何使用插件

Pelican从4.5版本开始使用了一种全新的插件结构,利用了命名空间包,并且可以轻松地通过 Pip 进行安装。支持此结构地插件都会被安装在命名空间包 pelican.plugins 下,因此Pelican可以自动发现他们。下面的命令可以用于查看环境中用Pip安装的所有插件:

pelican-plugins

若将 PLUGINS 配置项设为默认的 None ,Pelican会自动发现命名空间中的插件并且将他们全部加载;若你在 PLUGINS 设置项中指定了一系列的插件,Pelican就不会去自动发现插件,也就是说,你需要显式地指定所有要使用的插件。

在配置 PLUGINS 时,有两种方式。一是用字符串列表指定插件的名称,可以是包含命名空间的完整名称(例如 pelican.plugins.myplugin ),也可以是简短名称( myplugin):

PLUGINS = ['package.myplugin',
           'namespace_plugin1',
           'pelican.plugins.namespace_plugin2']

二是在设置文件中先import进来,再将import进的模块放在 PLUGINS 设置项中:

from package import myplugin
from pelican.plugins import namespace_plugin1, namespace_plugin2
PLUGINS = [myplugin, namespace_plugin1, namespace_plugin2]

备注

在尝试不同的插件时(尤其是那些处理元数据和内容的插件),缓存可能会相互干扰,一些更改不会生效。发生这种情况时,就需要通过设置 LOAD_CONTENT_CACHE = False 或使用 --ignore-cache 命令行选项禁用缓存。

如果插件处于无法直接进行import的路径,可以在 PLUGIN_PATHS 配置项中指定这些路径。如下例所示, PLUGIN_PATHS 中的路径可以是绝对的,也可以是相对于设置文件的:

PLUGIN_PATHS = ["plugins", "/srv/pelican/plugins"]
PLUGINS = ["assets", "liquid_tags", "sitemap"]

在哪儿下载插件

新的命名空间插件可以在GitHub的 pelican-plugins 组织 中找到,每个插件都是一个独立的仓库。而老的插件则可以在GitHub的 pelican-plugins 仓库 中找到。这些老的插件会逐步淘汰并转移到新的命名空间版本。

请注意,尽管我们尽全力审查和维护这些插件,但这些插件是Pelican社区提交的,因此支持性和互操作性程度各不相同。

Community plugins can also be found on PyPI tagged with "Framework :: Pelican :: Plugins".

如何创建插件

插件是基于信号这一概念的。Pelican会发送信号,插件则订阅这些信号。可用的信号在下一节会贴出来。

对于插件来说,唯一需要遵循的规则就是一定要定义一个可调用的 register ,在 register 中需要将信号映射到插件逻辑上。下面是一个简单的例子:

import logging

from pelican import signals

log = logging.getLogger(__name__)

def test(sender):
    log.debug("%s initialized !!", sender)

def register():
    signals.initialized.connect(test)

备注

信号接收器在Pelican中是弱引用的,因此不能将它定义在可调用的 register 中,否则接收器在信号发送之前就会被回收。

对于关联到同一个信号的多个插件,将按照它们注册的前后顺序执行。但若设置了 PLUGINS 配置项,则会以此配置项中的顺序为准。如果您使用了无需PLUGINS设置的新版命名空间插件,它们将按照被探测到的顺序进行连接(与 pelican-plugins 输出的顺序相同)。若您此时仍想要显式指定顺序,设置 PLUGINS 配置项即可。

命名空间插件的结构

命名空间插件必须遵循特定的结构才能正常工作。这些插件需要是可安装的(即包含 setup.py 或其他等效文件),并且遵循下述文件夹结构:

myplugin
├── pelican
│   └── plugins
│       └── myplugin
│           ├── __init__.py
│           └── ...
├── ...
└── setup.py

非常关键的一点就是, pelicanpelican/plugins 文件夹下都 不能 包含 __init__.py 文件。事实上,这两个文件夹下最好是只有上面列出的文件夹,并且保证与插件相关的文件都仅包含在 pelican/plugins/myplugin 文件夹中,以避免奇奇怪怪的问题。

为了让大家更容易就能建立正确的结构,我们为插件提供了一个 cookiecutter模板 ,使用方法参考此项目README文件中的指示即可。

信号列表

下面是目前已经实现了的信号:

信号

参数

描述

initialized

pelican object

finalized

pelican object

所有generator执行完成后调用,即pelican退出之前。这对于自定义后处理操作是非常有用的,例如可以简化js/css资源、向搜索引擎告知更新后的sitemap。

generator_init

generator

在Generator.__init__中调用

all_generators_finalized

generators

在所有generator执行完后,写入输出内容前调用,

readers_init

readers

在Readers.__init__中调用

article_generator_context

article_generator, metadata

invoked after the content and metadata for the article has been generated; use if you need to adjust the article metadata before it gets used by Pelican.

article_generator_preread

article_generator

在ArticlesGenerator.generate_context读取文章之前调用;若代码需要在解析每篇文章前执行某些操作,就可以使用此信号。

article_generator_init

article_generator

在ArticlesGenerator.__init__中调用

article_generator_pretaxonomy

article_generator

在创建类别和标签列表之前调用。例如,当需要变更要生成的文章列表时可以使用,如此可以避免一些已移除文章在分类或标签列表中泄露。

article_generator_finalized

article_generator

在ArticlesGenerator.generate_context的最后调用

article_generator_write_article

article_generator, content

在写入每篇文章前调用,文章以内容的形式作为参数传入。

article_writer_finalized

article_generator, writer

在所有文章及相关联页面写入完成后,在文章generator关闭前调用。

get_generators

pelican object

在Pelican.get_generator_classes中调用,可以返回一个Generator,也可以以一个元组或列表的形式返回多个generator。

get_writer

pelican object

在Pelican.get_writer前调用,可以返回一个自定义Writer。

page_generator_context

page_generator, metadata

page_generator_preread

page_generator

在PageGenerator.generate_context读取页面前调用,若代码需要在解析每个页面前执行某些操作,就可以使用此信号。

page_generator_init

page_generator

在PagesGenerator.__init__中调用

page_generator_finalized

page_generator

在PagesGenerator.generate_context的最后调用

page_generator_write_page

page_generator, content

在写入每个页面前调用,页面以内容形式作为参数传入

page_writer_finalized

page_generator, writer

调用于所有页面写入完成后,在页面generator关闭前。

static_generator_context

static_generator, metadata

static_generator_preread

static_generator

在StaticGenerator.generate_context读取静态文件前调用,若代码需要在每个静态文件加入静态文件列表前进行一些修改,就可以使用此信号。

static_generator_init

static_generator

在StaticGenerator.__init__中调用

static_generator_finalized

static_generator

在StaticGenerator.generate_context的最后调用

content_object_init

content_object

在Content.__init__的最后调用

content_written

path, context

每一次内容文件写入后调用。

feed_generated

context, feed

每个feed生成前调用。可以用于在feed写入前修改之。

feed_written

path, context, feed

每一个feed文件写入后调用。

警告

请避免使用 content_object_init 信号读取content对象的 summarycontent 属性,这可能导致在 内部链接 时无法解析链接(请参阅 pelican-plugins bug #314 )。请改用 _summary_content 属性,或者就在后续阶段再运行插件(例如 all_generators_finalized 时)。

备注

Pelican3.2之后,信号名都进行了标准化,较老的插件可能需要进行更新:

旧名称

新名称

article_generate_context

article_generator_context

article_generate_finalized

article_generator_finalized

article_generate_preread

article_generator_preread

pages_generate_context

page_generator_context

pages_generate_preread

page_generator_preread

pages_generator_finalized

page_generator_finalized

pages_generator_init

page_generator_init

static_generate_context

static_generator_context

static_generate_preread

static_generator_preread

具体使用方法举例

下面分享了一些创建插件的具体方法,请享用!

如何创建一个新的reader

你可能需要添加对输入文件格式的特殊支持。这似乎可以作为Pelican核心的一个功能,但我们选择避免将此功能放在核心中,而是通过插件实现不同的reader。

做出这个决定主要是因为实现这样的格式支持插件非常容易,而且这样在不需要此功能时也不会影响Pelican自身的速度。

多说无益,下面是一个具体例子:

from pelican import signals
from pelican.readers import BaseReader

# Create a new reader class, inheriting from the pelican.reader.BaseReader
class NewReader(BaseReader):
    enabled = True  # Yeah, you probably want that :-)

    # The list of file extensions you want this reader to match with.
    # If multiple readers were to use the same extension, the latest will
    # win (so the one you're defining here, most probably).
    file_extensions = ['yeah']

    # You need to have a read method, which takes a filename and returns
    # some content and the associated metadata.
    def read(self, filename):
        metadata = {'title': 'Oh yeah',
                    'category': 'Foo',
                    'date': '2012-12-01'}

        parsed = {}
        for key, value in metadata.items():
            parsed[key] = self.process_metadata(key, value)

        return "Some content", parsed

def add_reader(readers):
    readers.reader_classes['yeah'] = NewReader

# This is how pelican works.
def register():
    signals.readers_init.connect(add_reader)

添加新的generator

添加一个generator也非常简单,你可能会想要看一看 Pelican内部机制 ,其中有关于如何创建generator的内容。

def get_generators(pelican_object):
    # define a new generator here if you need to
    return MyGenerator

def register():
    signals.get_generators.connect(get_generators)

添加新的writer

添加writer可以让你将其他文件格式输出到磁盘,或者可以改变现有格式写入磁盘的方式。请注意,一次只能启用一个writer,因此请确保继承了内置的Writer,并且完全重新实现之。

下面是启用你的自定义writer的一个基本例子:

from pelican.writers import Writer
from pelican import signals

class MyWriter(Writer):
    # define new writer functionality
    pass


def add_writer(pelican_object):
    # use pelican_instance to setup stuff if needed
    return MyWriter


def register():
    signals.get_writer.connect(add_writer)

使用插件添加内容

可以通过插件以可编程的方式添加文章或页面。如果你打算从某些API获取文章,这就会很有用。

下面是一个简单的示例,说明了如何使用 article_generator_pretaxonomy 信号构建一个添加自定义文章的插件:

import datetime

from pelican import signals
from pelican.contents import Article
from pelican.readers import BaseReader

def addArticle(articleGenerator):
    settings = articleGenerator.settings

    # Author, category, and tags are objects, not strings, so they need to
    # be handled using BaseReader's process_metadata() function.
    baseReader = BaseReader(settings)

    content = "I am the body of an injected article!"

    newArticle = Article(content, {
        "title": "Injected Article!",
        "date": datetime.datetime.now(),
        "category": baseReader.process_metadata("category", "fromAPI"),
        "tags": baseReader.process_metadata("tags", "tagA, tagB")
    })

    articleGenerator.articles.insert(0, newArticle)

def register():
    signals.article_generator_pretaxonomy.connect(addArticle)