結構な頻度で.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 の場合これはスクラッチによって解決できます。
使えるととても便利なので、ぜひ使ってみてください。
この記事がお役に立てたならうれしいです。
読んでくださりありがとうございました。