Pelican的一些历史

警告

此页面来自原作者 Alexis Métaireau 在2010年12月完成Pelican后作的一篇报告,因此其中涉及的具体内容可能和最新的Pelican有些出入。

Pelican是一个简单的静态博客生成器。它解析标记文件(目前主要是Markdown和reStructuredText),并生成一个文件夹,其中包含了对应于标记文件的HTML。由于Python很简单并且符合需求,我选择使用Python来实现Pelican。我不想为每个东西定义一个类,但同时又想要各部件之间低耦合。事实证明,这正是我想要的。在发展过程中,多亏了用户给的反馈,我花了些时间修复了一些问题。到目前为止,我已经将Pelican的代码重构了两次,每次重构都不会超过30分钟。

使用场景

我之前使用的是WordPress,你可以将它部署在Web服务器上来管理博客。大多数时候,我更喜欢使用Markdown或reStructuredText等标记语言来撰写文章。为此,我一般用vim来写这些文章。我认为让大家自行选择用于写文章的工具是很重要的。在我看来,博客管理器应该能够接受任何类型的输入并将其转换为博客站。Pelican就采取这一思想。您可以选择自己喜欢的工具以及标记语言来撰写文章,然后生成静态的HTML博客站。

_images/overall.png

为了足够的灵活性,Pelican中支持使用模板,这样你就可以编写自己的主题了。

设计过程

Pelican来源于我的需求。从单文件应用程序出发,不断成长为现在功能丰富的应用。首先,我写了一份需求文档;然后创建了我想要解析的内容(reStructuredText文件),并开始实验性的编写代码。Pelican的第一个能够使用的版本包含了200行代码、10个函数以及1个类。

我不断遇到各种问题,在使用过程中还想要往Pelican中添加功能。在对代码的第一次修改中,添加了对配置文件的支持。虽然可以在命令行中往里传入选项,但当配置项多起来后,就会变得异常冗长。同样地,Pelican支持了越来越多的功能:Atom订阅源、多主体支持、多标记语言支持等等。在某一时刻,单文件应用已经不适合Pelican了,因此我决定多做些工作,将应用分离到多个文件中。

我将系统整体逻辑分为如下几个类和概念:

  • Writers 负责文件的写入工作,即负责完成 html、RSS订阅源等文件的写入。因为这些操作都是比较常用的,这个类只会被创建一次,然后再传给Generators。

  • Readers 用于读取不同格式的文件(目前支持Markdown、reStructuredText,但可以继续扩展)。向 Readers 输入一个文件,它会返回文档的元数据(作者、标签、分类等等)与HTML格式的文档正文内容。

  • Generators 用以生成不同的输出,Pelican自带了 ArticlesGeneratorPageGenerator 。给定一套配置信息, Generators 可以做几乎任何事。但大多数情况下,它的工作就是从输入生成文件。

同样,还要处理正文对象。正文对象可以是 ArticlesPagesQuotes 或者其他你想要的类型。这些对象在 contents.py 模块中完成定义,同时代表了应用中使用到的内容。

更细节的内容

以下是Pelican中涉及的类的概述。

_images/uml.jpg

上图中的接口事实上并不存在,我是为了整张图的完整性才加上去的。在实际实现中,使用了鸭子类型而不是接口。

应用内部按以下流程进行处理:

  • 首先,解析命令行,并根据用户给入的一些内容来初始化不同的generator对象。

  • 创建一个 context ,其中包含了来自命令行和文件的配置信息。

  • 调用各generator对象的 generate_context 方法来更新 context

  • 创建 Writers 并将其给入generator的 generate_output 方法。

由于当generator生成输出时并不会改变上下文,我进行了两次调用。换句话说,第一个方法 generate_context 会修改上下文,而第二个方法 generate_output 不会。

然后,事情就取决于各generator在 generate_contextgenerate_content 中做的操作了。拿 ArticlesGenerator 举例可以帮助理解其他的一些概念。下面是调用 generate_context 方法后会发生的事情:

  • 读取文件夹路径,查找并加载每个restructured文件,并为每个文件构建一个正文内容对象( Article )。此工作是由 Reader 对象完成的。

  • 根据所有的文章更新 context

然后, generate_content 方法使用 contextwriter 来生成想要的输出。