插件¶
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
非常关键的一点就是, pelican 和 pelican/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对象的 summary 或 content 属性,这可能导致在 内部链接 时无法解析链接(请参阅 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)