一个简单却可配置的静态博客生成器。 很早之前(三年前)就想写一个静态博客生成器,但苦于一直没有时间,最近把之前写的重构了一下,让其可以支持更多的定制化配置。
至于为什么要重新写一个:
大概是因为想要把之前未完成的轮子补充完整
我目前使用的博客系统使用的是 pelican,一个基于 Python 的静态博客生成器,目前里面包括了很多我自己写的插件,比如
Emacs org mode
的支持、文章加密、模版定制等,因为需要大量遍历所有文章,导致生成速度越来越慢,还有一点就是因为使用的是 Python,每次本地预览时都需要切换到虚拟环境我习惯使用 Emacs + Org,除了前期的几篇文章,后面都是使用 org mode 书写,之前是因为 Python 没有一个好用的 org mode 解析库,所以专门写了一个
org-python
用来解析 org mode;最近我也是完善了另一个我很早之前就写的org-golang
解析库(轮子+1),准备趁次机会利用一下这个库至于为什么不用最近几年流行的 Hugo, 因为我想要保持和我使用 Pelican 时一样的功能,比如文章加密,而 hugo 并不支持插件,想要自定义插件必须复制大段大段的启动函数, 甚至需要修改源代码。同样我想要定制一个相同的模版,而对于使用过其它模版系统如Django,jinja2, 再来使用 Go内置模版 的人来说, hugo 的内置模版除了难用就是难用,这也是我此次选用 pongo2 的原因
我的设想是提供插件的接口,并提供一个足够简单的启动函数, 在有用户需要自定义插件时只需要自己创建一个包, 使用三两行代码就能注册自定义插件并重新编译自己的snow
快速开始
└──╼ ./snow --help NAME: snow - snow is a static site generator. USAGE: snow [global options] command [command options] [arguments...] VERSION: 0.1.0 COMMANDS: init init a new site build build and output server server local files help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --conf FILE, -c FILE load configuration from FILE (default: "config.yaml") --help, -h show help (default: false) --version, -v print the version (default: false)
创建新的站点
──╼ ./snow init Welcome to snow 0.1.0. > Where do you want to create your new web site? [.] mysnow > What will be the title of this web site? [snow] > Who will be the author of this web site? The input is required > Who will be the author of this web site? honmaple > What is your URL prefix? (no trailing slash) [http://127.0.0.1:8000] > Do you want to create first page? [Y/n]
编译和预览
└──╼ cd mysnow └──╼ ../snow server -D DEBU Copying @theme/static/css/main.css to output/static/css/main.css INFO Done: Static Processed 1 static files in 588.705µs DEBU Writing output/categories/index.html DEBU Writing output/authors/index.html DEBU Writing output/tags/index.html DEBU Writing output/posts/index.html DEBU Writing output/authors/snow/index.html DEBU Writing output/tags/snow/index.html DEBU Writing output/categories/linux/index.html DEBU Writing output/tags/linux/index.html DEBU Writing output/tags/emacs/index.html DEBU Writing output/categories/linux/emacs/index.html INFO Done: Page Processed 1 normal pages, 0 hidden pages, 0 section pages in 10.087804ms INFO Done: Section Processed 1 posts in 10.1831ms INFO Done: Taxonomy Processed 1 authors, 3 tags, 1 categories in 10.18788ms
内容
Section
content/ ├── drafts // <- http://127.0.0.1:8000/drafts/index.html │ └── draft1.org // <- http://127.0.0.1:8000/drafts/index.html ├── pages // no url, because sections.pages.path is "" │ └── 404.org // <- http://127.0.0.1:8000/404.html └── posts // <- http://127.0.0.1:8000/posts/index.html ├── post1.org // <- http://127.0.0.1:8000/posts/2022/02/post1.html └── subposts // <- http://127.0.0.1:8000/posts/subposts/index.html └── post2.org // <- http://127.0.0.1:8000/posts/2023/02/post2.html
配置
sections: _default: # 页面中文章的默认排序, 多字段使用逗号分隔 orderby: "date desc" # 文章筛选,主要用于分页前 filter: "" # 页面中文章默认分页, path必须使用{number}变量, 0表示不分页 paginate: 10 # 分页路径 paginate_path: "{name}{number}{extension}" # 分页前筛选pages paginate_filter: "" # 生成路径, 为空表示禁止生成相关页面 path: "{section}/index.html" # 使用的模版 template: "section.html" # 当前section下所有文章生成路径 page_path: "{section}/{slug}/index.html" # 文章使用的模版 page_template: "post.html" posts: page_path: "posts/{date:%Y}/{date:%m}/{slug}.html" pages: path: "" pages/about: # 自定义pages/about下的文章生成路径,同时继承pages.path不会生成所有页面 page_path: "{slug}/index.html"
filter 格式(下同):
'emacs' in tags and not draft or weight > 1
其中 tags, draft 等都是page元数据
路径变量(sections.xxx.path)
变量 | 描述 |
---|---|
{section} | section名称 |
{section:slug} | section slug, 中国 -> zhong-guo |
模版变量(sections.xxx.template)
变量 | 描述 |
---|---|
section | |
section.Title | section标题 |
section.Path | section相对链接 |
section.Permalink | section绝对链接 |
section.Content | section内容 |
section.Pages | 当前section下的文章列表 |
section.Children | 子section |
section.Parent | 父section |
Page
配置
# 文章目录所在, 其中该目录下应该包括一系列子目录,这些子目录的名称对应为 *文章的类型*, 比如 *content/drafts/* 目录下的文章类型为 *drafts*, 当然也可以直接在文章文件头添加 =type: drafts= content_dir: "content"
路径变量(sections.xxx.page_path)
变量 | 描述 |
---|---|
{date:%Y} | 创建文章的年份 |
{date:%m} | 创建文章的月份 |
{date:%d} | 创建文章的日期 |
{date:%H} | 创建文章的小时 |
{lang} | 文章语言 |
{slug} | 文章标题或自定义slug |
{filename} | 文件名称(不带后缀名) |
模版变量(sections.xxx.page_template)
变量 | 描述 |
---|---|
page | |
page.Title | 页面标题 |
page.Lang | 页面语言 |
page.Date | 页面创建时间 |
page.Modified | 页面修改时间 |
page.Aliases | 页面其它链接 |
page.Path | 页面相对链接 |
page.Permalink | 页面绝对链接 |
page.Summary | 页面简介 |
page.Content | 页面内容 |
page.Meta.xxx | 自定义的元数据 |
page.Prev | 上一篇 |
page.Next | 下一篇 |
page.HasPrev() | 是否有上一篇 |
page.HasNext() | 是否有下一篇 |
page.PrevInType | 同一类型上一篇 |
page.NextInType | 同一类型下一篇 |
page.HasPrevInType() | 是否有同一类型上一篇 |
page.HasNextInType() | 是否有同一类型下一篇 |
Draft
默认草稿标志为 draft: true
, 也可以将所有草稿放入同一个 drafts 目录, 然后就可以在构建时增加筛选条件
-
排除草稿
snow build -F 'not draft'
或者
snow build --filter 'type != "drafts"'
-
包括草稿
snow build -F 'not draft or draft = true'
默认筛选条件可以写入配置
build_filter
Taxonomy
配置
taxonomies: _default: path: "{taxonomy}/index.html" # terms排序, 可选name,count orderby: "" template: "{taxonomy}/list.html" term_path: "{taxonomy}/{term:slug}/index.html" term_template: "{taxonomy}/single.html" # 文章列表筛选 term_filter: "" # 文章列表排序 term_orderby: "date desc" # 文章列表分页 term_paginate: 0 term_paginate_path: "" term_paginate_filter: "" categories: authors: tags:
路径变量
-
taxonomies.xxx.path
变量 描述 {taxonomy} 分类系统名称 -
taxonomies.xxx.term_path
变量 描述 {taxonomy} 分类系统名称 {term} 分类具体名称 {term:slug} 分类slug
模版变量
-
taxonomies.xxx.template
变量 描述 taxonomy taxonomy.Name 分类系统名称, 如:categories,tags,authors taxonomy.Terms -
taxonomies.xxx.term_template
变量 描述 term term.Name 分类名称 term.Path 相对链接 term.Permalink 绝对链接 term.List 文章列表 term.Children 子分类
Archive
taxonomies: date:2006/01: path: "archives/index.html" template: "archives.html" term_path: "archives/{term}/index.html" term_template: "period_archives.html"
归档页面类似分类系统,其它 date:2006/01 表示按年月归档, 并生成链接 /archives/2022/10/index.html
Pagination
路径变量
变量 | 描述 |
---|---|
{name} | 路径名称 |
{extension} | 路径扩展 |
{number} | 页码, 第一页为空 |
{number:one} | 页码, 第一页为"1" |
-
示例一:
path: "section/index.html" paginate_path: "{name}{number}{extension}"
-
第一页:
section/index.html
-
第二页:
section/index2.html
-
第三页:
section/index3.html
-
-
示例二:
path: "section/index.html" paginate_path: "page/{number:one}{extension}"
-
第一页:
section/page/1.html
-
第二页:
section/page/2.html
-
第三页:
section/page/3.html
-
模版变量
变量 | 描述 |
---|---|
paginator | |
paginator.URL | 分页链接 |
paginator.PageNum | 当前页 |
paginator.Total | 总页数 |
paginator.HasPrev() | 是否有上一页 |
paginator.Prev | 上一页 |
paginator.Prev.URL | 上一页链接 |
paginator.HasNext() | 是否有下一页 |
paginator.Next | 下一页 |
paginator.Next.URL | 下一页链接 |
paginator.All | 所有页 |
paginator.List | 当前页文章列表 |
Static
静态文件配置:
# 静态文件目录, 该目录区分主题的静态文件static static_dirs: - "static/" # 静态文件扩展,不配置将会使用静态文件目录下的所有文件 static_exts: - ".js" - ".css" # 静态文件路径,用于指定静态文件或静态目录的保存目录, 当有多条路径时,长度优先. static_paths: static/CNAME: "/" static/css/main.css: "static/css/" # 以@theme开头代表主题中的静态文件,即{theme.name}/static "@theme/static": "static/"
Formats
可以生成 rss,atom 或者其它任意格式(需要自定义模版)
配置
# 设置rss格式的默认值 formats.rss: template: "_internal/rss.xml" formats.atom: template: "_internal/atom.xml" sections: _default: # rss生成路径, 模版将会使用默认模版 formats.rss.path: "{section:slug}/index.xml" # 为空时禁止生成 formats.atom.path: "" taxonomies: tags: formats.atom: path: "tags/{term:slug}/index.xml" # 自定义模版 template: "custom.atom.xml"
模版变量
变量 | 描述 |
---|---|
section | 仅生成section 有效 |
term | 仅生成taxonomy term 有效 |
pages | 文章列表 |
theme
主题目录结构
其中 templates 和 static 名称不可修改
simple/ ├── templates │ ├── post.html │ ├── index.html │ ├── archives.html ├── static │ ├── main.css
配置
theme: # 主题名称, 未设置将使用默认主题 name: "test-theme" # 主题模版覆盖, 增加同名的文件到 *override* 配置的目录, snow将会优先使用该文件 override: "layouts"
TODO默认配置
hook
hooks: - "i18n" - "assets" - "encrypt" - "shortcode"
i18n
-
模版
{% i18n "tags" %} {% T "tags %d" 12 %} {{ i18n("authors") }} {{ T("authors") }} {{ _("authors %f", 3.14) }}
甚至可以直接使用变量 {{ _(term.Name) }}
-
翻译文件 默认会加载主题下 i18n 目录下的文件
i18n ├── en.yaml └── zh.yaml
文件内容
--- - id: "authors" tr: "作者" - id: "tags" tr: "标签"
也可以自定义文件位置或翻译内容覆盖主题原有的翻译
languages.en: translations: "i18n/en.yaml" languages.zh: translations: - id: "authors" tr: "作者"
encrypt
内容加密, 需要一个密码
{{ page.Content | encrypt:"123456" }}
shortcode
用于快速插入已有模版, 示例:
<shortcode _name="encrypt" password="1234567"> hello *markdown* </shortcode> <shortcode _name="gist" author="spf13" id="7896402" />
可以自定义 shortcode 到主题的 templates/shortcodes
目录下, 目前内置 gist, encrypt
assets
静态文件处理
params: assets: css: files: - "@theme/static/scss/main.scss" - "@theme/static/scss/entry.scss" filters: - libscss: path: ["@theme/static/scss/"] - cssmin: output: "static/lib.min.css"
模版文件
{% assets files="css/style.scss" filters="libsass,cssmin" output="css/style.min.css" %} <link rel="stylesheet" href="{{ config.site.url }}/{{ asset_url }}"> {% endassets %} {% assets css %} <link rel="stylesheet" href="{{ config.site.url }}/{{ asset_url }}"> {% endassets %}
本地测试和正式发布
snow 提供了 mode 配置用于区分本地测试和正式发布
site: url: "http://127.0.0.1:8000" output_dir: "output" mode.publish: site: url: "https://example.com" output_dir: "xxx" mode.develop: include: "develop.yaml"
只要在构建时使用 snow build --mode publish
即可覆盖本地默认配置