Lektor —— 静态内容管理系统

标签: lektor 内容管理 系统 | 发表时间:2017-12-15 15:14 | 作者:wzyboy
出处:https://wzyboy.im/

尝试了一下 Lektor 这个静态内容管理系统,觉得挺好用的,于是把博客从 WordPress 迁移到了 Lektor。

一、动态博客与静态博客

本博客的历史可以追溯到 2009 年,至今已经 8 年,一直都是使用 WordPress 作为博客软件。头几年写博客热情高涨,比较高产,而近几年由于种种原因,已经很少写博客了,最近两年基本是一年一更的节奏。博客本身也疏于打理,偶尔想起来,登录一次 WordPress,等待我的却是满满一筒的垃圾评论(被反垃圾插件过滤的),偶尔还能从垃圾筒里捞出几个被误杀的非垃圾评论。除此之外的例行便是点一下 WordPress 里的升级按钮,把 WordPress 本身和各种插件、主题全部更新成最新版本。

前段时间无意中发现了 Lektor 项目。阅读了一下作者的自述,产生了不少共鸣:

The longer I'm programming and creating software, the more I notice that I build a lot of stuff that requires maintenance even though it should not. In particular a topic that just keeps annoying me is how quickly technology moves forward and how much effort it is to maintain older code that still exists but now stands on ancient foundations.

This is not a new discovery mind you. This blog you're reading started out as a Django application many, many years ago; made a transition to WordPress because I could not be bothered with updating Django; and then turned into two different static site generators because I did not want to bother with making database updates and rather wanted to track my content in a git repository.

Armin Ronacher's Thoughts and Writings

写得不多,却要隔段时间维护一次——这正是我使用 WordPress 写博客的感受。当然,我并不说 WordPress 不好——WordPress 本身是一个很成熟而强大的内容管理系统,但是对我的使用场景来说,也许一个静态博客更加适合我。

我曾听说过 Jekyll, Huxo, Hugo 等静态网站生成器,但是从来没有尝试过。而 Lektor 正巧在我产生「我想试试静态博客」这样的念头的时候撞了上来,并且它使用的 Flask 和 Jinja2 这两大项目都是我使用较多、比较熟悉的(这三个项目都是同一作者 Armin Ronacher),应该会上手比较快吧?抱着这样的念头,我花了几天时间,把 WordPress 成功迁移到了 Lektor。这篇博客便是在 Lektor 里写的第一篇博客。

二、Lektor Quickstart

在正式使用一个项目之前我喜欢把它的文档完整读一遍(而不是只看「Quickstart」部分),Lektor 的文档并不长,我很快就读完了,以下是我总结的「Quickstart」。

安装与初始化

作为一个 Python 项目,Lektor 的安装无非就是 python -m venv venv --prompt lektorpip install lektor。静态博客肯定会用到版本管理,所以初始化一个 Git repo 存放 venv 和 Lektor 工程文件是比较自然的事情。Lektor 提供了 lektor quickstart 可以交互式地创建一个空的工程目录,里面有一个示例的站点,可以用 lektor server 起一个 dev server 然后在浏览器中访问查看。

lektor server 做了两件事情,一是把工程文件构建成 .html 文件输出到 $HOME/.cache/lektor/ 目录下,二是起一个 HTTP Server 服务这个目录。另外,Lektor 还会监视工程文件的变动,如有变化会自动重新构建。需要注意的是,在 dev server 的情况下,Lektor 会在页面里插入一段 JavaScript,在每个页面右上角显示一个悬浮的编辑按钮,用于在它的 /admin 页面编辑本页,实际上磁盘里已经输出的文件里并没有这样的 JavaScript 和按钮。实际写博客的时候,不一定要用它的编辑后台,可以直接用编辑器打开工程文件,在里面写。

目录结构

一个 Lektor 项目的工程文件是这样存放的:

.
├── assets
│�� ├── favicon.ico
│�� ├── robots.txt
│�� └── static/
├── content/
│�� ├── 1/
│�� │�� └── contents.lr
│�� ├── 2/
│�� │�� └── contents.lr
│�� ├── 3/
│�� │�� └── contents.lr
│�� └── contents.lr
├── models/
│�� ├── blog.ini
│�� └── blog-post.ini
├── templates/
│�� ├── blog.html
│�� ├── blog-post.html
│�� ├── layout.html
│�� └── macros/
└── wzyboy.im.lektorproject

几个目录的定义:

  • 以 .lektorproject 结尾的文件是项目的配置文件(文件名不重要);
  • content 目录是实际存放内容的目录,可以分层,但每层必须要有一个 contents.lr 文件,哪层没有,就不再向深处搜索;
  • models 目录定义了 contents.lr 的数据结构,即里面可以有哪些字段,每个字段的数据类型等,以下划线开头的字段是系统保留字段;
  • contents.lr 是纯文本文件,使用 Key: Value 存储字段,用单独一行三个连字符( ---)作为各字段的分隔线, Value 可以是多行的;
  • templates 目录存放 HTML 模板,用 Jinja2 写成,从 contents.lr 里解析出来的数据被填进这里,就渲染出了最终的页面;
  • assets 目录里的东西会保持整体结构叠加/覆盖到最终的输出目录里,可以用来放 CSS 和 robots.txt 之类的;

数据结构

直接以我定义的两个数据结构文件 blog.ini 和 blog-post.ini 为例。由于我不使用它的 /admin UI,所以与 UI 相关的 label 等属性我已经略去,以下为最简数据结构:

# blog.ini
[model]
name = Blog

[children]
model = blog-post
slug_format = /post/{{ this._id }}.html
order_by = -pub_date

[pagination]
enabled = yes
per_page = 5

blog.ini 里的数据结构用于博客的首页,它的子目录将会使用 blog-post.ini 里的数据结构,并且以 pub_date 字段降序排列(由新到旧),一次返回五个条目。如果翻译成 SQL 的话,大致是这样:

SELECT * from blog_post ORDER BY pub_date DESC LIMIT 5;

# blog-post.ini
[model]
name = Blog Post

[fields.title]
type = string

[fields.author]
type = string

[fields.pub_date]
type = datetime

[fields.body]
type = markdown

[fields.excerpt]
type = markdown

blog-post.ini 定义了单篇博客的数据结构,声明每个字段是什么数据类型。完整的数据类型定义在 这里

下面以我从 WordPress 迁移到 Lektor 的实际过程为例,展示这些数据结构的使用。

三、从 WordPress 迁移

兼容 URL 样式

从 WordPress 导出成 Lektor 的 contents.lr 文件可以用 lektor-exportor 这个 PHP 项目完成。导出的内容为每篇文章一个目录,每个目录里一个 contents.lr。目录名默认是文章标题。因为我的文章标题大部分含有中文字符,实际效果是一堆百分号转义符……为了美观,也为了方便和原来 WordPress 的 URL 结构( /post/<id>.html)保持一致,还是重命名一下好了。在 contents.lr 里有 _slug 字段保留了原来的 URL,把里面的 Post ID 提取出来作为目录的名字:

$ for i in *; do id=$(grep -Po '(?<=^_slug: /post/)\d+(?=\.html)' "$i"/contents.lr); echo $id; mv -v "$i" $id; done

重命名完成后,把这些以 Post ID 作为名字的目录复制到项目的 contents/ 目录下,再创建一个 content/contents.lr 文件,里面只有一行 _model: blog。这样,就把博客首页及每篇博客(即 contents/ 目录下的带 contents.lr 子目录)的数据结构声明完成了。

另外,在 blog.ini 里用 slug_format 规定了子条目的 URL 样式为 /post/{{ this._id }}.html,而 _id 这个系统字段即是目录名,这样最终的每篇文章的 URL 就和原来 WordPress 的一致了。之后的新文章不再需要 contents.lr 里指定 _slug 了,现有的文章的 _slug 字段也可以删除,而使用 blog.ini 里已经指定好的 URL 样式。

迁移 WordPress 附件

