【Hugo】.Scratch とは?

のテンプレート文を眺めていると、

結構な頻度で.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: init
before: init
after: changed
before: init
after: changed
v: 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" }} ->
  関連記事【Hugo】ショートコードで複雑な HTML を簡潔に
【Hugo】ショートコードで複雑な HTML を簡潔に

ローカルスコープのnewScratch

Hugo 0.43 からは、newScratchによってローカルスコープのスクラッチを作ることができます。

.Scratchはファイル間を行き来できるのですが、こちらはそのようなことができません。

ただしifrangeなどの関数の内外を行き来することはできます。

名前衝突による上書きを避ける目的で私は積極的に使っています。

{{ $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

withrangeと併用する際の注意点

newScratchの場合は問題にならないのですが、

.Scratchwithrangeの中で使うときには先頭に$が必要になります。

これは、with Arange Aと書いたブロックの中での.AA.という意味に捉えられるからです。

{{ .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】findRE・replaceRE の使い方
【Hugo】findRE・replaceRE の使い方

プログラミングをやっていると、予期せぬ動きをしたりすることが多々ありますが、

変数のスコープ問題がその気づきにくい原因であることも多くあります。

Hugo の場合これはスクラッチによって解決できます。

使えるととても便利なので、ぜひ使ってみてください。

この記事がお役に立てたならうれしいです。

読んでくださりありがとうございました。👋