今年三月份的时候因为受不了jekyll的种种问题,便花了点时间写了个自用的生成器。sushi的自定义灵活度应该要比jekyll和hugo之流要高得多,一旦配置完成,应该会十分顺手——所以就有了这篇介绍如何配置sushi的文章。
使用sushi的理由
灵活性和自定义是sushi的特色。
sushi力争做到KISS〔Keep It Simple Stupid〕,不做任何多余的事情。sushi只做一件事情,用一句话概括,就是:根据各处的配置,调用converter把你的博文转化为html格式,然后放到约定好的位置,其余的事情一概不做。
这件事情十分简单,使得sushi的代码本身十分的简单。而“其余的事情”就交给其余的程序去做,sushi不会强制用户选择哪个程序来完成这些”外围“的工作,允许用户发挥想象力,进行灵活的自定义,比如:
把markdown或者其他格式翻译为html(交给pandoc,或者用户自己写一个翻译器)
主题管理
部署(如果在本地测试,可以用sfz;正式部署可以用nginx、caddy等)
统一管理图片等资源(这个工作完全是多余的)
全文检索(交给专门的全文检索软件)
但也不是说sushi什么都不做,sushi本身围绕着组织站点结构这一任务展开,对于分类〔taxonomy〕、分页〔pagination〕、套模板〔template〕也有简单但是灵活的接口。
后记中具体举了一些例子,说明为什么这些灵活性是很重要的。多个简单的程序之间分工合作,也能实现很多复杂的任务,而灵活性是这种分工的前提。
当然,如果你不需要这些灵活性,不在乎自定义性,而是需要一个包打天下、囊括所有任务的生成器,那么就没有必要使用sushi,可能你更适合WordPress或者Jekyll、Hugo。
sushi原理简介
因为sushi遵循KISS原则,所以使用sushi必须对其原理有个大致的了解。
站点结构
sushi对于站点结构的要求很少,但站点必须放在一个文件夹内(下面称为站点目录)。站点目录下必须要有_site.yml
(站点配置文件),以及_converters
(转换器目录)、_includes
(页面片段目录)、_templates
(模板目录)。sushi会自动把生成后的站点放到_gen
(生成目录)中。如下:
site-example # 站点目录
├── _converters # 转换器目录
├── _gen # 生成目录
├── _includes # 页面片段目录
├── _site.yml # 站点配置
└── _templates # 模板目录
这些下划线_
开头的目录和文件总体的作用,就是为sushi生成页面提供必要的信息,具体作用后面将会解释。
这些目录和文件的命名是可以修改的,sushi不强制用户使用默认的命名。如果要使用自定义命名,则需要在命令行参数或者配置文件中指明。
工作流程
首先,sushi会读取_site.yml
作为站点配置文件,构建。然后紧接着读取_gen
,再然后是_converters
、_templates
、_includes
。读取结束后,sushi就掌握了这个站点整体有哪些配置、有哪些可用的转换器、哪些可用的模板、哪些页面片段、已经生成了哪些页面。
接下来,sushi开始遍历整个站点树,所有不以下划线_
开头的文件和文件夹都会被访问一遍,访问的同时sushi用自己内部的数据结构表示这一站点树。以_
下划线开头的文件和目录都会被sushi忽略。
然后进入生成阶段。sushi根据遍历得到的站点树,开始进行生成。sushi会在_gen
目录中创建一个一模一样的站点树。在这个生成后的站点树中,所有的页面文件都被替换为渲染后的HTML文件或者其他的某种文件。
这里展开说明生成一个页面的过程:首先会读取它的扉页内容〔Front Matter〕,然后读取其主体部分。如果配置文件中指定了生成器,那么就调用指定的生成器(生成器是一个可执行程序,sushi会把页面的主体部分送入生成器的标准输入,然后读取其标准输出作为渲染的结果)。接着sushi把渲染后的结果,连同站点配置、站点树、扉页中的页面配置、所需的页面片段填入指定的liquid模板中,最后把最终结果输出到站点树的对应位置;如果没有指定生成器,sushi则执行默认操作,即原封不动地把文件复制到生成的站点树的对应位置。
再展开说套用模板的过程:sushi使用liquid模板语言,有关详情可以参照liquid的文档。sushi会将读取的站点配置存到site
对象中,扉页的配置存到page
对象中,分页器相关的内容放到paginator
对象中,站点树的内容存放到sitetree
对象中等等。这些对象使得模板编写变得十分灵活(虽然有时可能会显得不太优雅),具体详情可以参照README。
基本使用
开始之前先安装sushi。目前(2022.12.29)暂时建议使用cargo安装。
cargo install sushi-gen
然后,找个站点主题(或者自己做一个),比如Empty。
git clone https://github.com/fpg2012/sushi-theme-empty
然后,找个合适的位置,然后创建站点,假设你的站点名字叫做Hey
ssushi init --theme ${path_to_theme} Hey
把${path-to-theme}
替换成你刚才下载的主题的路径。然后站点就创建完成了。
以Empty主题为例,所有posts文件夹下的内容都会被列到主页index.html
中。以这个主题出发,参考文档进行自定义应该会容易很多。
外部工具
Pandoc
sushi本身不提供页面渲染的功能,它只调用你提供的渲染器。这里十分推荐使用pandoc,因为它很方便自定义。如果在可以写个shell脚本直接调用Pandoc。比如在Linux下,可以写成:
#!/bin/bash
pandoc -t html
如果引入了自定义的pandoc过滤器,那么自行添加相应的参数。然后给这个脚本可执行权限,然后在_site.yml
中指定使用它。假如这个脚本叫做convert.sh
,那么_site.yml
中这样写可以让sushi把所有的md文件都转换成html:
convert_ext: ["md"] # 让sushi把md文件看作是需要渲染的页面文件,而非普通文件
converter_choice:
"md": "convert.sh" # md文件选用刚才的脚本进行转换
GitHub Pages
要部署到GitHub Pages,首先你需要创建一个GitHub项目,然后启用GitHub
Pages功能。然后把生成的_gen
目录中的内容传上去。
如果你想要把站点的源文件和生成后的页面放在一个仓库中。可以考虑使用git
worktree。比如说把源文件放在main
分支中,把生成后的页面放在gh-page
分支中,可以这样:
git checkout -b gh-page # 创建gh-page分支
git checkout -b main # 回到main分支
git worktree add --track - gh-page _gen
然后删除_gen
中的所有内容。那么之后sushi生成的东西会自动放到gh-page分支中。详情参考worktree的用法。
Pygments
实现代码高亮。有现成的用pygments实现高亮的pandoc filter,直接使用之。
SFZ
sfz可以简化在本地部署静态页面,这对于发布前预览页面尤其有用。sushi没有类似jekyll serve
的功能,这部分工作就交给sfz或者其他代劳了。下面给出了一种方法。
首先,把_site.yml
复制一份,重命名为_test.yml
。然后,修改_test.yml
,把里面的url
改成http://localhost:5000
或者其他本地的地址。然后添加一项:
gen_dir: _test_gen
之后,可以用以下命令模拟jekyll serve
。
ssushi build -A -c _test.yml && sfz -r _test_gen
Just
sushi生成站点的命令可能会显得有点长。just可以帮助缩短这些命令。
可以创建一个justfile
:
all:
ssushi build -A
inc:
ssushi build
test:
ssushi build -c _test.yml
sfz -r _test_gen
test-all:
ssushi build -A -c _test.yml
sfz -r _test_gen
clean-gen:
cd _gen
rm -rf *
cd ..
deploy-gen:
cd _gen
git add .
git commit -m "Update: `date`"
git push
cd ..
deploy: deploy-gen
git commit -a
git push
之后要生成站点,直接敲just
,增量生成则敲just inc
。
要执行类似jekyll serve
的操作,直接just test-all
或者just test
。
你可以按自己的需要修改justfile
。
主题编写
实际上sushi并不存在什么主题,所谓主题其实也和一般站点没有任何差别。ssushi init
做的事情其实是把主题的那个文件夹原封不动地拷贝过来。
一般而言,编写主题和写一般的网站基本没有区别,主要的区别在于使用liquid的模板。关于liquid模板,可以参照其文档。
这里建议对于一般的博客,至少准备page
、post
、archive
、home
四个模板便于用户使用,如果可以的话还可以加入rss的模板。模板之间可以有“继承关系”,比如套post模板后的内容需要再套入page
模板,这些可以通过模板的扉页来实现。
而通用的Javascript脚本、页面header、footer等部分放到_includes
文件夹中,用liquid的include
语句包含进来。
上面提到的Empty主题是个不错的起点,可以在其基础上参照文档进行修改和扩展。
后记
我最初编写sushi其实是为了解决两个简单的问题:
在渲染页面时直接转换数学公式。
图片可以随心所欲地放在任何位置,并被任何一个markdown页面引用。并且确保在markdown编辑器(比如Typora中)可以正确地查看图片。
这两个看起来微不足道的需求,居然没有办法很好地用Jekyll或者Hugo实现。
第一条不能解决,是因为它们为了追求渲染速度和开箱即用,使用了自己的markdown渲染器,用户很难做修改,除非改源码。但如果要渲染数学公式,就非得修改markdown的渲染过程不可,到这里我就卡住了。主流解决方案是把数学公式渲染的工作甩锅给前端JavaScript,但这会带来一大堆问题;还有人修改了Hugo的源码,但这也非常麻烦。
第二条不能解决,是因为它们对站点结构做了太多的限制,连插图这点小事也要管。为什么就不能保持我站点树的结构不要做修改呢?
所以就有了sushi。渲染器自己定,那就没有什么不能渲染的了(刚好有Pandoc这个神器,自己写filter、reader、writer,什么文档都能转换);不改变站点树,那么自定义站点、使用现有工具都会变得简单。
当然sushi也有自己的问题:
为了灵活性牺牲了速度。sushi为了调用用户提供的、外部的渲染器,需要通过管道传递数据,速度掉了一大截。如果你用pandoc,并且使用Python编写的pandoc过滤器,那就更慢了。增量生成部分地解决了速度慢的问题,但目前的增量生成仍不完美,依然有待改进。
开箱即用有一定的门槛。
用的人少
当然这些缺点都是可以克服的……比如速度慢,你可以修改ssushi的源码(和Jekyll与Hugo的源码不同,sushi的源码很短),使它直接调用Rust编写的渲染器。