2

想写就写,灵思无疆:用自动化部署让写作更得心应手

 11 months ago
source link: https://sspai.com/post/80741
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

想写就写,灵思无疆:用自动化部署让写作更得心应手 - 少数派

1
想写就写,灵思无疆:用自动化部署让写作更得心应手
07 月 03 日

Matrix 首页推荐 

Matrix  是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。

文章代表作者个人观点,少数派仅对标题和排版略作修改。


本文速通:

  1. 「Static Website Generator」近些年的发展历程,简单聊聊代表性产品。
  2. 我的第一套自动化写作部署方案「Obsidian + Hexo」,只在 Obsidian 中实现「写作」+「发布」。
  3. 我的第二套自动化写作部署方案「mdbook + Gitlab CI」,尝试线上写作。

第一套方案用于日常学习笔记和个人博客的发布,第二套方案用于自己最近写书。两套方案都比较小众,前者需要有一定的 Obsidian 使用经验,且针对 MaxOS/Linux 用户,没有对 Windows 用户进行适配;后者需要有一定的 git 使用基础。下面先为大家展示效果,感兴趣的读者可以直接跳转到对应章节。

只用在 obsidian 中通过一个快捷键即可发布到自己的个人博客/Github Page/Medium等平台

1

只用 git push 就可以完成网页的自动发布:

1

运维不应该是写作的负担

相信很多少数派的作者们都有过自建博客的经历。在读本科的时候,我自己也有一次建站经历,不过后面变得不了了之。流量统计、全栈搜索、MathJax 配置、代码复制等一系列看起来似乎非常基础的功能,都需要自己一项一项去处理。而且当时自己贪心不足,还试图维护了评论区等功能,导致本来就不够稳定的博客系统雪上加霜。这一年里,自己花费在运维上的成本远远高于写作本身。于是,在第二年年末,服务器需要续费的时候,自己终止了这一计划。

在之后的复盘分析中,我认为自己将过度的经历放在了「运维」和「管理」上,而忽视了最底层的「生产」。好比企业的发展,没有底层的生产力,优秀的管理系统和运营策略也很难成功。之后的几年里,我的写作风格也变得更「实用主义」,日用的笔记主要是为了方便自己进行阅读和检索,为此我选择了 Obsidian。丰富的插件社区可以让我能便捷的跨平台阅读自己的笔记。

最近,在少数派的鼓励下,自己的分享欲和创作欲再次被激发。慢慢继续写起这些知识共享的文章。随着自己的积累和沉淀,自己的生产效率和写作速度都得到了一定的提升。尽管现在的语言水平还有提升的空间,但是「码字」这件事情本身已经逐渐变得更加得心应手了。在经历了一段时间的写作和整理之后,自己也对写作工作流程有了一定的改良和提升。在保证了一定的文字积累后,适当筛选,进行整理和输出,从而保证了自己的「产量」。回过头来,就可以清晰的看到自己创作、发布和部署上的痛点和盲点。而不是和自己以前那样,盲目维护一个自己用不上的庞杂系统,最后事倍功半。

千里之行,始于足下。所以在搭建自己的写作系统之前,最重要的还是要多写。正如我之前在 Robert Birming 的 Blog 读到的一句话所言:「I don't wanna tell you what to do, but I know it works, so I say it as plainly as I can: Just write, keep on writing.」1

个人建站的选择

在解决了内容和产量的问题之后,对于建站的事情,也确实是一个见仁见智的选择。不过对于以内容为主的方案,我相信主流的写作方案应该都是基于 Markdown 进行排版和写作的。然而 .md 格式并不是一个在传统网站架构体系内的一环。「HTML」+「CSS」+「JS」中,对内容的组织和引用,并不会直接将 Markdown 文件变成网页布局。最直观的想法是使用 pandoc 或其他导出方案,将 Markdown 转到 HTML。但这样对样式可能产生一定的破坏,而且生成的 HTML 文件并不适合直接作为网站内容,因为该文件只包含了文章内容。考虑到美观性,网站的头尾部分可能需要添加标题,可能会在侧方添加章节目录。所以,为了解决这一痛点,「Static Website Generator」就此诞生,将「基于 Markdown 的网站生成」简化到最简。大家常见的 Hexo 和 Hugo 都是代表产品。

Jamstack - generators 收纳了许多 generator,着实有一种「乱花渐欲迷人眼」的感觉。大家选择当然也是自由的,不过除了「易用」和「美观」之外。在挑选的过程中,一些其他的维度大家应该作为考虑:

  • 能否导出 PDF,作为写书的用户可能会有需求
  • 轻量的站内搜索功能
  • tag 和 category 等分类管理系统,博客用户必备
  • 能否执行代码,创作编程教程类用户可能需要

