orez 的故事

去年 8 月,我写了「zero 的故事」系列,介绍了本身写的一个很小的文式编程工具 zero。如今郑重宣告,它死了。css

事实上,zero 的生死,对这个世界没有任何影响。尽管从理论上说,我是它惟一的用户,可是它的死对我也没有任何影响。由于在它死去以后,orez 就诞生了。不过,orez 的生,对这个世界继续不会有什么影响。html

zero 之因此死,是由于它的结构与实现代码太混乱。虽然它可以很好的完成我赋予它的使命,但它仍是死了。死于个人游手好闲以及对美的追求。python

我听见有人在笑。「对美的追求」,写出这么一句的我,也在笑。准确的说,是对更为紧凑的代码的追求。zero 用的代码并很少,大约 1600 行 C 代码,所用的数据结构都是 GLib 所提供的。如今 orez 的代码大概 1100 行。仅仅缩减了 500 行代码,倒也不值得太过于称道,值得骄傲之处在于,整个 orez 的设计与实现的过程是较为严格的贯彻了文式编程的思想——文档与代码是相互结合的,而且它们的撰写是同步进行的。git

从 orez 的源文档中能够提取到 orez 的所有代码,见 main.c。由 orez 的源文档转换而成的 PDF 文档,恕我自私,我将它藏匿了起来。github

它存在的意义

Linus said:「Talk is cheap, show me the code.」编程

Orez said:「Code is cheap, show me the doc.」segmentfault

Orez 的世界观是,程序的文档和程序自己能够放在一块儿来写。你能够用文本编辑器建立一份文本文件,而后在这份文件中写一段文档,而后再写一段代码。让这两个过程交替进行,像拧麻花同样,像织毛衣同样,像左右互搏同样,像左脚踩右脚右脚又踩左脚的梯云纵同样。orez 认为,这样能够实现码农们男耕女织的美好新生活。数组

按照 orez 的想法,我建立了一份名为 km.orz 文本文件,而后写了下面这段话:浏览器

\type{km_init} 函数实现了 $k$ 均值聚类初始化过程:从样点集合中选取指定数量的样点
做为初始种类中心,而后初始化种类树——对样点集合进行首次分类。

\type{...}$...$ 均为 TeX 排版标记。不懂 TeX,也能够用其余文档排版语言提供的标记来构造相同的内容。服务器

接下来,在 km.orz 中写一段代码:

@ agn_km.c #
static AgnTree * km_init(AgnArray *points, size_t K)
{
        size_t *indices = generate_indices(K, 0, points->n - 1);
        # 初始化种类中心 -> init_centers @
        # 初始化种类树 -> class_tree @
        agn_array_free(init_centers);
        free(indices);
        return class_tree;
}
@

其中,@ ... ## ... @ 以及 @ 这些奇怪的符号,是 orez 的暗语,如今能够不用理睬它们。

接下来,我以为有必要解释一下 km_init 这个函数的返回值的数据结构,因而继续撰写文档:

\type{km_init} 函数的返回结果是种类树,它是 \type{AgnTree} 的一个实例。种类树
的结构分为三层,根结点,亦即第一层结点,不存放数据;第二层结点表示种类,存放的数据为
种类中心。第三层结点存放分配到相应种类的样点序号。

若是没有上述文档内容的解释,该怎么理解 km_init 返回的那个 class_tree 是什么东西呢,从它的类型定义能看出来吗?

typedef struct AgnTree {
        struct AgnTree *upper;
        struct AgnTree *next;
        struct AgnTree *lower;
        void *data;
} AgnTree;

从它的类型定义,只能看出来它是一棵树。

km_init 函数的所有代码分析吗?此时,km_init 函数尚未彻底的写出来,它只是个外壳,它的主体内容目前只是两个占位语句,即:

# 初始化种类中心 -> init_centers @
# 初始化种类树 -> class_tree @

即便 km_init 函数已经彻底实现出来了,经过它的源代码能理解 class_tree 吗?

