Open25

Hugoテンプレートしたい

shotakahashotakaha

Templates

https://gohugo.io/templates/

  • Hugoのテンプレート操作について、きちんと理解する
  • とくにGoの書き方なのか、Goテンプレートの書き方なのかを、区別したい
  • 同じ処理をPythonで書くとどうなるかを考えてみる
shotakahashotakaha

Introduction to templating

https://gohugo.io/templates/introduction/

  • HugoではGoのtext/templatehtml/templateパッケージを使っている
shotakahashotakaha

HTMLテンプレートの例

{{ $v1 := 6 }}
{{ $v2 := 7 }}
<p>The product of {{ $v1 }} and {{ $v2 }} is {{ mul $v1 $v2 }}.</p>
  • (コードを書くときに goにすればいいのか、htmlにしたほうがいいのか?)
  • Goテンプレートでは{{ }}の中で、Go処理ができる
shotakahashotakaha

テンプレート変数の作成

  • :=で変数を作成できる
短縮変数宣言
v1 := 6
  • 明示的な宣言
  • Goは静的型付け言語なので、変数の型宣言が必要
明示的な変数宣言
var v1 int = 6
  • テンプレート構文で使う場合、変数名の先頭に$が必要
テンプレート構文
{{ $v1 := 6 }}
<p>The value of v1 is {{ $v1 }}</p>

  • Pythonと比較
v1 = 6
shotakahashotakaha

テンプレート変数の演算

  • テンプレート構文の中で、計算などができる
{{ $v1 := 6 }}
{{ $v2:=7 }}
<p>{{ $v1 }}{{ $v2 }} の積は {{ mul $v1 $v2 }} です</p>
  • 演算の引数はスペースで区切るみたい
{{ 関数名 引数1 引数2 ...}}
  • 利用できる関数は以下を参照

https://gohugo.io/functions/go-template/

shotakahashotakaha

if文

{{ $v1 := 10 }}
{{ if eq $v1 10 }}
  v1 is 10
{{ else }}
  v1 is not 10
{{ end }}
  • テンプレート構文の中でif文が使える
  • {{if 条件}} ... {{ else }} ... {{ end }} の形
  • 条件で比較演算子や論理演算子が使える

  • 通常のGoで書くとこうなる
v1 := 10  // 短縮変数宣言
if v1 == 10 {
  fmt.Println("v1 is 10")
} else {
  fmt.Println("v1 is not 10")
}
  • Pythonで書くとこうなる
v1 = 10
if v1 == 10:
   print("v1 is 10")
else:
   print("v1 is not 10")
shotakahashotakaha

Context

  • コンテキスト(context)は、テンプレートに渡されるデータのこと
  • Hugoのテンプレート作成で、とくに重要な概念
  • ひとつのページの内容はPageオブジェクトに格納されている
  • このページ用テンプレートはPageオブジェクトの中にあるデータ(=コンテクスト)にアクセスして、レンダリングする
shotakahashotakaha

Current Context

<h2>{{ .Title }}</h2>
  • . で現在のコンテキストにアクセスできる
    • この場合の.Pageオブジェクトを指す
  • .Title で現在のコンテキストにあるTitleの値を取得できる
    • Pageオブジェクトのタイトルは、ページのfrontmatterで設定されている値
shotakahashotakaha
  • 処理のスコープによって.が指すオブジェクトは変わる
<h2>{{ .Title }}</h2>

{{ range slice "foo" "bar" }}
  <p>{{ . }}</p>
{{ end }}

{{ with "baz" }}
<p>{{ . }}</p>
{{ end }}

  • スライス(=可変長配列)の処理をGoで書くとこうなる
slice := []string{"foo", "bar"}
for _, value := range slice {
    fmt.Println("<p>%s</p>\n", value)
}
  • Pythonで書くとこうなる
slice = ["foo", "bar"]
for value in slice:
    print(f"<p>{value}</p>")
  • {{ slice "foo" "bar" }}["foo", "bar"]に見えるようにならないといけない
shotakahashotakaha

Template Context

  • コンテキストのスコープについて
    • ブロックの中のコンテキストは.で取得できる
    • ブロックの外のコンテクスト(=ファイルのコンテキスト)は$.で取得できる
  • Go言語自体で、変数のスコープを小さくすることが推奨されている
    • テンプレートをいじるときも、スコープを意識すればOK
{{ with "foo" }}
  <p>{{ $.Title }} - {{ . }}</p>
{{ end }}
<p>Page Title - foo</p>
  • {{ . }}はブロック内のコンテキストなので"foo"となる
  • {{ $.Title }}は、ブロック外のコンテキストにアクセスしている。この場合、ブロック外はPageオブジェクトなので、Page.Titleが取得できる
shotakahashotakaha

Actions

{{ $convertToLower := true }}
{{ if $convertToLower }}
  <h2>{{ strings.ToLower .Title }}</h2>
{{ else }}
  <h2>{{ .Title }}</h2>
{{ end }}
shotakahashotakaha