对于 PDF 导出,mdbookmkdocs 是我使用体验比较好的产品,可以方便的进行 PDF 格式导出(mkdocs 需要通过插件实现)。站内搜索有通过 Algolia 实现的方案,但是对于静态页面,HexoDocsify 都有默认的搜索功能,使用体验也尚可。如果觉得这些工具使用有难度,也可以试试 Gitbook

所以对于个人用户而言,「易于维护」和「一定程度美观」作为自己的优先考虑要素比较合适。我工作使用最多的是 mkdocs,插件比较丰富,作为 wiki 比较简单好用,且维护成本非常低。个人生活则维护了两套其他方案,在下面的章节中分别进行介绍。

用「Obsidian + Hexo」实现自动化发布

本文的重点不是 Obsidian 或者 Hexo 本身,Obsidian 和 Hexo 的相关笔记在互联网上也有非常丰富的资料,对它们的使用这里也不做赘述。Obsidian 凭借丰富的社区插件,实现了惊人的拓展能力。所以也吸引了我使用 Obsidian 作为自己的笔记系统。经过多年的发展和进步,Obsidian 的能力也在不断变强。而这个时候,我也对自己的笔记发布过程进行了一些反思。

Wikilink:又恨又爱的语法

从流程上回顾,我是这么进行笔记发布的:

  1. 进行整理和筛选
  2. 将文章导出成 Standard Markdown,替换 Obsidian 中的 Wikilink
  3. 将引用的图片附件上传到图床
  4. 复制 Markdown 到 Hexo 对应目录,最后使用 Hexo deploy 命令进行发布和部署

这里不得不诟病 Obsidian 的 Wikilink 的兼容性。写作的时候确实非常好用,而且可以非常便捷的对图片的大小进行微调。例如这里,我只需要在图片文件名之后用 |[width] 进行指定说明,就可以对图片的宽度进行限制。虽然在 Markdown 中也可以使用 HTML 语法,使用 <img ...> 对图片进行限制。但是 Wikilink 的语法确实给写作带来了流畅感。对尺寸的修改是「增量式」的,而不是对图片的引用进行整行的替换,当我专注于写作的时候,确实宛如神兵利器。例如下面的这个例子,我直接使用 |600 就将一张过小的图片进行了宽度调整,导出 PDF 的时候也更加美观。因此,这也让我不舍得关闭 wikilinks 的编辑功能。

1

可是如果要导出一个标准的 Markdown 到其他的静态页面生成器的时候确实也非常麻烦了,因为在 Hexo 等静态网页生成器中,他们只能识别最基本的图片引用,而不能很好的对 Wikilink 语法进行支持。同理,不仅仅是图片,如果 Markdown 引用了其他的 Markdown 文件,这里也非常难进行支持。这个时候我想起了我本科参加 Hackthon 的一个作品,「Make Slides Automatically From Markdown」,初衷想法是借助「reveal.js」将 Markdown 文档自动变成可展示的 PPT。可惜的是,当时的作品没有很好的运维下去,后续的很多替代品,例如 vscode 插件,和 Marp 的出现,都比我最初版本做的更好。不过当初作品中保留下来了一些非常有价值的思想:自动翻译。

如果要具体的实现 Markdown 内容的全部转换工作,需要借助 AST(Abstract Syntax Tree) 的思想。这个概念来自编译原理,是一种用于表示编程语言中的代码抽象语法结构的树形数据结构。通常由编译器或解释器在解析源代码时生成。可用于进行语法分析、代码转换、代码优化和错误检测等操作。下面2通过 mdast 展示了一个例子。

1

所以通过将每一种语法类型分别处理,再重新进行组合拼装,就可以达到我们需要的效果。例如我们在将 Markdown 进行分割处理(专业一点的说法叫 parse)之后,对我们需要处理的节点进行转换,例如 Wikilink,替换成我们需要的格式就好了。

最终简化版本的程序在:obs2hexo - github(代码比较简陋,希望大家见谅)

主要功能为:替换引用图片的 wikilink,分析 tag,发布到指定的分类上。支持 picgo 上传图床。安装方法也非常简单,直接使用

pip install o2h

即可完成安装。

不过,项目中的缺点也非常明显。在这里进行自我批评和反省:

首先, wikilink 是可以引用非图片格式的文件的,例如其他笔记和 pdf 类型,但是项目中都没有进行处理。其次,wikilink 需要单独一行写出,这里也是由于程序简化导致的,为了方便检测,我进行匹配的方案比较简单粗暴,但是实际引用的时候,可能会有更加复杂的引用情况,例如放在列表中引用。此外,目前为止应该没有支持 Windows,我日用系统是 Linux 和 MacOS,所以还没有在 Windows 下进行测试。这部分我会尽快处理,争取在两周内完成新版本的整理更新。