static AgnTree *km_init(AgnArray *points, size_t K)
{
    size_t *indices = generate_indices(K, 0, points->n - 1);
    AgnArray *init_centers = agn_array_alloc(K);
    for (size_t i = 0; i < K; i++) {
        init_centers->body[i] = agn_point_copy(points->body[indices[i]]);
    }
    AgnTree *class_tree = agn_tree_alloc();
    for (size_t i = 0; i < K; i++) {
        agn_tree_prepend(class_tree, init_centers->body[i]);
    }
    assign_points(class_tree, points);
    agn_array_free(init_centers);
    free(indices);
    return class_tree;
}

聪明人也许很快就能看出,class_tree 的根结点存储了一个样点链表的指针;第二层结点存储了 K 个随机样点,并且这些样点彷佛扮演的是什么什么中心点的角色。那么 class_tree 的结构就是这样了吗,它还有没有第三层结点?这须要看 assign_points 函数的实现方能知道。此时,assign_points 函数我尚未实现……即便它实现了,经过它能理解 class_tree 的结构吗?

static void assign_points(AgnTree * class_tree, AgnArray * points)
{
    for (size_t i = 0; i < points->n; i++) {
        AgnTree *t = class_tree->lower;
        for (AgnTree * it = t->next; it != NULL; it = it->next) {
            if (agn_point_cmp(it->data, t->data, points->body[i]) <= 0) {
                t = it;
            }
        }
        size_t *id = malloc(sizeof(size_t));
        *id = i;
        agn_tree_prepend(t, id);
    }
}

看懂了 assign_points 函数的实现,若是此时尚未忘掉本身本来的意图——弄懂 class_tree 的结构,那么如今应该可以得出结论:

\type{km_init} 函数的返回结果是种类树,它是 \type{AgnTree} 的一个实例。种类树
的结构分为三层,根结点,亦即第一层结点,不存放数据;第二层结点表示种类,存放的数据为
种类中心。第三层结点存放分配到相应种类的样点序号。

但是,通过逐层分析所所得出的结论,不正是前文中我撰写的那段程序文档内容吗?

我,做为写代码的人,能够在彻底实现 km_init 函数以及它的辅助函数 assign_points 以前,就能理解 class_tree 的结构,可是做为阅读代码的人,须要通读全部代码才能弄明白一件在我看来是再简单也不过的事。

有一个问题,为何不直接将为代码所写的文档内容做为注释嵌入到代码之中?

代码中的注释确定有助于理解代码,可是这些注释之间的联系极为微弱,它无法像文档那样划分章节,并在这些章节之间创建关联。咱们阅读一份文件,老是习惯自上而下的阅读。这种阅读习惯致使咱们先看到 assign_points 函数的注释及其实现代码,可是咱们却被迫的先跳过它,去看下面的 km_init,如若否则,你就很难搞明白 assign_points 的真正做用。在看了 km_init 的注释及代码以后,再回过头来看 assign_points 的实现。为了阅读一份比较长的源代码,你不得不上下求索那些漫漫且修远的函数调用关系,而后顺着这个关系去阅读代码及其注释……最终,恭喜你,你终于在大脑中构建出了一份文档片断与代码片断的混合物——orez 的源文档。

安装

$ git clone https://github.com/liyanrui/orez.git
$ cd orez
$ make
$ sudo make install

绕出

km.orz 是以 orez 所提倡的那种编程方式的一个示例,可以使用如下命令获取它:

$ wget https://github.com/liyanrui/orez/raw/master/example/ConTeXt/km.orz

km.orz 包含了许多 C 代码片断,还有一部分 Bash 代码,其余内容彻底由 ConTeXt(一种 TeX 格式)标记文本构成。这些 C 代码片断按照我我的的行文习惯以一种较为容易理解的逻辑顺序嵌于 km.orz 文件之中,如何将它们提取出来交给 C 编译器呢?

从编译器/解释器的角度来看,orez 的行为是很是荒谬的。由于 orez 彷佛将编译/解释器所喜欢的代码片断顺序彻底的打乱了,像是将一根线弄的乱成一团。事实上并不是如此,orez 在打乱代码片断顺序的同时,也为每份代码片断放置了一些标记。经过这些标记,能够随时将这些代码片断从新排列为编译器偏好的那种形式并保存为程序代码文件。

km.orz 文件中,有一份名为「agn_km.c」的代码片断:

@ agn_km.c # ^+
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include "agn_km.h"
@

注: @ ... # 中的 ... 即是代码片断的名称。