WordPress 中上传的附件(主要是图片)是按年月分目录存储的,比如 /wp-content/uploads/2012/01/foo.png 这样。Lektor 虽然每篇文章能添加附件(默认情况下放在文章目录里文件都会被当成附件),但是要把 WordPress 里已有的这些附件转换成 Lektor 的附件还是比较麻烦的,还不如直接丢 assets/ 目录里。然后我灵机一动,干脆直接传到 AWS S3 得了。于是把整个 /wp-content/uploads/ 目录用 rclone 上传到 S3,再用 sed 对现有文章做一次批量替换,就成功地完成了现有 WordPress 附件的迁移。

至于以后的附件么,我写了个 简单的 Python 脚本,上传图片到 S3 然后直接打印一个 <img> 标签。这样就可以方便地在文章里贴图了。甚至「目录」结构也是仿照 WordPress 那样按年月分类的。

WordPress 里上传图片之后会自动生成多个尺寸的缩略图。既然已经用 S3 作为图床了,那可以考虑建立一个 AWS Lambda,监听 S3 上传事件,检测到有新图片上传就自动生成缩略图传到同样的「目录」下。 这种 AWS Lambda 的用法甚至是 AWS 官方教程的一部分。

在迁移附件过程中,我发现我最早有一批附件并没有上传到 WordPress,而是上传到 Flickr, Picasa (!) 等图床,这些外链图片早就已经年久失修崩坏了,好在它们中的大部分还是能看出文件名的,而我所有的截图都是有存档的,于是我从备份中捞出了它们,用上面所说的脚本重新上传,顺便修好了这些外链。

生成文章摘要

由 WordPress 导出的数据里,每篇文章有个 excerpt 字段,存储了文章的摘要(默认是文章的前若干个字)。摘要可以在渲染博客首页的时候用到。Lektor 里并没有自动生成摘要的功能,但是 Lektor 竟然自带了一套神奇的 插件开发系统,使用 lektor dev new-plugin 即可交互式地建立一个插件的框架(自动放在工程目录的 packages/ 子目录里),里面已经有了 .gitignore, setup.py 和一个 Python 文件示例。它的插件系统非常好懂,就是在各种 event 上面挂钩子。那么在 setup-env 事件的时候往 Jinja2 Env 里注册一个生成摘要的 filter 即可。我写了一个 简单的示例,把文章内容的所有 HTML 标签扒掉,留下纯文本,然后截取前若干个字符返回。稍加改动可以实现更加复杂的摘要输出算法。

生成 Sitemap 与 RSS / Atom

WordPress 里有插件可以生成 sitemap.xml。但这 .xml 也不过就是个 markup language 文本文件而已,所以用 Jinja2 渲染一个就行了。参考 官方文档

至于 RSS / Atom,也是个 .xml 文件,理论上也可以用类似的方法渲染出来,不过考虑到 Google Reader 倒闭之后没多少人继续用 RSS 了,所以我暂时没去研究它的生成。(其实是我懒)这儿有个现成的 lektor-atom 插件可以用于生成 feed.xml 文件。比如本博客的 订阅链接现在就是 https://wzyboy.im/feed.xml。需要注意的是,由于没有动态服务器会往这个链接的返回里添加 Content-Type: 了,所以最好添加一个 .xml 扩展名,让 HTTP Server 自动添加一个合适的 Content-Type:

评论

静态网站没法实现评论系统,只能外包。可以考虑 Disqus 之类的。

主题

这个其实是最花时间的。WordPress 的主题多如牛毛,而 Lektor 的主题……几乎没有。好在我之前 WordPress 主题是一款基于 Bootstrap 的极简主题。我对 Jinja2 还算熟悉,对 HTML 和 CSS 也有一定的了解,于是对着 Bootstrap 的文档,很快就把 WordPress 的主题基本复制过来了。但是剩下的细节,比如文章里各种图片的 inline style 等,还是不够完美,触发了我的强迫症。于是我 sed + Perl + Beautifulsoup 各种工具齐上阵,终于把现有文章里各种图片的样式调对了,至少所有的页面都实现了响应式布局。

部署

如前文所说,通过 lektor server 在浏览器看到的页面几乎就是最终 HTML 了,只不过额外注入了一个 JavaScript。要真正部署的话,需要用一个全功能的 HTTP Server,如 Nginx,把最终的 HTML 文件服务起来,而不是用 Lektor 自带的简易 server。Lektor 自带了 deploy 命令,可以用 rsync 或 FTP 将 HTML 文件发布到别的机器上(通过第三方插件也可以支持发布到 S3 上)。但是我希望能看到两次发布之间到底有哪些修改,因此我选择把最终的 HTML 文件也纳入 Git 版本管理。使用 lektor build -O dist 即可最终文件输出到 dist/ 目录里,而不是默认的 $HOME/.cache/lektor/ 目录。将 HTTP Server 的 web root 指向该目录,即完成了部署。再也不需要调配 MySQL 和 PHP 了。

四、Lektor 最佳实践

目前就想到这两个。再有想到的再补充吧。

markdown 与 html

在定义 blog 数据结构的时候,body 和 excerpt 字段都是 markdown 数据类型(而不是 html 数据类型),但其实这些字段都是可以放 HTML 的。事实上 WordPress 导出的 contents.lr 里 body 字段都是 HTML。这个字段的内容是可以 Markdown 与 HTML 混排的。在写博客的时候,大部分元素 Markdown 比 HTML 方便,因为不用写一堆 <p><h1> - <h6>,但是面对一些稍复杂的元素,比如引用、代码块、表格等,Markdown 需要对 * _ 等代码常用字符进行转义,实在是不方便。而 Markdown 与 HTML 则解决了这个问题。本篇文章在写作的时候就是 Markdown 与 HTML 混排的, <p> <h1> - <h6> 是用 Markdown 写的,而 <blockquote> <code> <pre> <img> 等则用 HTML 写成。Lektor 在把 Markdown 字段渲染成 HTML 的时候,遇到成对的 HTML 标签会跳过,原样保留。这样的特性我很喜欢。

回收站

在 WordPress 里可以设置一篇文章的隐藏属性,或将一篇文章移入回收站。Lektor 里,有 _discoverable 和 _hidden 两个系统字段,如果前者为假,则 HTML 依然会生成,但是在文章列表中不会显示,只有知道 URL 才能访问到;如果后者为真,则 Lektor 会完全跳过这个文件不再生成 HTML,此时这个文件只有被 explicitly include 才会出现在输出的 HTML 里。如果有些文章不想公开却又舍不得删,可以调整这两个字段的真假值,达到类似于回收站的效果。如果建立一个 trash 目录,再在这个目录的 contents.lr 里指定这两个值,那么所有移入这个目录的文章都进了回收站。

五、后记

友人听说我从 WordPress 迁移到了 Lektor,很惊讶我竟然这么晚才尝试静态博客。的确,这不是什么最近才流行起来的东西。在我将博客从 WordPress 迁移到 Lektor 的过程中,我真切地感受到了静态博客最大的优势:所有的内容都是朴实、平白、易于操作的文本,可以用各种文本工具方便地批量修改,可以用版本控制工具比较与回退。 就像纯文本记账一样美好。

相关 [lektor 内容管理 系统] 推荐:

Lektor —— 静态内容管理系统

- - wzyboy’s blog
尝试了一下 Lektor 这个静态内容管理系统,觉得挺好用的,于是把博客从 WordPress 迁移到了 Lektor. 本博客的历史可以追溯到 2009 年,至今已经 8 年,一直都是使用 WordPress 作为博客软件. 头几年写博客热情高涨,比较高产,而近几年由于种种原因,已经很少写博客了,最近两年基本是一年一更的节奏.

壳系统

- Vernsu - It Talks-魏武挥的blog
经常有人被我问到“你用什么浏览器”时的答案是:傲游啦360啦,但事实上,这些都不是真正的浏览器,从技术角度讲,充其量只是在IE浏览器上加一个壳罢了. 在国外,壳浏览器是以“皮肤”的形式存在,纯属为了美化浏览器而用. 但在中国,壳浏览器成了一门生意. 奇虎的主要收入来源并非来自那个由于一场商战而赫赫有名的安全卫士,而是来自于360浏览器(它有两个版本,分别以IE和Chrome为内核).

秒杀系统

- - 开源软件 - ITeye博客
秒杀系统架构分析与实战. (反馈非常好的文章,推荐). (1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;(6)卖家发货. (1)低廉价格;(2)大幅推广;(3)瞬时售空;(4)一般是定时上架;(5)时间短、瞬时并发量高;. 假设某网站秒杀活动只推出一件商品,预计会吸引1万人参加活动,也就说最大并发请求数是10000,秒杀系统需要面对的技术挑战有:.

Ext文件系统

- Haides - 博客园-首页原创精华区
  虽然从Ext2到Ext4,找数据的方式发生了变化,但是,磁盘的布局还是非常相似的. 其实这个东西也不需要变化,因为现在也没什么特别巧妙的方式,而且磁盘的吞吐量、效率的瓶颈也不在这里. 当然,这里排除那些根据自身文件特点设计的数据库,毕竟还是为了支持通用文件.   Boot在第一个块,放的应该是引导程序,超级块就放在了第二个块上,如果不是可以在mount的时候通过参数sb来设置.

HBase 系统架构

- - 博客园_首页
HBase是Apache Hadoop的数据库,能够对大型数据提供随机、实时的读写访问. HBase的目标是存储并处理大型的数据. HBase是一个开源的,分布式的,多版本的,面向列的存储模型. 5 可在廉价PC Server搭建大规模结构化存储集群. HBase是Google BigTable的开源实现,其相互对应如下:.

Linux系统监控

- - CSDN博客系统运维推荐文章
查看所有的进程和端口使用情况:. 查看nginx并发(连接数)进程数:. 查看当网络连接状态中,已建立连接的数量:. 查看系统tcp连接中各个状态的连接数. 输出每个ip的连接数,以及总的各个状态的连接数. df -hl 查看磁盘使用情况 . df -hl 查看磁盘剩余空间. df -h 查看每个根路径的分区大小.

mysql 权限系统

- - 数据库 - ITeye博客
mysql 权限系统控制一个用户是否能进行连接,以及连接后能够针对那些对象进行什么操作. mysql权限控制包含两个阶段. 2:检查用户是否具有所执行动作的权限. 本文实例,运行于 MySQL 5.0 及以上版本. MySQL 赋予用户权限命令的简单格式可概括为:. 一、grant 普通数据用户,查询、插入、更新、删除 数据库中所有表数据的权利.

银行核心系统-贷款系统【信贷系统】

- - ITeye博客
一、         贷款业务. 贷款按期限分为短期、中期与长期贷款,短期贷款是指期限在1年以内的贷款,中期贷款是指期限在1年(含1年)至3年(含3年)之间的贷款,长期贷款是指期限超过3年的贷款. 贷款的种类目前有个人助学贷款和个人住房贷款:. l     个人助学贷款:须提供两位担保人,无须质押物,贷款额度不超过人民币10万元.

理解Linux系统负荷

- Adam - 阮一峰的网络日志
如果你的电脑很慢,你或许想查看一下,它的工作量是否太大了. 在Linux系统中,我们一般使用uptime命令查看(w命令和top命令也行). (另外,它们在苹果公司的Mac电脑上也适用. 你在终端窗口键入uptime,系统会返回一行信息. 这行信息的后半部分,显示"load average",它的意思是"系统的平均负荷",里面有三个数字,我们可以从中判断系统负荷是大还是小.

iPhone通知系统改进

- armgod - 月光博客
  不管是对比 Android 还是 WebOS ,iOS 的通知系统都只能用低能来形容,也是最让用户不满意的地方之一. 简单列举一些iOS的通知系统不足的地方. 你必须记得是哪个程序通知的你,不记得的话就只能在满屏的 app 中找到上一条或者上几条通知,实在是一件痛苦的事情. 不管你现在做什么都会被打断,只要来了通知就弹出,并且置顶获取焦点,比如你正在玩切水果,刚要破记录了,来一个短信,游戏暂停,短信置顶,节奏都被打乱了.