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

相關文章
相關標籤/搜索