Snow静态博客生成器


一个简单却可配置的静态博客生成器。 很早之前(三年前)就想写一个静态博客生成器,但苦于一直没有时间,最近把之前写的重构了一下,让其可以支持更多的定制化配置。

至于为什么要重新写一个:

  1. 大概是因为想要把之前未完成的轮子补充完整

  2. 我目前使用的博客系统使用的是 pelican,一个基于 Python 的静态博客生成器,目前里面包括了很多我自己写的插件,比如 Emacs org mode 的支持、文章加密、模版定制等,因为需要大量遍历所有文章,导致生成速度越来越慢,还有一点就是因为使用的是 Python,每次本地预览时都需要切换到虚拟环境

  3. 我习惯使用 Emacs + Org,除了前期的几篇文章,后面都是使用 org mode 书写,之前是因为 Python 没有一个好用的 org mode 解析库,所以专门写了一个 org-python 用来解析 org mode;最近我也是完善了另一个我很早之前就写的 org-golang 解析库(轮子+1),准备趁次机会利用一下这个库

  4. 至于为什么不用最近几年流行的 Hugo, 因为我想要保持和我使用 Pelican 时一样的功能,比如文章加密,而 hugo 并不支持插件,想要自定义插件必须复制大段大段的启动函数, 甚至需要修改源代码。同样我想要定制一个相同的模版,而对于使用过其它模版系统如Django,jinja2, 再来使用 Go内置模版 的人来说, hugo 的内置模版除了难用就是难用,这也是我此次选用 pongo2 的原因

  5. 我的设想是提供插件的接口,并提供一个足够简单的启动函数, 在有用户需要自定义插件时只需要自己创建一个包, 使用三两行代码就能注册自定义插件并重新编译自己的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

可以生成 rssatom 或者其它任意格式(需要自定义模版)

配置

# 设置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

主题目录结构

其中 templatesstatic 名称不可修改

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 即可覆盖本地默认配置

作者: honmaple
链接: https://honmaple.me/articles/2022/10/Snow静态博客生成器.html
版权: 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat