内容适配器
用内容适配器在构建站点时根据外部数据动态生成页面。
概述
内容适配器(content adapter)是一种在构建站点时动态创建页面的模板。例如,可以用它根据远程数据源生成页面,数据格式可以是 JSON、TOML、YAML 或 XML。
与放在 layouts 目录中的模板不同,内容适配器放在 content 目录中,每个目录、每种语言最多一个。内容适配器创建页面时,页面的逻辑路径相对于该适配器所在的位置。
content/
├── articles/
│ ├── _index.md
│ ├── article-1.md
│ └── article-2.md
├── books/
│ ├── _content.gotmpl <-- 内容适配器
│ └── _index.md
└── films/
├── _content.gotmpl <-- 内容适配器
└── _index.md内容适配器的约定文件名是 _content.gotmpl,其语法与 layouts 目录中的模板相同。适配器中可以使用任意模板函数,也可以使用下面这些方法。
方法
AddPage:向站点添加一个页面。
{{ $content := dict
"mediaType" "text/markdown"
"value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
}}
{{ $page := dict
"content" $content
"kind" "page"
"path" "the-hunchback-of-notre-dame"
"title" "The Hunchback of Notre Dame"
}}
{{ .AddPage $page }}AddResource:向站点添加一个页面资源。
{{ with resources.Get "images/a.jpg" }}
{{ $content := dict
"mediaType" .MediaType.Type
"value" .
}}
{{ $resource := dict
"content" $content
"path" "the-hunchback-of-notre-dame/cover.jpg"
}}
{{ $.AddResource $resource }}
{{ end }}之后可以在页面模板中这样取回新添加的资源:
{{ with .Resources.Get "cover.jpg" }}
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
{{ end }}Site:返回页面将被添加到的站点。
{{ .Site.Title }}Store:返回一个用于存储与操作键值对的持久数据结构(maps.Scratch)。它的主要用途是在启用 EnableAllLanguages 时,在多次执行之间传递值。
{{ .Store.Set "key" "value" }}
{{ .Store.Get "key" }}EnableAllLanguages:默认情况下,Hugo 只对站点矩阵中第一个匹配的站点执行一次内容适配器。用这个方法可以把执行扩展到所有语言,同时保持当前的语言角色与版本。
{{ .EnableAllLanguages }}EnableAllDimensions:默认同样只执行一次;用这个方法可以把执行扩展到语言、版本与角色的所有可能组合。
需要更细粒度的控制时,可以在前置元数据或内容挂载(mount)中定义 sites.matrix。
页面映射
传给 AddPage 的映射可以设置任意前置元数据字段,但有两个例外:不要设置 markup,而要用 content.mediaType 指定内容格式;分类法键(例如 tags、categories)不要放在顶层,应放进 params 映射。
下表是最常传入 AddPage 的字段:
| 键 | 说明 | 必填 |
|---|---|---|
content.mediaType |
内容的媒体类型,默认是 text/markdown |
|
content.value |
内容值,字符串 | |
dates.date |
页面创建日期,time.Time 值 |
|
dates.expiryDate |
页面失效日期 | |
dates.lastmod |
页面最后修改日期 | |
dates.publishDate |
页面发布日期 | |
params |
页面参数映射 | |
path |
相对于内容适配器的页面逻辑路径,不要包含开头的斜杠与文件扩展名 | 是 |
title |
页面标题 |
资源映射
传给 AddResource 的映射字段如下:
| 键 | 说明 | 必填 |
|---|---|---|
content.mediaType |
内容的媒体类型 | 是 |
content.value |
内容值,字符串或资源 | 是 |
name |
资源名称 | |
params |
资源参数映射 | |
path |
相对于内容适配器的资源逻辑路径,不要包含开头的斜杠 | 是 |
title |
资源标题 |
示例:根据远程数据生成书评页面
第 1 步:建立内容结构。
content/
└── books/
├── _content.gotmpl <-- 内容适配器
└── _index.md第 2 步:查看远程数据,确定如何把键值对映射到前置元数据字段。
第 3 步:编写内容适配器。
{{/* 获取远程数据。 */}}
{{ $data := dict }}
{{ $url := "https://gohugo.io/shared/examples/data/books.json" }}
{{ with try (resources.GetRemote $url) }}
{{ with .Err }}
{{ errorf "Unable to get remote resource %s: %s" $url . }}
{{ else with .Value }}
{{ $data = . | transform.Unmarshal }}
{{ else }}
{{ errorf "Unable to get remote resource %s" $url }}
{{ end }}
{{ end }}
{{/* 添加页面与页面资源。 */}}
{{ range $data }}
{{/* 添加页面。 */}}
{{ $content := dict "mediaType" "text/markdown" "value" .summary }}
{{ $dates := dict "date" (time.AsTime .date) }}
{{ $params := dict "author" .author "isbn" .isbn "rating" .rating "tags" .tags }}
{{ $page := dict
"content" $content
"dates" $dates
"kind" "page"
"params" $params
"path" .title
"title" .title
}}
{{ $.AddPage $page }}
{{/* 添加页面资源。 */}}
{{ $item := . }}
{{ with $url := $item.cover }}
{{ with try (resources.GetRemote $url) }}
{{ with .Err }}
{{ errorf "Unable to get remote resource %s: %s" $url . }}
{{ else with .Value }}
{{ $content := dict "mediaType" .MediaType.Type "value" .Content }}
{{ $params := dict "alt" $item.title }}
{{ $resource := dict
"content" $content
"params" $params
"path" (printf "%s/cover.%s" $item.title .MediaType.SubType)
}}
{{ $.AddResource $resource }}
{{ else }}
{{ errorf "Unable to get remote resource %s" $url }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}第 4 步:创建单页模板渲染每一条书评。模板中可以用 .Params.author、.Params.isbn 等读取上一步写入的参数,用 .Resources.GetMatch "cover.*" 取回封面图,用 .GetTerms "tags" 取用标签。
多语言项目
在多语言项目中,你可以用 EnableAllLanguages 方法为所有语言创建一个内容适配器,也可以为每种语言分别创建内容适配器。
按文件名区分语言时,在适配器文件名中加入语言标识。例如项目配置为:
[languages.en]
weight = 1
[languages.de]
weight = 2对应的内容结构为:
content/
└── books/
├── _content.de.gotmpl
├── _content.en.gotmpl
├── _index.de.md
└── _index.en.md按目录区分语言时,项目配置为:
[languages.en]
contentDir = 'content/en'
weight = 1
[languages.de]
contentDir = 'content/de'
weight = 2则应在每个目录中各放一个内容适配器:
content/
├── de/
│ └── books/
│ ├── _content.gotmpl
│ └── _index.md
└── en/
└── books/
├── _content.gotmpl
└── _index.md页面冲突
两个或多个页面发布路径相同时即发生冲突。由于并发处理,最终发布页面的内容是未定义的,也无法指定处理顺序。例如目录中已有 books/the-hunchback-of-notre-dame.md,而同一目录下的内容适配器又创建 books/the-hunchback-of-notre-dame,就会导致这种结果。构建项目时可以用 --printPathWarnings 标志检测页面冲突。
适用场景与限制
- 适合从外部数据源(JSON、TOML、YAML、XML)批量生成页面与页面资源,并让这些页面与手写内容一起参与模板渲染。
- 每个目录、每种语言最多一个内容适配器,文件名固定为
_content.gotmpl。 - 执行期间
Site对象尚未完全初始化,依赖已构建页面的方法不可用,会返回错误。 - 冲突页面的内容不确定,且无法控制处理顺序,可用
--printPathWarnings检测。