結構な頻度で.Scratch
というものがでてきます。
この.Scratch
が果たしている役割についてお話したいと思います。
スコープ問題
Hugo で変数を定義するとき、その変数の
は関数を超えることはできません。例えば、次のような例(原文ママ)があります。
{{ $v := "init" }}{{ if true }}{{ $v := "changed" }}{{ end }}v: {{ $v }} {{/* => init */}}
.Scratch
は、このような事態を解消するために用いられます。
関数内部から外部で定義された関数を参照することはできますが、逆はできません。
{{ $v := "init" }}v: {{ $v }}{{ range seq 0 1 }}before: {{ $v }}{{ $v := "changed" }}after: {{ $v }}{{ end }}v: {{ $v }}-> v: initbefore: initafter: changedbefore: initafter: changedv: init
関数内部の変数$v
を.Scratch
を使って関数外部に運ぶということですね。
逆に、外の変数を中に運ぶために.Scratch
を使う必要はありません。
.Scratch
の実際のスコープは「ページ単位」「ショートコード単位」だそうです。
.Scratch
の使い方
スクラッチはキーと値のペアからなるディクショナリ(辞書・マップ)のような構造をとります。
.Set
スクラッチを作るには、.Scratch.Set
を使います。
{{ .Scratch.Set "key" "value" }}
すでに指定したキーに対応する値があった場合、上書きをします。
{{ .Scratch.Set "key" "value" }}{{ .Scratch.Set "key" "newvalue" }}{{ .Scratch.Get "key" }} -> newvalue
.Get
値を呼び出すには次のようにします。
{{ .Scratch.Get "key" }} -> value
.Add
.Add
は、与えられたキーに対応する値に、指定された値をプラスします。
golang で対応している + 演算子の使い方は使えるようです。
数値の加算、文字列の結合、リストの結合などができます。
{{ .Scratch.Add "key" "added" }}{{ .Scratch.Get "key" }} -> valueadded
.Set
の代わりに使ってもエラーになったりはしませんが、上書きはしません。
.SetInMap
.SetInMap
というものもあって、これはマップをスクラッチの値にとって管理したいときに便利そうですね。
次の例は公式のものがもと。
{{ .Scratch.SetInMap "greetings" "english" "Hello" }}--何か処理--{{ .Scratch.SetInMap "greetings" "french" "Bonjour" }}{{ .Scratch.Get "greetings" }} -> map[french:Bonjour english:Hello]
.SetInMap
は Set という名前ですが Add 的な側面がある(上書きしない)ようです。
.SetInMap
を使わなければ次のようになるでしょう。
{{ .Scratch.Set "greetings" (dict "english" "Hello") }}--何か処理--{{ .Scratch.Set "greetings" (merge (.Scratch.Get "greetings") (dict "french" "Bonjour")) }}{{ .Scratch.Get "greetings" }} -> map[french:Bonjour english:Hello]
.GetSortedMapValues
マップから値のみを取り出した配列を返します。この例も公式のがもとです。
{{ .Scratch.SetInMap "greetings" "english" "Hello" }}{{ .Scratch.SetInMap "greetings" "french" "Bonjour" }}{{ .Scratch.GetSortedMapValues "greetings" }} -> [Hello Bonjour]
使わなければこんな感じ。
{{ .Scratch.SetInMap "greetings" "english" "Hello" }}{{ .Scratch.SetInMap "greetings" "french" "Bonjour" }}{{ $greetings := .Scratch.Get "greetings" }}{{ slice (index $greetings "english") ((index $greetings "french")) }}-> [Hello Bonjour]
.Delete
スクラッチを削除します。
{{ .Scratch.Delete "key" }}{{ .Scratch.Get "key" }} ->
ローカルスコープのnewScratch
Hugo 0.43 からは、newScratch
によってローカルスコープのスクラッチを作ることができます。
.Scratch
はファイル間を行き来できるのですが、こちらはそのようなことができません。
ただしif
やrange
などの関数の内外を行き来することはできます。
名前衝突による上書きを避ける目的で私は積極的に使っています。
{{ $scr := newScratch }}{{ $scr.Set "a" "b" }}{{ if true }}{{ $scr.Add "a" "aa" }}{{ end }}{{ $scr.Get "a" }} -> baa
さきほど問題になっていたコードは、スクラッチによってこのように解決されます。
{{ $scr := newScratch }}{{ $scr.Set "v" "init" }}{{ if true }}{{ $scr.Set "v" "changed" }}{{ end }}{{ $scr.Get "v" }} -> changed
with
やrange
と併用する際の注意点
newScratch
の場合は問題にならないのですが、
.Scratch
をwith
やrange
の中で使うときには先頭に$
が必要になります。
これは、with A
やrange A
と書いたブロックの中での.
はA
やA.
という意味に捉えられるからです。
{{ .Scratch.Set "v" "init" }}{{ with .Scratch.Get "v" }}{{ . }}{{ end }}-> init
{{ .Scratch.Set "v" "init" }}{{ with .Scratch.Get "v" }}{{ .Scratch.Set "v" "changed" }}{{ end }}-> ERROR: can't evaluate field Scratch in type string
先頭に$
をつければこれが回避できます。
{{ .Scratch.Set "v" "init" }}{{ with .Scratch.Get "v" }}{{ $.Scratch.Set "v" "changed" }}{{ end }}{{ .Scratch.Get "v" }} -> changed
プログラミングをやっていると、予期せぬ動きをしたりすることが多々ありますが、
変数のスコープ問題がその気づきにくい原因であることも多くあります。
Hugo の場合これはスクラッチによって解決できます。
使えるととても便利なので、ぜひ使ってみてください。
この記事がお役に立てたならうれしいです。
読んでくださりありがとうございました。