能够将它想象为一根线的线头。若系统中已经安装了 orez,使用如下命令,可将这根线——km.orz 中全部这个代码片断相关联的代码片断抽取出来:

$ orez --tangle --entrance="agn_km.c" km.orz --output=agn_km.c

抽取结果是可编译的 agn_km.c 文件。

orez 支持短命令选项,上述命令可简写为:

$ orez -t -e "agn_km.c" km.orz -o agn_km.c

km.orz 中不止一根线。例如还有一根线,其线头分别是「ang_km.h」。orez 可将多根线一并绕出:

$ orez -t -s "," -e "agn_km.h, agn_km.c" km.orz -o "agn_km.h, agn_km.c"

-e-o 选项的值相同,这只是偶然,并不是必然。前者是各个「线头」的名称,后者是绕出的各条「线」的名称,它们必须逐一对应,不能乱了次序。

其实,km.orz 还有一根线头「gen-km-src.sh」,经过它能够绕出一份 Bash 脚本:

$ orez -e gen-km-src.sh -o gen-km-src.sh km.orz

执行这份 Bash 脚本可从一组 .orz 文件中绕出一个 $k$ 均值聚类的测试程序的所有源码文件。

注:因为不便将所有源码文件都放出,因此在上述示例中绕出的源码文件或 Bash 脚本均不可用。

若线头的名称与绕出的源码文件名称相同,能够省略 -o 选项。例如:

$ orez -t -s "," -e "agn_km.h, agn_km.c" km.orz

上文所用的「线头」与「线」的比喻,不是我创造的。在好久好久好久之前,Donald Knuth 将这个过程称为 tangle。多是由于这个过程,相似于从一团混乱的线团中找到一根线的线头,缠在绕线器上,将它抽取出来。Knuth 所写的文学编程工具 WEB,用于从 WEB 文件中抽取可编译的 PASCAL 代码的工具就叫 tangle,「orez -t」与之相似。

编程语言是形式化的,遵循严格的机器规则——图灵机,或遵循某种数学演算规则——Lambda 演算。orez 试图将天然语言与编程语言结合到一块儿,有人对此持否认态度。不过,他们否认的角度彻底是错的。他们很是错误的将文式编程理解为让天然语言凌驾于编程语言之上,或者让编程语言更像天然语言。事实上,文式编程强调的是代码片断出现的顺序符合人类的阅读习惯,而且对代码进行良好的注解,并不是在强调天然语言能够替代程序语言。不过,认为直接经过阅读源码就能够弄懂源码的人,在使用「orez -t」从 orez 源文件中绕出可编译的代码以后,能够将 orez 源文件删除,以此表示对 orez 世界观的蔑视。

秩序

虽然在 orez 源文件中,代码片断并不是是按照编译器的逻辑顺序分布的,可是 orez 提供了一些特殊的标记,经过这些标记能够随时将代码片断按照编译器或解释器的逻辑顺序提取出来。这是上一节「绕出」部分所讲述的内容。

咱们在 orez 源文件中实际上维护了代码片断的两种顺序,一种是人序,另外一种是机序。人序是可见的,当咱们从上而下逐行阅读 orez 源文件或它经由排版软件产生的文档时,所看到代码片断的排列顺序即是人序。机序,是指代码片断按照程序代码编译器或解释器的运做规律所创建的顺序。在 orez 源文件中,代码片断的机序由几个特殊符号来维护。

代码片断的定义与引用

最基本的机序是代码片断的定义与引用。定义一个代码片断,基本格式以下:

@ 代码片断的名称 #
[... 代码片断 ...]
@

@ 是代码片断名称的起始符。# 是代码片断名称的终结符。

下面是一份来自 km.orz 的代码片断的定义:

@ 样点从新分类 #
empty_classes(class_tree);
assign_points(class_tree, points);
@

代码片断的名称能够是除 @# 以外的任意字符。代码片断必须以 @ 做为结尾。

每一个代码均可以做为一个起点,orez 可从它开始绕出一份代码。只不过有的代码片断能够绕出一份很长的代码,有的只是绕出个线头。譬如上面的「样点从新分类」这个代码片断,从它开始,绕出的代码是这个代码片断自己:

$ orez -t -e "样点从新分类" -o foo.c km.orz

若某个代码片断引用了其余代码片断,那么以它为起点绕出一份比它更长一些的代码。代码片断的引用语法以下:

# [... 被引用的代码片断的名称 ...] @

与代码片断名称的格式相反,此时 # 变成了代码片断引用标记的起始符,而 @ 变成了代码片断引用标记的终结符。

例如,在 km.orz 中,上述的「样点从新分类」这个代码片断被另外一个代码片断「agn_km.c」引用了,后者的定义以下:

@ agn_km.c # +
<agn-km-classify>
AgnTree * agn_km_classify(AgnArray *points, size_t K)
{
        AgnTree *class_tree = km_init(points, K);
        while (!class_tree_stablized(class_tree, points)) {
                # 样点从新分类 @
        }
        return class_tree;
}
@

若以「agn_km.c」为起点,在绕出其代码的过程当中,orez 会遇到代码片断「样点从新分类」的引用。此时,orez 会先绕出代码片断「样点从新分类」的内容,而后再继续绕出「agn_km.c」的内容。代码片断的引用链越长,orez 绕出的代码也就越长。

代码片断的累加

除代码片断的引用能够延长绕出的代码以外,还有一种同名代码片断的机制能够实现这种效果。所谓同名的代码片断,就是先定义一个代码片断,例如:

@ agn_point.c #
AgnPoint *agn_point_copy(AgnPoint *x)
{
        AgnPoint *y = agn_point_alloc(x->n);
        y->n = x->n;
        for (size_t i = 0; i < y->n; i++) y->body[i] = x->body[i];
        return y;
}
@

接下来,还能够定义一样名称的代码片断,可是代码片断名称终结符 # 以后必须跟随 +^+ 运算符。例如上文中已经出现的那份代码片断:

@ agn_point.c # +
void agn_point_free(AgnPoint *x)
{
        free(x->body);
        free(x);
}
@

可将同名的代码片断视为一个数组中的元素。+ 表示将当前的代码片断追加到数组的尾部,而 ^+ 表示将当前的代码片断追加到数组的首部,它们只能出如今代码片断名称终结符 # 以后,且与 # 处于同一行。当 orez 对同名的代码进行绕出时,它会从这个数组中的首个元素开始,逐一进行代码绕出。

同名的代码片断,颇有效缓解了个人命名恐惧症。在撰写 km.orz 的过程当中,大部分代码片断都是同名的代码片断,主要借助 +^+控制它们的机序。

标签

当我实现了 orez 对基于 +^+ 的同名代码片断的支持以后,不由暗自佩服本身,居然也能想出一些好主意。不过,很快就发现了问题,单凭 +^+ 来控制同名代码片断数组元素的次序,没法实如今数组的指定位置插入一个同名代码片断。因而,我又想出一个好主意,为代码片断附加一个标签。例如,上文出现的代码片断「agn_km.c」即是一份带标签的代码片断:

@ agn_km.c # +
<agn-km-classify>
AgnTree * agn_km_classify(AgnArray *points, size_t K)
{
        AgnTree *class_tree = km_init(points, K);
        while (!class_tree_stablized(class_tree, points)) {
                # 样点从新分类 @
        }
        return class_tree;
}
@

<agn-km-classify> 即是标签,它只能出现于代码片断名称终结符的下一行。咱们将这份代码片断称为代码片断「agn_km.c <agn-km-classify>」。

如今,若想将一个代码片断插入到代码片断「agn_km.c <agn-km-classify>」在同名代码片断数组中的位置以前,亦即前者在同名代码片断数组中的下标值比后者小 1,此时即可借助代码标签来实现这一目的,即:

@ agn_km.c # <agn-km-classify> ^+
static bool class_tree_stablized(AgnTree *class_tree, AgnArray *points)
{
        # 生成新旧种类中心集合 -> centers 与 new_centers @
        # 判断 centers 与 new_centers 是否相同 -> stablized @
        for (size_t i = 0; i < centers->n; i++) {
                agn_point_free(centers->body[i]);
        }
        agn_array_free(centers);
        agn_array_free(new_centers);
        return stablized;
}
@

代码片断标签在参与 +^+ 运算时,它们只能处于当前代码片断名称的终结符 # 以后,运算符以前。