总之,基于这个小代码项目,就完成了对 Obsidian 文件中 wikilink 和一些细节语法的自动处理。但是距离自动发布还有一些距离,这个时候,另一个 Obsidian 插件,帮我完成了自动化的最后一块拼图。

Obsidian Shell Commands

在我起初使用 Obsidian 的很长一段时间里,我一直不理解这个功能的作用。因为作为 Shell Command Executor,这个功能又太过于鸡肋。如果我为了执行一个脚本,我为什么不通过 iTerm 等终端直接执行呢?

不过后来,在我有了自动化的需求之后,我也确实发现了他的实用性。我通过自己的 obs2hexo 翻译器确实完成了 Markdown 文档的转换,变成了可以直接放到 Hexo 目录下使用的 Markdown 格式,并且对图片文件进行了管理。但是这个过程也是比较僵硬的,我需要:

  1. 运行 obs2hexo
  2. 复制文件到对应目录
  3. 执行 hexo ghexo d 进行发布

如此反复,也确实不爽。所以最终,在 Shell Command 的帮助之下,完成这部分脚本命令的自动执行。下面我用自己的自动发布作为例子介绍使用方法。

首先是插件安装,在社区插件中找到 Shell commands:

1

之后点击安装即可,此时在设置栏中,会出现 Shell commands 的设置界面。

1

新建 shell commands 即可将自己的脚本写入,这里我的脚本内容如下:

