前回の続き。今回は Categories, Tags そして Section について
Categories と Tags
まずは Categories と Tags について。
Hugo では記事に Categories および Tags を設定することができる。以下のように記述すれば良い。
+++
date = "2015-09-05T16:40:41+09:00"
draft = false
title = "Hello!"
categories = [ "hugo" ]
tags = [ "hello", "world" ]
+++
ようこそ, [Hugo](http://gohugo.io/) の世界へ!
このように Categories および Tags のキーワードを配列で列挙する(キーワードがひとつでも配列に入れること)。これをビルドすると以下のようになる。
C:\hugo-env\www>hugo
0 draft content
0 future content
1 pages created
0 paginator pages created
2 tags created
1 categories created
in 20 ms
C:\hugo-env\www>tree /f .
C:\HUGO-ENV\WWW
│ config.toml
│
├─archetypes
├─content
│ hello.md
│
├─data
├─layouts
│ │ 404.html
│ │ index.html
│ │
│ └─_default
│ single.html
│
├─public
│ │ 404.html
│ │ index.html
│ │ index.xml
│ │ sitemap.xml
│ │
│ ├─categories
│ │ └─hugo
│ │ index.html
│ │ index.xml
│ │
│ ├─hello
│ │ index.html
│ │
│ └─tags
│ ├─hello
│ │ index.html
│ │ index.xml
│ │
│ └─world
│ index.html
│ index.xml
│
└─static
出力先に categories
および tags
フォルダが作成され,更にその下にキーワードのフォルダが作成されているのがおわかりだろうか。キーワードのフォルダの index.xml
は feed である。つまり Hugo ではカテゴリ・タグ毎に自動で feed が作成される。
index.html
は(テンプレートがないため)この時点では空である。テンプレートは layouts/_default
フォルダに list.html
ファイルを配置する。名前からして Categories/Tags 毎に記事を列挙することを期待しているわけやね(笑) とりあえず中身はこんな感じでどうだろう。
<!DOCTYPE html>
{{ with .Site.LanguageCode }}<html lang="{{ . }}">{{ else }}<html>{{ end }}
<head>
<meta charset="utf-8">
<title>List of {{ .Title }} -- {{ .Site.Title }}</title>
</head>
<body>
<h1>List of {{ .Title }}</h1>
<ul style="list-style:none;">
{{ range .Data.Pages }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a> (<time pubdate="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>){{ if .Draft }} #Draft{{ end }}</li>
{{ end }}
</ul>
</body>
<html>
ビルド結果はこんな感じ。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>List of Hugo -- Hello World!</title>
</head>
<body>
<h1>List of Hugo</h1>
<ul style="list-style:none;">
<li><a href="http://hello.example.com/hello/">Hello!</a> (<time pubdate="2015-09-05">2015-09-05</time>)</li>
</ul>
</body>
<html>
おおう。キーワードの頭文字が勝手に大文字に変換されてるぜ。
前回を読んでいる人はトップページのテンプレート layouts/index.html
とほぼ同じ構成であることに気づくと思う。違うのは {{ .Title }}
には Categories/Tags のキーワードが入ることと {{ range }}
構文の対象変数が .Site.Pages
ではなく .Data.Pages
であることだ。
ついでに記事ページで Categories/Tags を表示できるようにしてみよう。
<!DOCTYPE html>
{{ with .Site.LanguageCode }}<html lang="{{ . }}">{{ else }}<html>{{ end }}
<head>
<meta charset="utf-8">
<title>{{ .Title }} -- {{ .Site.Title }}</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<nav>
{{ with .Params.categories }}<div>Categories:{{ range . }} <a href="/categories/{{ . }}/">{{ . }}</a>{{ end }}</div>{{ end }}
{{ with .Params.tags }}<div>Tags:{{ range . }} <a href="/tags/{{ . }}/">#{{ . }}</a>{{ end }}</div>{{ end }}
</nav>
<div>{{ .Content }}</div>
</body>
<html>
以下がビルド結果。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>Hello! -- Hello World!</title>
</head>
<body>
<h1>Hello!</h1>
<nav>
<div>Categories: <a href="/categories/hugo/">hugo</a></div>
<div>Tags: <a href="/tags/hello/">#hello</a> <a href="/tags/world/">#world</a></div>
</nav>
<div><p>ようこそ, <a href="http://gohugo.io/">Hugo</a> の世界へ!</p>
</div>
</body>
<html>
{{ with }}
構文の中に {{ range }}
構文が入ってて分かりにくいが,変数のスコープに注意すれば,それほど難しくはないはず。注意しないといけないのは, Categories/Tags の変数名が .Categories
, .Tags
ではなく .Params.categories
, .Params.tags
になっている点である。
Categories と Tags との間に機能上の違いはない。名前が違うだけである。おそらく他のブログサービスとの互換性の為にあるのだろうが,「ゼロから始める」のであれば Categories と Tags を併記することに意味はない。それなら後述する Section と組み合わせるほうが合理的である。
.Params のルール
Front matter で指定する変数は,「テンプレート変数」にある既定のもの以外は .Params
以下に自動的に組み換えられる。なおかつ .Params
以下の変数名は小文字になる決まりである。 Categories/Tags は標準機能なのだが,どういうわけかこれだけ .Params
以下に組み替えられる。なんだかなぁ。「歴史的経緯」ってやつだろうか。
ちなみに config.toml
によるサイト設定では .Site.Params
への暗黙的な組み換えは行われないため,明示的に記述する必要がある。
[params]
author = "Spiegel"
この非対称性も分かりにくいんだよなぁ。
Section
content
フォルダの下に practice
というフォルダを作り,ここに hello.md
を移動させてみよう。新たに作る場合は path 付きで作成すればよい。
C:\hugo-env\www>hugo new practice/hello.md
C:\hugo-env\www\content\practice\hello.md created
これでビルドしてみる(ファイルを移動した際は出力フォルダの中をいったんクリーンにしてからビルドするとゴミが残らない)。
C:\hugo-env\www>hugo
0 draft content
0 future content
1 pages created
0 paginator pages created
2 tags created
1 categories created
in 19 ms
C:\hugo-env\www>tree /f .
C:\HUGO-ENV\WWW
│ config.toml
│
├─archetypes
├─content
│ └─practice
│ hello.md
│
├─data
├─layouts
│ │ 404.html
│ │ index.html
│ │
│ └─_default
│ list.html
│ single.html
│
├─public
│ │ 404.html
│ │ index.html
│ │ index.xml
│ │ sitemap.xml
│ │
│ ├─categories
│ │ └─hugo
│ │ index.html
│ │ index.xml
│ │
│ ├─practice
│ │ │ index.html
│ │ │ index.xml
│ │ │
│ │ └─hello
│ │ index.html
│ │
│ └─tags
│ ├─hello
│ │ index.html
│ │ index.xml
│ │
│ └─world
│ index.html
│ index.xml
│
└─static
hello/index.html
が practice/hello/index.html
に配置されるのは予想通りだと思うが, practice
に index.html
と index.xml
が生成されているのがおわかりだろうか。 practice/index.html
の中身はこんな感じ。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>List of Practices -- Hello World!</title>
</head>
<body>
<h1>List of Practices</h1>
<ul style="list-style:none;">
<li><a href="http://hello.example.com/practice/hello/">Hello!</a> (<time pubdate="2015-09-05">2015-09-05</time>)</li>
</ul>
</body>
<html>
これは layouts/_default/list.html
テンプレートで生成されたページだ。 Hugo ではフォルダ付きの記事を作成すると,そのフォルダが Section として機能する。
当然だが,ひとつの記事はひとつの Section にしか帰属できない。これは先の Categories/Tags との大きな違いである。 Section と Categories/Tags を組み合わせれば縦串と横串で記事を指示できるようになる。
ついでに記事ページで Section を表示できるようにしてみよう。
<!DOCTYPE html>
{{ with .Site.LanguageCode }}<html lang="{{ . }}">{{ else }}<html>{{ end }}
<head>
<meta charset="utf-8">
<title>{{ .Title }}{{ with .Section }} -- {{ . }}{{ end }} -- {{ .Site.Title }}</title>
</head>
<body>
<h1>{{ .Title }}{{ with .Section }} [<a href="/{{ . }}/">{{ . }}</a>]{{ end }}</h1>
<nav>
{{ with .Params.categories }}<div>Categories:{{ range . }} <a href="/categories/{{ . }}/">{{ . }}</a>{{ end }}</div>{{ end }}
{{ with .Params.tags }}<div>Tags:{{ range . }} <a href="/tags/{{ . }}/">#{{ . }}</a>{{ end }}</div>{{ end }}
</nav>
<div>{{ .Content }}</div>
</body>
<html>
以下がビルド結果。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>Hello! -- practice -- Hello World!</title>
</head>
<body>
<h1>Hello! [<a href="/practice/">practice</a>]</h1>
<nav>
<div>Categories: <a href="/categories/hugo/">hugo</a></div>
<div>Tags: <a href="/tags/hello/">#hello</a> <a href="/tags/world/">#world</a></div>
</nav>
<div><p>ようこそ, <a href="http://gohugo.io/">Hugo</a> の世界へ!</p>
</div>
</body>
<html>
記事のフォルダ階層はいくらでも深くできるが, Section として認識されるのは直下のフォルダのみのようである。たとえば content/practice/hello.md
を content/practice/firstcode/hello.md
に移動してビルドすると(出力フォルダはクリーンアップしてね)
C:\hugo-env\www>tree /f .
C:\HUGO-ENV\WWW
│ config.toml
│
├─archetypes
├─content
│ └─practice
│ └─firstcode
│ hello.md
│
├─data
├─layouts
│ │ 404.html
│ │ index.html
│ │
│ └─_default
│ list.html
│ single.html
│
├─public
│ │ 404.html
│ │ index.html
│ │ index.xml
│ │ sitemap.xml
│ │
│ ├─categories
│ │ └─hugo
│ │ index.html
│ │ index.xml
│ │
│ ├─practice
│ │ │ index.html
│ │ │ index.xml
│ │ │
│ │ └─firstcode
│ │ └─hello
│ │ index.html
│ │
│ └─tags
│ ├─hello
│ │ index.html
│ │ index.xml
│ │
│ └─world
│ index.html
│ index.xml
│
└─static
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>Hello! -- practice -- Hello World!</title>
</head>
<body>
<h1>Hello! [practice]</h1>
<nav>
<div>Categories: <a href="/categories/hugo/">hugo</a></div>
<div>Tags: <a href="/tags/hello/">#hello</a> <a href="/tags/world/">#world</a></div>
</nav>
<div><p>ようこそ, <a href="http://gohugo.io/">Hugo</a> の世界へ!</p>
</div>
</body>
<html>
というわけで,あくまでも practice
フォルダが Section になっているのがわかると思う。
Section ごとのカスタマイズ
Hugo では Section ごとにカスタマイズすることができる。 layouts
フォルダに section
フォルダを作成し,その中に <section name>.html
ファイルを作成すると,そのテンプレートで Section のトップページ(<section name>/index.html
)を作成する。今回は practice.html
を作成してみる。
<!DOCTYPE html>
{{ with .Site.LanguageCode }}<html lang="{{ . }}">{{ else }}<html>{{ end }}
<head>
<meta charset="utf-8">
<title>Hugo の練習 -- {{ .Site.Title }}</title>
</head>
<body>
<h1>Hugo の練習</h1>
<ul style="list-style:none;">
{{ range .Data.Pages }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a> (<time pubdate="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>){{ if .Draft }} #Draft{{ end }}</li>
{{ end }}
</ul>
</body>
<html>
ビルド結果。変わり映えしなくてすみません。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>Hugo の練習 -- Hello World!</title>
</head>
<body>
<h1>Hugo の練習</h1>
<ul style="list-style:none;">
<li><a href="http://hello.example.com/practice/hello/">Hello!</a> (<time pubdate="2015-09-05">2015-09-05</time>)</li>
</ul>
</body>
<html>
更に Section 内の記事ページもカスタマイズできる。これは layouts
フォルダに Section 名のフォルダを作成し,その中に single.html
を配置する。
<!DOCTYPE html>
{{ with .Site.LanguageCode }}<html lang="{{ . }}">{{ else }}<html>{{ end }}
<head>
<meta charset="utf-8">
<title>{{ .Title }} -- Hugo の練習 | {{ .Site.Title }}</title>
</head>
<body>
<h1>{{ .Title }} -- Hugo の練習</h1>
<nav>
{{ with .Params.categories }}<div>Categories:{{ range . }} <a href="/categories/{{ . }}/">{{ . }}</a>{{ end }}</div>{{ end }}
{{ with .Params.tags }}<div>Tags:{{ range . }} <a href="/tags/{{ . }}/">#{{ . }}</a>{{ end }}</div>{{ end }}
</nav>
<div>{{ .Content }}</div>
</body>
<html>
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>Hello! -- Hugo の練習 | Hello World!</title>
</head>
<body>
<h1>Hello! -- Hugo の練習</h1>
<nav>
<div>Categories: <a href="/categories/hugo/">hugo</a></div>
<div>Tags: <a href="/tags/hello/">#hello</a> <a href="/tags/world/">#world</a></div>
</nav>
<div><p>ようこそ, <a href="http://gohugo.io/">Hugo</a> の世界へ!</p>
</div>
</body>
<html>
これも変わり映えしなくてすみません。
しかし,なんでこんなテンプレートの構成なんだろう。これだと section
や _default
という名前のセクションはカスタマイズ出来ないことになる(まだ説明してないが layouts
フォルダには他にも partials
や shortcodes
といったフォルダもある)。そうではなくて, layouts/section
フォルダの下にセクション名のフォルダを掘ってその中に list.html
や single.html
を配置すればスッキリするのに。
さきほどの .Params
の話といい,どうも Hugo は名前管理がいきあたりばったりな気がする。
categories/hugo という名前の記事はどうなるの?
これも名前に関する話。
categories/hugo.md
という名前の記事は categories/hugo/index.html
に展開される。これって Categories の機能と丸かぶりである。実は Hugo では名前が衝突した際の挙動は明文化されていない(筈)。強いて言うなら実装依存で状況依存である。またビルド時にエラーになることもない。
C:\hugo-env\www>hugo new categories/hugo.md
C:\hugo-env\www\content\categories\hugo.md created
C:\hugo-env\www>hugo undraft content/categories/hugo.md
C:\hugo-env\www>hugo
0 draft content
0 future content
2 pages created
0 paginator pages created
2 tags created
1 categories created
in 24 ms
C:\hugo-env\www>tree /f .
C:\HUGO-ENV\WWW
│ config.toml
│
├─archetypes
├─content
│ ├─categories
│ │ hugo.md
│ │
│ └─practice
│ hello.md
│
├─data
├─layouts
│ │ 404.html
│ │ index.html
│ │
│ └─_default
│ list.html
│ single.html
│
├─public
│ │ 404.html
│ │ index.html
│ │ index.xml
│ │ sitemap.xml
│ │
│ ├─categories
│ │ │ index.html
│ │ │ index.xml
│ │ │
│ │ └─hugo
│ │ index.html
│ │ index.xml
│ │
│ ├─practice
│ │ │ index.html
│ │ │ index.xml
│ │ │
│ │ └─hello
│ │ index.html
│ │
│ └─tags
│ ├─hello
│ │ index.html
│ │ index.xml
│ │
│ └─world
│ index.html
│ index.xml
│
└─static
私の場合は categories/hugo/index.html
が記事ページになった。上のフォルダ構成なら practice.md
も名前が衝突する。このような衝突を避けるにはユーザ側で名前を管理するしかない。小規模なサイトなら人間が気をつければいいが,中規模以上で複数人が関わるようになると結構危ないかもしれない。