织出

orez 可将 orez 源文件转换为 YAML 文件。Donald Knuth 将相似的过程称为 weave(织出)。由于程序文档像一张网,不只文档的内容中存在着许多交叉引用,并且代码片断之间也存在着许多交叉引用——代码片断之间的引用关系以及带标签的代码片断的 +^+ 运算均产生交叉引用。事实上,将 orez 源文件转换为 YAML 文件的过程当中,orez 程序的主要工做是生成代码片断之间的交叉引用信息。至于文档内容中的交叉引用,直接交于可与 orez 相结合的文档排版工具来处理。

基于 YAML 文件,orez 可以与许多排版工具取得结合。咱们的时代已经进步到了不须要再为得到一份排版美观的程序文档而像 Donald Knuth 写 TeX 那样拼的程度了。

对于网页形式的程序文档,有 Markdown 这种的轻量级的标记语言,也有 reStructuredText 这种重量级的标记语言,实在不济,还有 HTML 5 + CSS 3 + JavaScript。它们都有助于生成美观的网页形式的程序文档。对于由 orez 源文件转换而来的 YAML 文件,主流的脚本语言均能借助一些 YAML 解析库将其转换为 Markdown,reStructuredText,或 HTML 5 + CSS 3 + JavaScript 的文档形式。若是文档中须要数学公式,能够学点 TeX 数学标记,将显示数学公式的任务交给 MathJax 来作。

对于面向纸本书籍排版的程序文档,有 TeX,LaTeX 或 ConTeXt。过去,这些排版工具对中文支持的不够好,排版字体的配置极为繁琐。如今有 XeTeX 与 LuaTeX 引擎了。

总之,过去很难解决的文档排版问题,如今都不是问题。如今的问题是掌握这些工具。

在普遍意义(不限于印刷意义)的文档排版方面,我较为熟悉 Nikola 与 ConTeXt。Nikola 能够将 Markdown 或 reStructuredText 之类的标记语言转化为静态网页。因为 Nikola 支持代码高亮显示,能嵌入 MathJax 实现 TeX 数学公式的显示,可以以静态站点的形式发布文档,所以我将它做为 orez 战略合做伙伴,写了一份名为 orez-md 的 Python 脚本实现两者的沟通交流。

获取 hello-world.orz:

$ wget https://github.com/liyanrui/orez/raw/master/example/markdown/hello-world.orz

从 hello-world.orz 中织出 hello-world.md 的命令为:

$ orez -w hello-world.orz -o hello-world.yml
$ orez-md hello-world.yml > hello-world.md

$ orez -w hello-world.orz | orez-md > hello-world.md

而后将 Markdown 文件交给 Nikola 来处理便可。

注:Nikola 的安装与配置指南,见:https://getnikola.com/getting...

对于面向纸本书籍排版的程序文档方面,要从好久好久以前提及。早年,受王垠的蛊惑,喜欢上了 ConTeXt,还为它写了一个支持中文排版的插件。为了继续维护这种喜欢,我将 ConTeXt 也做为 orez 的另外一个战略合做伙伴,写了一份名为 orez-ctx 的 Python 脚本实现两者的沟通交流。能够经过如下命令,可将前几节用做示例的 km.orz 转换为 YAML 文件,进而由 orez-ctx 产生 ConTeXt 文件,最后由 ConTeXt MkIV 生成 PDF 格式的程序文档:

# 假设已经 `git clone https://github.com/liyanrui/orez.git`
$ cd orez/example
$ orez -w km.orz | orez-ctx > km.tex
$ context km.tex

注:orez-style.tex 相对于 km.tex,相似于 CSS 文件相对于 HTML 文件

注:有关 ConTeXt MkIV 的安装以及中文环境的配置,可参考我写的一些文档

若是但愿 orez 可以支持其余的文档排版工具,这只能自力更生,本身编写一些脚本将 YAML 文档转换为指定的文档排版语言。结合 orez 的源文档,看懂 orez 生成的 YAML 文档应该并不困难。

借 Nikola 的力

