Hugo

ゼロから始める Hugo — Categories, Tags そして Section

More than 3 years have passed since last update.

前回の続き。今回は Categories, Tags そして Section について


Categories と Tags

まずは Categories と Tags について。

Hugo では記事に Categories および Tags を設定することができる。以下のように記述すれば良い。


content\hello.md

+++

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 毎に記事を列挙することを期待しているわけやね(笑) とりあえず中身はこんな感じでどうだろう。


layouts/_default/list.html

<!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>


ビルド結果はこんな感じ。


public/categories/hugo/index.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 を表示できるようにしてみよう。


layouts/_default/single.html

<!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>


以下がビルド結果。


public/hello/index.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 への暗黙的な組み換えは行われないため,明示的に記述する必要がある。


config.toml

[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.htmlpractice/hello/index.html に配置されるのは予想通りだと思うが, practiceindex.htmlindex.xml が生成されているのがおわかりだろうか。 practice/index.html の中身はこんな感じ。


public/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 を表示できるようにしてみよう。


layouts/_default/single.html

<!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>


以下がビルド結果。


public/practice/hello/index.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.mdcontent/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


public/practice/firstcode/hello/index.html

<!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 を作成してみる。


layouts/section/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>


ビルド結果。変わり映えしなくてすみません。


public/practice/index.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 を配置する。


layouts/practice/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>



public/practice/hello/index.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 フォルダには他にも partialsshortcodes といったフォルダもある)。そうではなくて, layouts/section フォルダの下にセクション名のフォルダを掘ってその中に list.htmlsingle.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 も名前が衝突する。このような衝突を避けるにはユーザ側で名前を管理するしかない。小規模なサイトなら人間が気をつければいいが,中規模以上で複数人が関わるようになると結構危ないかもしれない。


ブックマーク