Whitespace

  • {{ }}テンプレート処理したブロックは改行され、空行として残る
  • {{- -}}を使うと、その空行をトリムできる
{{- $convertToLower := true -}}
{{- if $convertToLower -}}
  <h2>{{ strings.ToLower .Title }}</h2>
{{- end -}}
shotakahashotakaha

Pipes

  • テンプレート関数はパイプ処理できる
// "Hugo" を引数として使っている感じ
{{ strings.ToLower "Hugo" }}

// "Hugo"という結果をパイプで渡している感じ
{{ "Hugo" | strings.ToLower }}
  • 同じ結果が得られるので、どちらをつかってもよい(はず)
  • Hugoではパイプ処理を推奨、ということもないはず
shotakahashotakaha

Line splitting

{{ $v := or .Site.Language.LanguageName .Site.Language.Lang }}

// こう書いてもOK
{{ $v := or
    .Site.Language.LanguageName
    .Site.Language.Lang
}}
shotakahashotakaha

Variables

  • := で変数を初期化する
  • =で、定義済みの変数に代入する
{{ $total := 3 }}
{{ range slice 7 11 21 }}
  {{ $total = add $total . }}
  {{ end }}
{{ $total }}

  • Goで書くとこうなる
total := 3
slices := []int{7, 11, 21}
for _, num := range slices {
    total = total + num
}
fmt.Println(total)
  • Pythonで書くとこうなる
total = 3
for i in [7, 11, 21]:
    total = total + i
print(total)
shotakahashotakaha
  • スライス(=可変長配列)から要素を取得する
{{ $slice := slice "foo" "bar" "baz" }}
{{ index $slice 2}}  // -> baz
shotakahashotakaha
  • 辞書型配列からキーを指定して、要素を取得する
{{ $map := dict "a" "foo" "b" "bar" "c" "baz" }}
{{ index $map "c" }} // -> "baz"
  • dict "a" "foo" "b" "bar" ...を辞書型と読むのは、慣れが必要そう

  • Pythonで書くとこうなる
map = {
  "a": "foo",
  "b": "bar",
  "c": "baz",
}
map["c'"]
# もしくは map.get("c")
shotakahashotakaha

オブジェクトの再代入

  • PageオブジェクトやSiteオブジェクトなどを、好きな変数に代入できる
  • 代入した変数は、元のオブジェクトの値やメソッドにアクセスできる
{{ $homepage := .Site.Home }}
{{ $homepate.Title }}  // <- .Site.Home.Title
shotakahashotakaha

Comments

  • コメントはCスタイル(/* ... */
  • インラインとブロックで書くことができる
  • コメント前後の空行をトリムできる

インライン・コメント

{{/* これはインライン・コメント */}}
{{- /*これは余白なしのインライン・コメント */ -}}

ブロック・コメント

{{/*
これは
ブロック
コメント
*/}}

{{- /*
これは余白を削除した
ブロックコメント
*/ -}}
shotakahashotakaha

HTMLコメント

  • safeHTMLすると、出力したHTMLにコメントを残すことができる
{{ "<!-- これはHTMLに残すコメント -->" | safeHTML }}
  • printfで文字列フォーマットできる
{{ printf "<!-- これは %s に残すコメント -->" .Site.Title | safeHTML }}
shotakahashotakaha

Methods

コンテキストのオブジェクトごとに使えるメソッドの一覧

https://gohugo.io/methods/

  • Pageオブジェクト、Siteオブジェクトがよく使われる
layouts/_default/single.html
<h1>{{ .Page.Title }} | {{ .Site.Title }}</h1>
  • このテンプレートは基本がPageオブジェクトなので、.TitleでもOK
layouts/_default/single.html
<h1>{{ .Title }} | {{ .Site.Title }}</h1>
  • .Page.GetPage パス
    • 指定したパスのPageオブジェクトを取得
    • パスは現在のディレクトリからの相対パス、もしくは
    • ContentDirディレクトリからのパスで指定する
layouts/_default/single.html
{{ $page := .Page.GetPage "/books/les-miserables" }}
{{ $page.Title }}
shotakahashotakaha

Logical Operators

{{ $v1 := true }}
{{ $v2 := false }}
{{ $v3 := false }}
{{ $result := false }}
  • AND
{{ if and $v1 $v2 $v3 }}
    {{ $result = true }}
{{ end }}
{{ $result }}  // -> false
  • OR
{{ if or $v1 $v2 $v4 }}
    {{ $result = true }}
{{ end }}
{{ $result }} // -> true
shotakahashotakaha

Loops

{{ $slices := slice "foo" "bar" "baz" }}
{{ range $slices }}
    <p>{{ . }}</p>
{{ else }}
    <p>The collection is empty</p>
{{ end }}

  • Pythonで書くとこうなる
slices = ["foo", "bar", "baz"]
for s in slices:
    print(f"<p>{s}</p>")
shotakahashotakaha

{{ $total := 0 }}
{{ range seq 4 }}
    {{ $total = add $total . }}
{{ end }}
{{ $total }}    // -> 10

  • Pythonで書くとこうなる
total = 0
for i in range(4):
    total = total + i
print(total)