snow

command module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 27, 2024 License: BSD-3-Clause Imports: 1 Imported by: 0

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

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL