控制你的标记语言
我虽然博文写得不多,但十分渴望有一种任我支配的标记语言来满足我对博客排版的需求。或者说正是因为花了很久时间去寻找这种语言,所以我的博文很少。
创造如愿的标记语言实在是有意思的话题,同时也是很多人思考过并以五花八门的方式解决过的问题。可是那些解决方案的存在就像一个个孤岛,大家都在自说自话,没有一个全景式的导览。也许是我的搜索能力不够,也许是大部分人并不关心这个问题。总之,我准备把我知道的方法都简单介绍一遍——因为它们确实很有趣。
目录
Pollen
我是在读在线书《Practical Typography》时发现Pollen的:那本书的作者Matthew Butterick(MB)曾经用WordPress发布过一本在线书,但是他觉得WordPress不够灵活,所以基于Racket的Scribble方言创造了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。以image和link为例,你可以把它们变成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写的文档。MB为Pollen写了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转化为HTML的tag;而这个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
这是我目前使用的方法,详情在《用Pandoc把Markdown转成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出发这一点很有趣。