静态博客生成器
──╼ ./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
└──╼ go install https://github.com/honmaple/snow
└──╼ git clone https://github.com/honmaple/snow --depth=1 └──╼ cd snow └──╼ go mod tidy └──╼ go build .
└──╼ ./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: --config 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 └──╼ ./snow init myblog
如果不指定 myblog 目录,默认会在当前目录下生成一个 config.yaml 文件和一个 content 目录
该命令会构建站点内容内写入到 {output_dir} 目录, 如果该目录已经有文件存在,除非制定 -C
参数,否则不会自动清理
- 清理输出目录
└──╼ ./snow build --clean └──╼ ./snow build -C
- 显示输出详情
└──╼ ./snow build --debug └──╼ ./snow build -D
- 指定输出目录
└──╼ ./snow build --output {output_dir} └──╼ ./snow build -o {output_dir}
- 指定mode
└──╼ ./snow build --mode {mode} └──╼ ./snow build -m {mode}
- 筛选页面
└──╼ ./snow build --filter {build_filter} └──╼ ./snow build -F {build_filter}
- 显示所有hooks
└──╼ ./snow build --hooks
build 支持的命令 server也同样支持, 除此之外,还有
- 指定监听地址
└──╼ ./snow server --listen 127.0.0.1:8088 └──╼ ./snow server -l 127.0.0.1:8088
默认监听地址是
site.url
- 监听文件修改并重新构建
└──╼ ./snow server --autoload └──╼ ./snow server -r
. ├── config.yaml ├── content │ └── posts │ └── first-page.md ├── static ├── layouts └── themes │ └── snow │ └── static │ └── template
- config.yaml: 使用的配置文件
- content:
包括所有的页面内容, 比如
.md
,.org
等,如果一个子目录包括index.{md,org}
文件,那么这个目录将会成为一个页面,否则每一个子目录都是一个 section, 同样的,子目录下_index.{md,org}
文件也是该 section 的配置文件 - static:
static_dirs
指定的静态文件或目录,名称可修改 - layouts: 主题模版覆盖目录
theme.override
指定的主题覆盖文件,比如有一个主题模版{theme}/templates/post.html
, 当指定了override
目录后就可以在该目录创建一个同样名称为post.html
的文件进行覆盖 - themes:
主题目录, 该目录下包括的子目录就是主题名称,可以在
theme.name
里指定
# 站点配置信息
site:
url: "http://127.0.0.1:8000"
title: "snow"
subtitle: "Snow is a static generator."
language: "zh"
author: "honmaple"
# 发布时使用的配置
mode.publish:
site:
url: "https://honmaple.me"
output_dir: "output"
content_dir: "content"
build_filter: "not draft"
theme:
name: "snow"
# 按照主题需要进行配置
params.extra:
menus:
- name: "关于"
url: "/pages/about.html"
content/ ├── pages // no url, because sections.pages.path is "" │ └── about // <- http://127.0.0.1:8000/pages/about.html │ └── index.org // no url │ └── contact.org // <- http://127.0.0.1:8000/pages/contact.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"
# 自定义某个section下的页面筛选
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"
formats.atom:
path: "{section:slug}/atom.xml"
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元数据
变量 | 描述 |
---|---|
{section} | section名称 |
{section:slug} | section slug, 中国 -> zhong-guo |
变量 | 描述 |
---|---|
section | |
section.Title | section标题 |
section.Path | section相对链接 |
section.Permalink | section绝对链接 |
section.Content | section内容 |
section.Pages | 当前section下的页面列表 |
section.Children | 子section |
section.Parent | 父section |
- markdown
--- title: "title" categories: - Snow/Templates tags: - linux - snow ---
- orgmode
#+TITLE: title #+DATE: 2022-02-26 17:14:46 #+CATEGORIES: Snow/Templates #+PROPERTY: TAGS linux,snow #+PROPERTY: MODIFIED 2023-02-26 14:35:37
- html
<head> <title>Project</title> <meta name="categories" content="Snow/Templates" /> <meta name="tags" content="linux,snow" /> <meta name="date" content="2015-12-22" /> </head>
# 页面目录所在, 其中该目录下应该包括一系列子目录,这些子目录的名称对应为 *页面的类型*, 比如 *content/drafts/* 目录下的 页面类型为 *drafts*, 当然也可以直接在 页面文件头添加 =type: drafts=
content_dir: "content"
变量 | 描述 |
---|---|
{date:%Y} | 创建页面的年份 |
{date:%m} | 创建页面的月份 |
{date:%d} | 创建页面的日期 |
{date:%H} | 创建页面的小时 |
{lang} | 页面语言 |
{slug} | 页面标题或自定义slug |
{filename} | 文件名称(不带后缀名) |
变量 | 描述 |
---|---|
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() | 是否有同一类型下一篇 |
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 子分类
snow 中的分类系统是基于归档实现的,该功能类似 SQL 中的 group by
, 所以如果要实现归档页可以有两种方式:
- 添加
taxonomies.{key}
,{key}
可以是页面元数据里的任意字段, 比如categories
,tags
, 如果需要按照时间归档, 格式为date:2006/01
, 其中2006/01
为Go时间格式,表示按年月归档, 并生成链接 /archives/2022/10/index.htmltaxonomies: date:2006/01: path: "archives/index.html" template: "archives.html" term_path: "archives/{term}/index.html" term_template: "period_archives.html"
- 在
{content_dir}
下添加一个archives.md
的文件path: archives.html template: archives.html section: true
然后在模板
{templates}/archives.html
使用pages.GroupBy({key})
{%- for subterm in pages.GroupBy("date:2006-01").OrderBy("name desc") %} {%- set date = subterm.Name | split:"-" %} {%- set year = date[0] %} {%- set month = date[1] %} ... {%- endfor %}
变量 | 描述 |
---|---|
{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 | 当前分页下的页面列表 |
使用者可以自定义草稿标志,但推荐使用两种形式:
- 添加元数据
draft: true
, 构建时增加筛选条件- 草稿
snow build --filter 'draft = true'
- 非草稿
snow build -F 'not draft'
- 草稿
- 创建一个单独的
drafts
目录存放草稿- 草稿
snow build -F 'type = "drafts"'
- 非草稿
snow build -F 'type != "drafts"'
- 草稿
注: 默认筛选条件可以写入配置 build_filter
可以生成 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 | 页面列表 |
静态文件分 主题静态文件 和 配置指定的静态文件
├── themes │ └── snow │ └── static │ └── main.css
主题目录下的所有文件默认会复制到 output 目录, 除非设置 statics.@theme/static.path
为空
该文件需要在配置指定
statics:
# 根目录下static目录下的文件将会拷贝到{output_dir}/static
static:
# 拷贝的路径, 为空时表示不写入, 如果以"/"结尾, 表示拷贝到该目录
# static -> {output_dir}/static
# static/ -> {output_dir}/static/static
path: "/"
# 指定扩展,不配置将会使用目录下的所有文件
exts:
- ".js"
- ".css"
# 如果指定的静态文件是一个目录,可以设置忽略文件, 比如忽略static目录下的images子目录
ignore_files:
- "^images/"
# 以@theme/开头表示主题目录, 以@theme/_internal/开头表示内置的主题目录
@theme/static:
path: "static"
@theme/_internal/static:
path: "static"
# 同样可以指定任意静态文件或目录
content/pages/css:
path: "static/css"
需要配置 languages
languages.en:
translations: "i18n/en.yaml"
taxonomies:
special_tags:
path: "{taxonomy}/index.html"
languages.fr:
translations: "i18n/fr.yaml"
ignores:
# 忽略所有的静态文件,与主站点共用一个静态目录
- statics
页面格式:
{title}.en.md
{title}.fr.md
或者可以在文件头指定 lang: en
https://github.com/flosch/pongo2
其中 templates 和 static 名称不可修改
simple/ ├── theme.yaml ├── templates │ ├── post.html │ ├── index.html │ ├── archives.html ├── static │ ├── main.css
theme:
# 主题名称, 未设置将使用默认主题
name: "test-theme"
# 默认的主题配置,该配置会自动合并,除非设置为空
config: "theme.yaml"
# 主题模版覆盖, 增加同名的文件到 *override* 配置的目录, snow将会优先使用该文件
override: "layouts"
registered_hooks:
- "i18n"
- "assets"
- "encrypt"
- "shortcode"
- 模版
{% 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: "作者"
内容加密, 需要一个密码
{{ page.Content | encrypt:"123456" }}
用于快速插入已有模版, 示例:
<shortcode _name="encrypt" password="1234567"> hello *markdown* </shortcode> <shortcode _name="gist" author="spf13" id="7896402" />
可以自定义 shortcode 到主题的 templates/shortcodes
目录下, 目前内置 gist, encrypt
- 如果使用的外部
js,css
文件可以加载内置的shortcode.js
实现全局只加载一次,具体可以参考 shortcodes/encrypt.html - 如果想要在单个页面只加载一次,请使用
_counter == 0
静态文件处理
hooks.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 %}
sofile 允许使用Go的 Plugin
系统支持自定义插件
- 创建一个
sofile.go
的文件package main import ( "fmt" "github.com/honmaple/snow/builder/hook" "github.com/honmaple/snow/builder/page" "github.com/honmaple/snow/builder/theme" "github.com/honmaple/snow/config" ) type testHook struct { hook.BaseHook } func (testHook) Name() string { return "test" } func (testHook) Page(page *page.Page) *page.Page { fmt.Println(page.Title) return page } func NewHook(conf config.Config, theme theme.Theme) hook.Hook { return &testHook{} }
- 编译为so文件
go build -buildmode=plugin sofile.go
- 注册插件
registered_hooks: - "sofile" hooks.sofile.files: - "sofile.so"
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
即可覆盖本地默认配置