Nikola 是使用 Python 开发的一个静态网站生成工具。它接受使用 Markdown 或 reStructuredText 之类的标记语言撰写的文档,输出 HTML 页面。Nikola 的安装以及基本用法,可参考它的「Getting Started」文档。下面我要讲述 orez 如何与 Nikola 取得结合,以实现 orez 源文件向 HTML 页面的逐步转换。

建立文档站点

如今,假设你已经具有了一个可用的 Nikola 环境。可以使用如下命令创建名为 orez 站点(能够根据本身喜爱取其余名字):

$ nikola init --demo --quiet orez
$ tree -L 1 orez
orez
├── conf.py
├── files
├── galleries
├── images
├── listings
├── posts
├── README.txt
├── stories
└── templates

其中,conf.py 是 orez 站点的配置文件,output 目录是 orez 站点的根目录——该目录内存放 Nikola 生成的网页文件。

让 Nikola 支持 Markdown

Nikola 默认支持的文档标记语言是 reStructuredText,若使之支持 Markdown,须要修改 conf.py,将其中的变量 POSTSPAGES 的值修改成:

POSTS = (
    ("posts/*.md", "posts", "post.tmpl"),
    ("posts/*.rst", "posts", "post.tmpl"),
    ("posts/*.txt", "posts", "post.tmpl"),
    ("posts/*.html", "posts", "post.tmpl"),
)
PAGES = (
    ("stories/*.md", "stories", "story.tmpl"),
    ("stories/*.rst", "stories", "story.tmpl"),
    ("stories/*.txt", "stories", "story.tmpl"),
    ("stories/*.html", "stories", "story.tmpl"),
)

注:若但愿本身定制站点首页,可将 PAGES 设为:

PAGES = (
    ("stories/*.md", "", "story.tmpl"),
    ("stories/*.rst", "", "story.tmpl"),
    ("stories/*.txt", "", "story.tmpl"),
    ("stories/*.html", "", "story.tmpl"),
)

这样,在使用 nikola new_postnikola build 命令时,这些命令会将后缀名为 .md 的文件做为 Markdown 文档进行处理。

建立 orez 源文件

首先在 orez 站点建立一个目录 orez-meta,专用于存放 orez 源文件:

$ mkdir orez-meta

面向 Nikola 的 orez 源文件,本质上也是一份 Markdown 文件,只是其中的代码片断是 orez 的特有格式。所以,能够先让 Nikola 为咱们生成 Markdown 文档模板,而后将其做为 orez 源文件使用。

做为示例,在 orez 目录下,执行如下命令:

$ nikola new_post -f markdown --title="hello-world"

即可建立一份 Markdown 文件 hello-orez.md。

所建立的 hello-world.md 位于 posts 目录,nikola new_post 命令会在这份文件中写入一些模板信息:

<!-- 
.. title: hello-orez
.. slug: hello-orez
.. date: 2016-08-07 07:47:40 UTC
.. tags: 
.. category: 
.. link: 
.. description: 
.. type: text
-->

Write your post here.

这些模板信息可根据自行酌情修改。

而后将 hello-world.md 文件复制到 orez-meta 目录,并将后缀名改成 .orz,这样便有了一份 orez 源文件。接下来能够向这份文件中按照 orez 的理想撰写程序文档,编写程序代码……若仅仅是为了体验一下,可复制 orez 项目的 example/markdown 目录中的 hello-world.orz 文件。

orez-md

orez 源文件编辑完毕以后,即可将其再转换为 Markdown 文件。对于上一节建立的 hello-world.orz 文件,可以使用如下命令将其再次转换为 hello-world.md 文件,并将这份文件覆盖 posts 目录中由 nikola new_post 命令建立的那份 hello-world.md 文件:

$ orez -w hello-world.orz | orez-md > ../posts/hello-world.md

文档生成

将 orez 源文件转换为 Markdown 文件以后,即可在 orez 目录下执行:

$ nikola build

nikola build」 命令可将 posts 目录中的全部 Markdown 文件转换为 HTML 文件,并将这些 HTML 文件存放于 output/posts 目录。output 目录即是 Nikola 静态站点全部网页的的根目录。

而后执行:

$ nikola serve --browser

即可运行 Nikola 内置的 HTTP 服务器,并开启浏览器定位到 orez 站点的首页。若是上述过程没有出错,应该可以在 orez 站点首页上看到标题为「 Orez 的 Hello World 示例」的文档。

自定义 css 文件

若是但愿 orez 源文件转换而成的网页可以美观一些,而且但愿代码片断可以高亮显示,那么须要对 orez 站点的 css 文件进行一些定制。在 orez 目录中建立 mycss 目录,将我制做的一份 css 文件 tweak.css 放入该目录:

$ mkdir mycss
$ cd mycss
$ wget https://github.com/liyanrui/orez/raw/master/example/markdown/tweak.css

而后修改 orez 站点的配置文件 conf.py,将其中的 FILES_FOLDERSEXTRA_HEAD_DATA 的值修改成:

FILES_FOLDERS = {'mycss': 'mycss'}

EXTRA_HEAD_DATA = """
<link href="/mycss/tweak.css" rel="stylesheet" type="text/css">
<base target="_parent" />
"""

注:FILES_FOLDERS 的值是驱使 nikola build 命令将 mycss 目录复制到 output 目录——orez 站点根目录。若是直接在 output 目录中建立 mycss 目录并置入 css 文件,它们可能会被 nikola build 命令清除。

而后再次执行:

$ nikola build

即可将 tweak.css 中定义的样式做用于站点的全部页面。

hello-world-page

答疑

Q:你真的在用它?

A:Right. 除了让 orez 可以「自举」以外,咱们在用它来写一个小型的 C 库:

agn-doc

Q:使用「orez -t」命令从 orez 源文件中提取的代码,若编译时出错,如何将错误信息定位到 orez 源文件?

A:使用 orez 的 --line 选项(简写为 -l)能够在提取的代码中增长一些 #line 语句。例如:

$ orez -t -l -e agn_km.c km.orz

提取的代码,其中某个片断以下:

#line 170 "./km.orz"
AgnTree *
agn_km_classify(AgnList *points, size_t K, AgnArrayEnv *env) {
        AgnTree *class_tree = km_init(points, K, env);
        while (!class_tree_stablized(class_tree, env)) {
                #line 237 "./km.orz"
                empty_classes(class_tree);
                assign_points(class_tree, points, env);
                #line 175 "./km.orz"
        }
        return class_tree;
}

对于 C/C++ 代码而言,这些 #line 语句可让编译器将代码定位到 orez 源文件。对于非 C/C++ 语言,可想法将 #line 语句修改成代码注释,而后对代码进行人肉定位……

Q:在使用「orez -w」命令将 orez 源文件转换为 YAML 文件时,为什么有时会遇到相似下面这样的警告信息?这种状况该如何处理?

WARNING **: Line 324: Programing Language of <@ agn_km.c #> is unknown!

A:之因此会出现这样的警告信息,是由于 orez 但愿排版工具可以对代码片断进行高亮处理。排版工具在对代码进行高亮处理时,须要知道代码片断所用的编程语言。要消除这些警告信息,只需在做为起点的代码片断中添加编程语言标记。例如:

@ agn_km.c # [C]
<km-init>
static AgnTree *
km_init(AgnList *points, size_t K, AgnArrayEnv *env) {
        size_t n = agn_list_size(points);
        size_t *indices = generate_indices(K, 0, n - 1);
        # 初始化种类中心 -> init_centers @
        # 初始化种类树 -> class_tree @
        agn_array_free(init_centers, NULL);
        free(indices);
        return class_tree;
}
@

其中,[C] 即是编程语言标记。编程语言标记只能位于代码片断名称终结符 # 以后,且位于 +^+ 运算符(包括带代码片断标记的运算符)以前。

不须要为每一个代码片断添加语言标记。orez 会将某个代码片断的语言标记传播到与该代码片断有直接或间接联系的全部代码片断。

Q:如今有适合编辑 orez 源文件的文本编辑器吗?

A:应该是没有。这个世界上,有哪一种文本编辑器可以在编辑一份文本文件的过程当中,同时支持某种排版标记或语言,某种或多种编程语言?

不过,有一些迂回的办法。譬如,若是用 Emacs,以下图所示,能够开启两个窗口,一个用于撰写文档,一个用于编写代码。代码写好后,复制到文档中。若是我有更多的时间,会考虑基于这种模式写一些便民的 Emacs 命令。

orez and emacs

相关文章
相关标签/搜索