控制你的标记语言

我虽然博文写得不多,但十分渴望有一种任我支配的 标记语言(Markup Language) 来满足我对博客排版的需求。或者说正是因为花了很久时间去寻找这种语言,所以我的博文很少。

创造如愿的标记语言实在是有意思的话题,同时也是很多人思考过并以五花八门的方式解决过的问题。可是那些解决方案的存在就像一个个孤岛,大家都在自说自话,没有一个全景式的导览。也许是我的搜索能力不够,也许是大部分人并不关心这个问题。总之,我准备把我知道的方法都简单介绍一遍——因为它们确实很有趣。

目录

Pollen

我是在读在线书《Practical Typography》时发现Pollen的:那本书的作者Matthew Butterick(MB)曾经用WordPress发布过一本在线书,但是他觉得WordPress不够灵活,所以基于RacketScribble方言创造了Pollen

It occurred to me that what I wanted was not a simple but regimented system like WordPress—it just wouldn’t let me work with the sophistication and detail I needed. Instead, I wanted a flexible tool for describing complex HTML & CSS layouts with simpler, high-level notation.

In short: I wanted my own programming language.

So I did. I named the resulting language Pollen.

如果用Pollen来写博客的话,大概是这样:

#lang pollen

◊(define-meta title "席德・米德的赞歌"
              date  "2019-12-31T15:59:56+08:00"
              tags  '(Painting Gundam))

◊image["图片地址"]{图片描述}

小时候第一眼看到倒A高达就迷得不行,
还去他的网站想找找有没有倒A的设计图啥的。
然后发现了好多没见过风格的画,
最后右键下载了一张◊link["链接地址"]{特厉害的红色高架桥}。

Pollen眼里,整个文章是一个形如下面形式的X-expression

xexpr = string
      | (list symbol (list (list symbol string) ...) xexpr ...)
      | (list symbol (list xexpr ...))

文本中以菱形开头的叫作tag,它们对应同名的tag function。每个tag function接收括号里的参数,经过你想要的处理后返回一个X-expression。以imagelink为例,你可以把它们变成HTML<img><a>:

#lang racket/base

(provide image link)

(define (image url alt)
  `(img ([alt ,alt] [src ,url])))

(define (link url text)
  `(a ([href ,url]) ,text))

所有的tag function执行完毕后我们就可以得到经过转换的,表示整个文本的X-expression。此时你可以遍历它,来生成自己想要的输出——可以是HTML、PDF,或者未来出现的任意格式。

如果你觉得Pollen多此一举,不如Markdown的话,不妨读一读MB的这两段文字:

如果你想使用Pollen,可以MB写的文档。MBPollen写了Quick tour、The big picutre、连续的四个tutorial以及按话题分章节的讨论。我希望所有写文档的人都向MB学习。

我尝试过使用Pollen写博客,但它运行起来实在太慢了,所以放弃了:写博客可是要live preview的。但Pollen的想法真的很好,所以我仍然很喜欢它。

Quad

你可能早发现了,Pollen tag的格式太像TeX了。但那只是表示方法像罢了,Pollen的重心并不是计算布局与输出PDF。我曾经感到可惜:Pollen可是能革TeX命的工具,结果只差最后五十米。

后来发现MB单独给那五十米做了一个叫Quad的工具,并且可以和Pollen连接起来。只是Quad仍处在早期阶段,支持的排版方式不多

Lambda Project

Alain Marty也发现了使用S-expression表示HTML的结构比使用XML清爽许多。他用JavaScript写了一个类Lisp的语言Lambda Talk,让他可以混合计算和HTML。然后他又为Lambda Talk写了可以用作wiki的编辑器Lambda Tank。他对这些以Lambda命名的项目的概述是:

S-expression based markup, styling & scripting

我多次想搞明白这项目到底是怎么回事,但作者的介绍页排版实在有些不适合阅读。所以我对它的认识也只是介绍开头的部分。可是直觉告诉我Alain Marty在寻找的东西也是完全自己做主的标记语言。

Tcl

HTML是棵树,函数嵌套起来也是棵树,所以可以用嵌套的函数来表示HTML。如果用普通的编程语言以这种方式写HTML,那函数调用的括号和字符串的引号将会充斥整个屏幕,给书写和阅读都带来困难。换句话说,如果有一种编程语言不需要括号和引号,那就很适合用来写博客。这样的编程语言确实存在,它的名字叫Tcl(读作挠痒痒的tickle

事实上,我是看到有人用Tcl做静态网站生成器才发现Tcl确实挺合适的。在那个生成器里,每篇文章都是可以运行的Tcl程序。多说无益,来看例子:

set Title 这是文章标题
set Date 1970-01-01

set Contents {
  section 二级标题 {
    txt {
       Tcl里万物皆字符串。
       如果字符串需要换行,可以用花括号把它包裹起来。
       在这里的文字会被喂给cmark。
       cmark是命令行里的Markdown渲染器。
       所以这个生成器里可以写Markdown。
    }

    code c {
int main() {
    puts(
        "code函数的第一个参数是代码语言,第二个参数是代码。"
        "code函数把两个参数交给命令行程序chroma来得到高亮后的HTML。"
    );
}
    }
    ……

虽然看起来和Pollen很像:每个tag都是函数(具体定义在markup.tcl,但理念不完全相同:Pollen将文档视为树,通过遍历节点把自定义tag转化为HTMLtag;而这个Tcl程序的函数只是按顺序向字符串尾部拼接HTML片段(在markup.tcl。StackOverflow上有一名篇指出HTML不该用正则表达式解析,我想说的是:HTML也不该用字符串拼接的方式生成。

Markdown里插入代码

MDX

就是Markdown + JSX,没什么好说的。看起来挺好,但是依赖Node.js。

我曾经尝试使用它,直到在一个叫next-mdx-remote的库的README里看到了这段劝退:

Data has shown that 99% of use cases for all developer tooling are building unnecessarily complex personal blogs. Just kidding. But seriously, if you are trying to build a blog for personal or small business use, consider just using normal html and css. You definitely do not need to be using a heavy full-stack javascript framework to make a simple blog. You'll thank yourself later when you return to make an update in a couple years and there haven't been 10 breaking releases to all of your dependencies.

Pandoc filters

这是我目前使用的方法,详情在《PandocMarkdown转成Markdown。其实Pandoc filters可以干的活,放在别的Markdown解析器上也可以干。选择Pandoc的原因是它只有一个二进制文件(内嵌了Lua解释器,而且可以输出Markdown。

为什么不用Org-mode

Org-mode相对markdown有更多的功能以及根植于Emacs的可扩展性。然而正是因为Org-mode功能太丰富我才选择扩展Markdown——扩展的平台必须足够简单,才能让扩展完的成果保持简单。

当然,Org-mode只有在Emacs中才能获得良好体验也是我选择Markdown的原因之一。

Soupault

前面的方法都聚焦于如何生成HTML,而Soupault着眼于如何转化(其他方法生成的)HTML。类似Pandoc,它也有跨平台的二进制文件,内置Lua解释器让你尽情扩展。

我不想手写HTML,也不想在用Soupault之前先用Pandoc/cmark生成HTML,所以没有用它。但是它选择从HTML出发这一点很有趣。