1
export PATH="$HOME/.pyenv/shims:$HOME/.yarn/bin:$HOME/.nvm/versions/node/v16.16.0/bin:$PATH";
obs2hexo -c {{_category}} -p {{file_path:absolute}};
mkdir -p $HOME/Documents/Projects/Blog/source/_posts/{{date:YYYY}};
cd $HOME/Documents/Projects/Blog/source/_posts/{{date:YYYY}};
mv /tmp/o2houtput/* .;
hexo g;
hexo d;

逐行进行说明:

  1. 设置环境变量,我的 python 环境是通过 pyenv 进行管理的,而 hexo、picgo 则分别通过 nvm 和 yarn 进行安装管理,所以将他们的对应路径写入环境变量中。(使用其他环境管理工具也只需要将对应的目录写入 PATH 中即可)
  2. 实现翻译,{{_category}} 是我手工添加的环境变量,在 Preaction 中进行设置,稍候会展示。{{file_path:absolute}} 则是 Shell Commands 插件提供的宏,使用时会替换成当前文件的绝对路径。
  3. 检查 Hexo 存放 Markdown 文件的路径,我多设置了一个按年份分类的管理。
  4. 前往对应目录
  5. 完成文件复制
  6. Hexo 完成静态网页生成
  7. Hexo 完成部署

上面的 Preaction 是我为了方便「发布时进行博客分类」,单独设置了一个页面:

1

这样就会在我使用这个 Shell Command 脚本时,弹出窗口让我填写类别了,效果如下:

1

完成配置之后,发布就可以只在 Obsidian 中完成,而不需要切到终端输入命令。

1

基于类似的方案,我还额外进行了 Medium 平台的自动发布和 WordPress 的自动发布。不过这些发布其实摧毁了 Obsidian 的核心双链功能。wikilink 引用了其他笔记的时候,这里的发布方案并不能进行智能的处理。和官方的 Obsidian Publish 比起来还是有一些距离的。不过毕竟是小作坊的小打小闹,也是抛砖引玉为大伙提供一个自动发布的思路。也欢迎大家对我的方案进行改进建议和批评。

懒人写书「mdbook + Gitlab CI」

第二个故事是我独立在 Obsidian 之外进行的一系列创作。项目的出发点是来自 Quivr - Github。少数派的「玉树芝兰」老师前两天也发了一篇非常不错的教程:如何用 ChatGPT 和你的卡片笔记对话?开源应用 Quivr 尝试,对我非常有启发。

项目部署之后,可以通过整理好的笔记,完成自己知识系统的描述,借助 LLM 辅助自己对已经掌握的知识进行索引和查询。另一个出发点来自 Knowledge - Github,作者将自己的已有知识进行了梳理和总结,保留了自己的学习历程。因此,在我自己博士生活正式开始之前,我也希望通过这个过程整理自己已经掌握的知识。为了部署的便捷性和运维的简洁,我最终选择了 mdbook - Github 作为自己的写作框架。

mdbook 开始的基础框架

mdbook 是一个基于 Rust 开发的静态网页生成工具,可以用于生成一个轻量的静态网站,默认提供了检索、pdf 生成、分享等功能。安装基于 cargo 进行(这里不赘述 cargo 的安装,参考相关链接 Rust installation3

cargo install mdbook

更多安装信息参考: mdbook - installation

常用指令如下:

# Create a new mdbook project
mdbook init
# Serve on local and open a browser
mdbook serve --open
# Generate website
mdbook build

mdbook 拓展

在基础的 mdbook 框架上,还有一些提升拓展,也可以通过 cargo 进行安装:

都可以使用 cargo install 直接进行安装。

Gitlab CI/CD 自动管理

这个项目也是我个人第一次尝试使用 Gitlab CI/CD 进行项目管理。自己的 Github 账号由于没有续上教育认证,暂时没有 Pro 的额度,所以将一部分业务转移到了 Gitlab 上。先谈谈 CI/CD,持续集成交付,包含了:

  • Continuous Integration (CI)
  • Continuous Delivery (CD)
  • Continuous Deployment (CD)

几个层面的含意。具体的介绍可以参考 Gitlab CI/CD 上详细的介绍。

概括的说,持续集成(CI),通过将开发人员的代码频繁地集成到共享存储库中,然后自动构建和测试该代码,以确保新功能和修改不会导致主干代码的破坏。

而持续交付(CD)是在持续集成的基础上构建的方法,它旨在自动化软件交付过程。通过持续交付,开发团队能够自动构建、测试和部署应用程序,使新功能和更改能够快速、可靠地交付给最终用户。

CI/CD 通过自动化和自动化工具的使用,可以加快开发团队的软件交付速度,减少错误并增加整体质量。

上述的概念比较生硬,在这里我们写作的场景下,可以理解成:

作者只需关注作品,上传到 Gitlab 之后,部署和发布可以由平台自动完成

回归内容,具体的操作可以参考如下流程。

首先在 Gitlab 中创建一个新的 Public 项目用于放置内容:

1

完成创建之后,我们需要上传内容。在使用 git 上传 mdbook 项目之前,还需要写入 gitlab CI 的配置文件:

pages:
  stage: deploy
  image: "rust:latest"
  variables:
    CARGO_HOME: "$CI_PROJECT_DIR/cargo"
  before_script:
    - export PATH="$PATH:$CARGO_HOME/bin"
    - mdbook --version || cargo install mdbook
    - mdbook-toc --version || cargo install mdbook-toc
    - mdbook-admonish --version || cargo install mdbook-admonish
    - mdbook-mermaid --version || cargo install mdbook-mermaid
  script:
  - mdbook build
  artifacts:
    paths:
    - public
  only:
  - main
  cache:
    paths:
    - $CARGO_HOME/bin

这里提供我都版本供大家参考,思路是在一个 rust 容器中,检查我们需要的包即可。

为了使用上这些额外的插件,mdbook 的配置文件也要进行一些微调,book.toml 文件内容的模板如下:

[book]
authors = ["John Doe"]
language = "en"
multilingual = false
src = "src"
title = "xxxx"

[build]
build-dir = "public"
create-missing = true

[preprocessor.toc]
command = "mdbook-toc"
renderer = ["html"]
marker = "[TOC]"

[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`

[preprocessor.mermaid]
command = "mdbook-mermaid"

[output.html]
mathjax-support = true
additional-css = ["./theme/catppuccin.css", "./theme/catppuccin-highlight.css", "./theme/mdbook-admonish.css"] # additional theme settings, this line is optional
git-repository-url = "https://gitlab.com/Chivier/chivipedia/"
git-repository-icon = "fa-gitlab"
additional-js = ["mermaid.min.js", "mermaid-init.js"

[output.html.playground]
editable = false
copyable = true
copy-js = true
line-numbers = false
runnable = false

[output.html.fold]
enable = true
level = 0

之后和一般的 git 使用方法一样,上传自己已经写好的内容。接着可以看到 Gitlab pipeline 中有对应的任务:

1

由于这里基于容器,并且安装相关包使用的是 cargo,效率有一定程度的影响,可能会有 5~20 分钟不等的延迟才能生效。所以为了验证效果,建议在本地配置 mdbook 并使用 mdbook serve 命令进行验证。

可选操作:之后还可以在 Settings > Pages 里面更换网页的域名。

1

写作是一个长期的过程,也是我的一个日常爱好。所谓「千里之行,始于足下」,我并非一个成熟的作者,但是我也确实有一些微小的表达欲和分享欲,每天也随性写几行自己的感受和心得。过去一年,也确实有了十几万字的笔记和作品积累,也不希望敝帚自珍,所以找了个时间,简化了自己分享发布的流程。希望上面的两种自动化写作方案能帮到需要的朋友,让发布和分享不会成为大家的负担。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK