Snow静态博客生成器


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 exmaple.com
Welcome to snow 0.1.0.
> Where do you want to create your new web site? [exmaple.com]
> What will be the title of this web site? Custom Title
> Who will be the author of this web site? snow
> What is your URL prefix? (see above example; no trailing slash) https://example.com

创建新的文章

└──╼ cd example.com
└──╼ mkdir -p content/posts
└──╼ cat > content/posts/first-post.org <<EOF
> #+TITLE: Post1
> #+AUTHOR: honmaple
> #+DATE: 2021-10-06 14:57:51
> #+CATEGORY: Linux
> #+PROPERTY: MODIFIED 2022-10-13 00:04:38
> #+PROPERTY: TAGS linux
>
>
> post1
> EOF

编译和预览

──╼ ./snow server -D
──╼ ./snow server -D
DEBU Writing output/posts/2021/10/Post1.html
DEBU Writing output/categories/index.html
DEBU Writing output/categories/linux/index.html
DEBU Writing output/index.html
DEBU Writing output/tags/index.html
DEBU Writing output/tags/linux/index.html
DEBU Writing output/authors/index.html
DEBU Writing output/archives/index.html
INFO Done: Processed 1 pages in 133.999021ms
INFO Listen 127.0.0.1:8000 ...

配置说明

文章列表

文章列表配置:

content_dir: "content"
page_dirs:
  - "drafts"
  - "posts"
page_meta:
  index:
    list.lookup:
      - "index.html"
    list.output: "index{number}.html"
page_filter: "-drafts"
page_orderby: "date desc,title"
  • content_dir: 文章目录所在, 其中该目录下应该包括一系列子目录,这些子目录的名称对应为 文章的类型, 比如 *content/drafts/* 目录下的文章类型为 drafts, 当然也可以直接在文章文件头添加 type: drafts

  • page_dirs: 所有类型的文章列表,当 page_dirs 为空时,snow将会搜索 content 目录下的所有子目录

  • page_meta: 文章页面元数据,所有页面的生成都和该配置有关,具体见下文

  • page_filter: 全局的文章列表筛选,格式见下文

  • page_orderby: 全局的文章列表排序

  • page_paginate: 全局的文章列表分页大小

静态文件

静态文件配置:

static_exts:
  - ".js"
  - ".css"
static_dirs:
  - "static/"
  - "static/css/"
static_meta:
  static/CNAME: "/"
  static/css/main.css: "static/css/"
  theme/static: "static/"
output_dir: "output"
  • static_dirs: 静态文件目录, 该目录区分主题的静态文件static

  • static_exts: 静态文件扩展,不配置将会使用静态文件目录下的所有文件

  • static_meta: 静态文件元数据,用于指定静态文件或静态目录的保存目录, 当有多条路径时,长度优先

    • */*: 保存到 output

    • theme/static: 指定主题的静态文件保存目录

主题

主题配置:

theme:
  path: "themes/test-theme"
  engine: "pongo2"
  override: "layouts"
  • path: 主题目录, 内置主题: simple

  • engine: 主题模版引擎, 当前只支持 pongo2

  • override: 主题模版覆盖目录, 比如一个存在的主题可以增加同名的文件到 override 配置的目录, snow将会优先使用该文件

  • 主题目录结构: 其中 templatesstatic 名称不可修改

    simple/
    ├── templates
    │   ├── post.html
    │   ├── index.html
    │   ├── archives.html
    ├── static
    │   ├── main.css
    

动态页

在snow中所有的页面都是根据配置和主题模板进行生成, 其中动态页面分为三类:

详情页

详情页的配置为 page_meta.xxx, 其中 xxx 必须是所有页面类型中的一种, 比如 page_meta.posts, page_meta.pages 等, 示例:

page_meta:
  posts:
    lookup:
      - "post.html"
    output: "posts/{date:%Y}/{date:%m}/{slug}.html"
  pages:
    lookup:
      - "page.html"
      - "post.html"
    output: "pages/{slug}.html"
  drafts:
    lookup:
      - "post.html"
    output: "drafts/{date:%Y}/{date:%m}/{slug}.html"
  • lookup: 主题中查找的模版文件

  • output: 写入的文件路径, slug 为页面标题

  • output变量:

    变量 描述
    {date:%Y} 创建文章的年份
    {date:%m} 创建文章的月份
    {date:%d} 创建文章的日期
    {date:%H} 创建文章的小时
    {slug} 文章标题或自定义slug
    {filename} 文件名称(不带后缀名)
  • 模版变量:

    变量 描述
    page
    page.Title 页面标题
    page.URL 页面链接
    page.Categories 页面分类
    page.Tags 页面标签
    page.Authors 页面作者
    page.Summary 页面简介
    page.Content 页面内容
    page.Meta.xxx 自定义的元数据
    page.Prev 上一篇
    page.Next 下一篇
    page.HasPrev() 是否有上一篇
    page.HasNext() 是否有下一篇
    page.PrevInType 同一类型上一篇
    page.NextInType 同一类型下一篇
    page.HasPrevInType() 是否有同一类型上一篇
    page.HasNextInType() 是否有同一类型下一篇
列表页

列表页的配置为 page_meta.xxx.list, 其中 xxx 可以是除页面类型以外的任意名称, 比如 page_meta.tags.list, page_meta.mycustom.list 等, 示例:

page_meta:
  index:
    list.lookup:
      - "index.html"
    list.output: "index{number}.html"
    list.filter: "-pages"
    list.paginate: 5
  tags:
    list.lookup:
      - "tag.html"
    list.output: "tags/{slug}/index{number}.html"
    list.filter: "-pages"
    list.groupby: "tag"
    list.paginate: 10
  • output: 列表页和详情页不同,列表页可能会根据不同的分页大小写入多个文件, 其中分页由 output 中的 {number} 决定, 比如上述的 tags.list.output, 假如有36篇 taglinux 的页面, 写入的文件为:

    [output_dir]/tags/linux/index.html
    [output_dir]/tags/linux/index1.html
    [output_dir]/tags/linux/index2.html
    [output_dir]/tags/linux/index3.html
    

    {number} 在第一页时会自动配置成空字符串,如果需要禁止该动作,可以使用 {number:one} 代替

  • groupby: 如果想要配置按标签或者分类进行分组, 并写入不同的分组文件,可以配置 groupby 字段,目前可选: type、tag、category、author 和 date:xxx, 其中 date:xxx 中的xxx为Go格式化时间格式, 比如 date:2006 表示按年分组

  • filter: 列表显示需要提前筛选不想展示的页面,可以配置 filter 字段, 格式:

    • 字符串格式: type1 表示只展示类型为type1的页面, -type2 表示排除类型为type1的页面, 多个类型使用英文逗号分隔

    • 字典格式:

      page_meta:
        tags:
          list.filter:
            type: "type1,-type2"
            tag: "tag1,tag2,-tag3"
            author: "author1"
            category: "category"
      

      其中任意字段都可以使用前缀 - 排除相应的页面

  • paginate: 分页大小, 0代表不分页,默认的分页在 page_pagniate

  • 模版变量:

    变量 描述
    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 当前页文章列表
    slug 分组key
归档页

归档页表示所有分组页面的集合, 比如所有的标签,所有的作者或类型

page_meta:
  tags:
    lookup:
      - "tags.html"
    output: "tags/index.html"
  archives:
    lookup:
      - "archives.html"
    output: "archives/index.html"
  • 模版变量:

    变量 描述
    labels 分组列表
    pages 文章列表

本地测试和正式发布

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