README
¶
* Snow
静态博客生成器
** 快速开始
*** 开始(Quickstart)
**** 创建新的站点
#+begin_example
──╼ ./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]
#+end_example
**** 编译和预览
#+begin_example
└──╼ 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
#+end_example
*** 安装(Installation)
#+begin_example
└──╼ go install https://github.com/honmaple/snow
#+end_example
*** 编译(Build)
#+begin_example
└──╼ git clone https://github.com/honmaple/snow --depth=1
└──╼ cd snow
└──╼ go mod tidy
└──╼ go build .
#+end_example
*** 命令行(Cli usage)
#+begin_example
└──╼ ./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)
#+end_example
**** init
#+begin_example
└──╼ ./snow init
└──╼ ./snow init myblog
#+end_example
如果不指定 *myblog* 目录,默认会在当前目录下生成一个 *config.yaml* 文件和一个 *content* 目录
**** build
该命令会构建站点内容内写入到 *{output_dir}* 目录, 如果该目录已经有文件存在,除非制定 =-C= 参数,否则不会自动清理
- 清理输出目录
#+begin_example
└──╼ ./snow build --clean
└──╼ ./snow build -C
#+end_example
- 显示输出详情
#+begin_example
└──╼ ./snow build --debug
└──╼ ./snow build -D
#+end_example
- 指定输出目录
#+begin_example
└──╼ ./snow build --output {output_dir}
└──╼ ./snow build -o {output_dir}
#+end_example
- 指定mode
#+begin_example
└──╼ ./snow build --mode {mode}
└──╼ ./snow build -m {mode}
#+end_example
- 筛选页面
#+begin_example
└──╼ ./snow build --filter {build_filter}
└──╼ ./snow build -F {build_filter}
#+end_example
- 显示所有hooks
#+begin_example
└──╼ ./snow build --hooks
#+end_example
**** server
build 支持的命令 server也同样支持, 除此之外,还有
- 指定监听地址
#+begin_example
└──╼ ./snow server --listen 127.0.0.1:8088
└──╼ ./snow server -l 127.0.0.1:8088
#+end_example
默认监听地址是 =site.url=
- 监听文件修改并重新构建
#+begin_example
└──╼ ./snow server --autoload
└──╼ ./snow server -r
#+end_example
*** 目录结构(Driectory structure)
#+begin_example
.
├── config.yaml
├── content
│ └── posts
│ └── first-page.md
├── static
├── layouts
└── themes
│ └── snow
│ └── static
│ └── template
#+end_example
- *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= 里指定
*** 配置文件(Configuration)
#+begin_src yaml
# 站点配置信息
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"
#+end_src
** 内容管理
*** Section
#+begin_example
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
#+end_example
**** 配置
#+begin_src yaml
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"
#+end_src
*filter* 格式(下同):
#+begin_example
'emacs' in tags and not draft or weight > 1
#+end_example
其中 *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)
**** 元数据
- markdown
#+begin_example
---
title: "title"
categories:
- Snow/Templates
tags:
- linux
- snow
---
#+end_example
- orgmode
#+begin_example
#+TITLE: title
#+DATE: 2022-02-26 17:14:46
#+CATEGORIES: Snow/Templates
#+PROPERTY: TAGS linux,snow
#+PROPERTY: MODIFIED 2023-02-26 14:35:37
#+end_example
- html
#+begin_src 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>
#+end_src
**** 配置
#+begin_src yaml
# 页面目录所在, 其中该目录下应该包括一系列子目录,这些子目录的名称对应为 *页面的类型*, 比如 *content/drafts/* 目录下的 页面类型为 *drafts*, 当然也可以直接在 页面文件头添加 =type: drafts=
content_dir: "content"
#+end_src
**** 路径变量(*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() | 是否有同一类型下一篇 |
*** 分类系统(Taxonomy)
**** 配置
#+begin_src yaml
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:
#+end_src
**** 路径变量
- *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)
*snow* 中的分类系统是基于归档实现的,该功能类似 *SQL* 中的 =group by=, 所以如果要实现归档页可以有两种方式:
1. 添加 =taxonomies.{key}=, ={key}= 可以是页面元数据里的任意字段, 比如 =categories=, =tags=, 如果需要按照时间归档, 格式为 =date:2006/01=, 其中 =2006/01= 为Go时间格式,表示按年月归档, 并生成链接 */archives/2022/10/index.html*
#+begin_src yaml
taxonomies:
date:2006/01:
path: "archives/index.html"
template: "archives.html"
term_path: "archives/{term}/index.html"
term_template: "period_archives.html"
#+end_src
2. 在 ={content_dir}= 下添加一个 =archives.md= 的文件
#+begin_example
path: archives.html
template: archives.html
section: true
#+end_example
然后在模板 ={templates}/archives.html= 使用 =pages.GroupBy({key})=
#+begin_src html
{%- 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 %}
#+end_src
*** 分页(Pagination)
**** 路径变量
|--------------+-------------------|
| 变量 | 描述 |
|--------------+-------------------|
| {name} | 路径名称 |
| {extension} | 路径扩展 |
| {number} | 页码, 第一页为空 |
| {number:one} | 页码, 第一页为"1" |
- 示例一:
#+begin_src yaml
path: "section/index.html"
paginate_path: "{name}{number}{extension}"
#+end_src
- 第一页: =section/index.html=
- 第二页: =section/index2.html=
- 第三页: =section/index3.html=
- 示例二:
#+begin_src yaml
path: "section/index.html"
paginate_path: "page/{number:one}{extension}"
#+end_src
- 第一页: =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)
使用者可以自定义草稿标志,但推荐使用两种形式:
1. 添加元数据 =draft: true=, 构建时增加筛选条件
- *草稿*
#+begin_example
snow build --filter 'draft = true'
#+end_example
- *非草稿*
#+begin_example
snow build -F 'not draft'
#+end_example
2. 创建一个单独的 =drafts= 目录存放草稿
- *草稿*
#+begin_example
snow build -F 'type = "drafts"'
#+end_example
- *非草稿*
#+begin_example
snow build -F 'type != "drafts"'
#+end_example
注: 默认筛选条件可以写入配置 =build_filter=
*** 输出格式(Atom,Rss,JSON)
可以生成 *rss* ,*atom* 或者其它任意格式(需要自定义模版)
**** 配置
#+begin_src yaml
# 设置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"
#+end_src
**** 模版变量
|---------+--------------------------|
| 变量 | 描述 |
|---------+--------------------------|
| section | 仅生成section 有效 |
| term | 仅生成taxonomy term 有效 |
| pages | 页面列表 |
*** 静态文件(Static)
静态文件分 *主题静态文件* 和 *配置指定的静态文件*
**** 主题静态文件
#+begin_example
├── themes
│ └── snow
│ └── static
│ └── main.css
#+end_example
主题目录下的所有文件默认会复制到 *output* 目录, 除非设置 =statics.@theme/static.path= 为空
**** 指定的静态文件
该文件需要在配置指定
#+begin_src yaml
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"
#+end_src
*** 多语言(Multilingual)
需要配置 =languages=
#+begin_src yaml
languages.en:
translations: "i18n/en.yaml"
taxonomies:
special_tags:
path: "{taxonomy}/index.html"
languages.fr:
translations: "i18n/fr.yaml"
ignores:
# 忽略所有的静态文件,与主站点共用一个静态目录
- statics
#+end_src
页面格式:
- ={title}.en.md=
- ={title}.fr.md=
或者可以在文件头指定 =lang: en=
** 模版(templates)
[[https://github.com/flosch/pongo2]]
** 主题(theme)
*** 安装
*** 开发
**** 主题目录结构
其中 *templates* 和 *static* 名称不可修改
#+begin_example
simple/
├── theme.yaml
├── templates
│ ├── post.html
│ ├── index.html
│ ├── archives.html
├── static
│ ├── main.css
#+end_example
**** 配置
#+begin_src yaml
theme:
# 主题名称, 未设置将使用默认主题
name: "test-theme"
# 默认的主题配置,该配置会自动合并,除非设置为空
config: "theme.yaml"
# 主题模版覆盖, 增加同名的文件到 *override* 配置的目录, snow将会优先使用该文件
override: "layouts"
#+end_src
** 插件(hooks)
#+begin_src yaml
registered_hooks:
- "i18n"
- "assets"
- "encrypt"
- "shortcode"
#+end_src
*** i18n
- 模版
#+begin_src html
{% i18n "tags" %}
{% T "tags %d" 12 %}
{{ i18n("authors") }}
{{ T("authors") }}
{{ _("authors %f", 3.14) }}
#+end_src
甚至可以直接使用变量
{{ _(term.Name) }}
- 翻译文件
默认会加载主题下 *i18n* 目录下的文件
#+begin_example
i18n
├── en.yaml
└── zh.yaml
#+end_example
文件内容
#+begin_src yaml
---
- id: "authors"
tr: "作者"
- id: "tags"
tr: "标签"
#+end_src
也可以自定义文件位置或翻译内容覆盖主题原有的翻译
#+begin_src yaml
languages.en:
translations: "i18n/en.yaml"
languages.zh:
translations:
- id: "authors"
tr: "作者"
#+end_src
*** encrypt
内容加密, 需要一个密码
#+begin_src html
{{ page.Content | encrypt:"123456" }}
#+end_src
*** shortcode
用于快速插入已有模版, 示例:
#+begin_example
<shortcode _name="encrypt" password="1234567">
hello *markdown*
</shortcode>
<shortcode _name="gist" author="spf13" id="7896402" />
#+end_example
可以自定义 *shortcode* 到主题的 =templates/shortcodes= 目录下, 目前内置 *gist*, *encrypt*
- 如果使用的外部 =js,css= 文件可以加载内置的 =shortcode.js= 实现全局只加载一次,具体可以参考 [[https://github.com/honmaple/snow/blob/master/builder/theme/internal/templates/shortcodes/encrypt.html][shortcodes/encrypt.html]]
- 如果想要在单个页面只加载一次,请使用
#+begin_example
_counter == 0
#+end_example
*** assets
静态文件处理
#+begin_src yaml
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"
#+end_src
#+begin_src html
{% 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 %}
#+end_src
*** sofile
*sofile* 允许使用Go的 =Plugin= 系统支持自定义插件
- 创建一个 =sofile.go= 的文件
#+begin_src 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{}
}
#+end_src
- 编译为so文件
#+begin_example
go build -buildmode=plugin sofile.go
#+end_example
- 注册插件
#+begin_src yaml
registered_hooks:
- "sofile"
hooks.sofile.files:
- "sofile.so"
#+end_src
** 本地测试和正式发布
snow 提供了 *mode* 配置用于区分本地测试和正式发布
#+begin_src yaml :noindent
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"
#+end_src
只要在构建时使用 =snow build --mode publish= 即可覆盖本地默认配置
Documentation
¶
There is no documentation for this package.
Click to show internal directories.
Click to hide internal directories.