include
とextends
は呼出側でset
すれば、被呼出側でその変数を参照できる。それ以外は一切、ファイルをまたいで変数を参照することができない。
成果物
情報源
ファイルをまたいだ変数の参照について、掲載されていないと思う。たぶん。
前回
疑問
スコープはどうなっているのか。{% set %}
した変数は、外部ファイル同士で参照できるのか?
結論
機能 | 定義元 | 呼出元 | 是非 |
---|---|---|---|
include | set 定義 |
set 参照 |
⭕ |
include | set 参照 |
set 定義 |
❌ |
import | set 定義 |
set 参照 |
❌ |
import | set 参照 |
set 定義 |
❌ |
extends | set 定義 |
set 参照 |
⭕ |
extends | set 参照 |
set 定義 |
❌ |
include
- includeする側で定義した変数は、includeされる側で参照できた
- includeされる側で定義した変数は、includeする側で参照できなかった
import
- importする側で定義した変数は、importされる側で参照できなかった(先頭import版)
- importする側で定義した変数は、importされる側で参照できなかった(先頭set版)
- importされる側で定義した変数は、importする側で参照できなかった(先頭macro版)
- importされる側で定義した変数は、importする側で参照できなかった(先頭set版)
- importされる側で定義した変数は、importされる側でさえ参照できなかった(先頭set版) importする側
extends
- extendsされる側で定義した変数は、extendsする側で参照できた(setはblockの前)
- extendsされる側で定義した変数は、extendsする側で参照できなかった(setはblockの後)
- extendsする側で定義した変数は、extendsされる側で参照できなかった(setはextendsの後でblockの前。block内はsuper())。
- extendsする側で定義した変数は、extendsする側でさえ参照できなかった(setはextendsの後でblockの前。block内で参照)。 extendsされる側
エラー集
includeされる側で定義した変数は、includeする側で参照できなかった
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/1/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' Reason: Variable `title` not found in context while rendering 'index.html'
importする側で定義した変数は、importされる側で参照できなかった(先頭import版)
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/2/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html': error while rendering macro `body::body` Reason: Variable `title` not found in context while rendering 'macro/body.html'
importする側で定義した変数は、importされる側で参照できなかった(先頭set版)
Error: Error parsing templates Reason: * Failed to parse "/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/2/ytyaru-zola/templates/index.html" --> 3:1 | 3 | {% import "macro/body.html" as body %}␊ | ^--- | = unexpected tag; expected end of input or some content
欠陥ドキュメントへのグチ
インポートはファイルの先頭でしかできない。だからこんなエラーになったんだと思う。そういうことはドキュメントに書いてほしい。
ドキュメントは巧妙に都合の悪いところを避けて書いている。私がインポートにどれだけ文句をつけたかわからないのに。すごいな。自画自賛スキル高い。阿部晋三のような信者。いや教祖か。
ていうか必要条件くらい書いてくれよ。「ファイルの先頭に書かないとエラーになる」ってドキュメントに書くべきことだろ。なんなの? 嫌がらせなの? デバックやらされている気分だよ。いちいち「使ってみなきゃわからない」とか苦痛すぎる。事前にドキュメントでルールを教えてくれたらムダな失敗を避けられるのに。クソが。これだから自分に都合のいいことばかり言うクズは嫌いなんだ。なにがポジティブだ。お前らの自画自賛オナニーのせいで、こっちがネガティブになるんだよ。
importされる側で定義した変数は、importする側で参照できなかった(先頭macro版)
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/import/2/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' Reason: Variable `title` not found in context while rendering 'index.html'
importされる側で定義した変数は、importする側で参照できなかった(先頭set版)
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/import/3/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' Reason: Variable `title` not found in context while rendering 'index.html'
importされる側で定義した変数は、importされる側でさえ参照できなかった(先頭set版)
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/import/4/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html': error while rendering macro `body::body` Reason: Variable `title` not found in context while rendering 'macro/body.html'
だれも参照できない変数が作れてしまうのに構文エラーにならないことへのグチ
だったらもう構文エラーにしろや!
つまりマクロを定義したHTML内で、{% macro 名前() %}
より上に{% set %}
を定義できちゃうけど、それはマクロを定義したファイルからも、それをインポートする側からも、一切参照できないってこと。
バカなの? だれも参照できない変数を宣言することができちゃうって、バカなの?
構文エラーにしろや。
こういうテキトーな感じ、すごくモヤモヤする。もっとキッチリ・カッチリやってくれ。
それとも私がなにか勘違いしている? でもだってこんなふうに書けちゃうよ?
import/4/.../templates/macro/body.html
{% set title = "importのスコープ調査" %} {% set summary = "importされる側で定義した変数は、importされる側でさえ参照できなかった。<code>Reason: Variable `title` not found in context while rendering 'macro/body.html'</code>" %} {% macro body() %} <h1>{{ title }}</h1> <p>{{ summary }}</p> {% endmacro body %}
そしてエラーが以下だよ? 変数title
がないって? 書いてるんですけど? こっちが勝手に「ははーん、さては変数がスコープ外なんだな?」と推測せねばならない。だったらそうと書いてくれ。ドキュメントには仕様が明記されていない。ムリだろ。エラーメッセージも結論しか言ってない。なぜそうなったか、どうすればいいかってことを教えてほしいのに。
Reason: Variable `title` not found in context while rendering 'macro/body.html'
仕方ないから、どうすればいいかは私が書こう。{% set %}
は{% macro %}
から{% endmacro %}
の中に書くべし。というか、マクロファイルの先頭と末尾はかならず{% macro %}
〜{% endmacro %}
にすべし。
{% macro body() %} {% set title = "importのスコープ調査" %} {% set summary = "importされる側で定義した変数は、importされる側でさえ参照できなかった。<code>Reason: Variable `title` not found in context while rendering 'macro/body.html'</code>" %} <h1>{{ title }}</h1> <p>{{ summary }}</p> {% endmacro body %}
だからそうでないケースは構文エラーにしてほしいんだけどなぁ。
extendsされる側で定義した変数は、extendsする側で参照できなかった(setはblockの後)
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/extends/1/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' Reason: Variable `title` not found in context while rendering 'index.html'
extendsする側で定義した変数は、extendsされる側で参照できなかった(setはextendsの後でblockの前。block内はsuper())
Failed to build the site Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/extends/2/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' (error happened in 'extends/base.html'). Reason: Variable `title` not found in context while rendering 'index.html'
extendsする側で定義した変数は、extendsする側でさえ参照できなかった(setはextendsの後でblockの前。block内で参照)。
Error: Failed to render section '/tmp/work/Shell.Zola.Include.Import.Extends.Scope.20210803135518/src/extends/3/ytyaru-zola/content/_index.md' Reason: Failed to render 'index.html' Reason: Variable `title` not found in context while rendering 'index.html'
調査するまでもないパターン
- includeする側で定義した変数は、includeする側で参照できるはず
- includeされる側で定義した変数は、includeされる側で参照できるはず
- importする側で定義した変数は、importするる側で参照できるはず
- importされる側で定義した変数は、importされる側で参照できるはず
- extendsする側で定義した変数は、extendsする側で参照できるはず
- extendsされる側で定義した変数は、extendsされる側で参照できるはず
残念ながら、書き方によっては上記の期待を裏切られることがあった。import
だ。{% macro 名前() %}
から{% endmacro %}
の中で{% set 変数 = 1 %}
した変数を、おなじくその中で{{ 変数 }}
と参照することはできる。ただし、{% macro 名前() %}
の外側で定義した変数は参照できない。むしろどうやっても参照できない変数ができあがってしまう。
{% set_global %}
{% set_global v = 1 %}
のようにグローバル変数にすれば、宣言順序さえ先にやっていればファイルをまたいでも参照できるはず
と思っているのだが、本当にそうだろうか。一部期待を裏切られたせいで、確認しないと安心できなくなった。
というか、考えてみればset
が実行順序に依存するなら、セクション間をまたいだ参照はできないのでは? なにせセクションが複数あるとき、その実行順序はおそらく順不同か、セクション名の辞書順だろう。もしそうなら困る。事前にすべてのset_global
変数値を取得していてほしい。でも複数のセクション間で共有し、かつ演算しているのだとしたら、実行順序によって状態が変わってくるはず。なんかもうバグを作り込む予感しかしない。C言語でいうスパゲッティ状態。
あ、set_globalについて書いてあった。for
文の中で値を保持するためだけにset_global
があるっぽい。なにそれ、ぜんぜんグローバルじゃなくね?
とにかくset_global
はファイルをまたいで参照できないと考えてよいだろう。for
文で保持できる以外はset
と同じらしいので、あらたに調査する必要もなさそう。
結論
include
とextends
は呼出側でset
すれば、被呼出側でその変数を参照できる
上記以外は一切、ファイルをまたいで変数を参照することができない。
使い分け
じゃあ、ファイル分割したとき、どうやって変数を使えばいいの?
別のHTML箇所で、同じデータを出力したいとき、変数をつかう。それぞれ他のファイルに分割して書きたい。このとき、共通データを変数にもたせることで、DRYに書ける。
つまりDRYに書くためには、どうしたらいいの? 以下の2パターンある。
macro
をimport
する(変数の参照はできない)include
,extends
する(親の変数を子が参照できる)
上記2つのあわせ技はできない。マクロで親の変数を使うことはできないからだ。マクロはインポートする必要がある。インポートはファイルの先頭でしかできない。そのせいでインポートする前にset
で変数を定義できない。よって、マクロでは呼出元である親の変数を使うことができない。
いつ、どちらを使うべき?
macro
かinclude
/extends
か。一体いつ、どれを使うべきなの? なにを観点にして判断すればいいの? つぎの優先順位で使っていくとよい。
macro
extends
include
できるだけmacro
を使うことをおすすめする。なぜなら変数をカプセル化できるから。マクロは{% set %}
により他のファイルと共有することが一切できない。なのでマクロを定義したファイル内で完結する。絶対にほかのファイルで宣言された変数に影響されない。唯一、呼出元でわたされた引数のみ気にしていればいい。そのおかげで、読むべきコードの範囲が狭まる。理解しやすくなりバグが出づらい。
つぎはextends
。これはHTMLの外側が共通しているときに使う。<html>
や<body>
など、あきらかに全ページにおいて共通するであろうものだけに厳選する。これで使える部分はかなりかぎられる。
最後にinclude
。これは外側でなく内側の共通部分にもちいる。それでいて引数でなく{% set %}
変数で呼出元データを参照したいときに使う。include
が最も幅広く使えるだろう。記述も簡単。ただし親の変数を参照するせいで、自分より親をすべてみないと変数の値がわからない。バグがあったとき読むべきファイル(コード)の範囲が増えてしまう。
macro
かinclude
かset
か
extends
はほとんど一番最初の<html>
くらいにしか使えない。なのでこれは使いどころに悩まずに済む。
ほかはほぼmacro
かinclude
を使ってDRYに書いてゆく。set
を使ってもいい。では、どれを使えばいいのか。なにを基準に判断すればよいか。
いずれも「共通部分をそれらで定義する」ということは同じだ。では、共通部分のうち、どんな特徴があるところを、どちらで共通化すればいいのか。答えは以下。
set
: 値(bool
,int
,float
,string
)macro
,include
: HTML
set
は短いデータを共通化できる。たとえば以下のように。
... {% set title = "表題A" %} ... <title>{{ title }}</title> ... <h1>{{ title }}</h1> ...
それに対してマクロやインポートは、HTMLを共通化する。
マクロとインクルードはどう使い分けるべきか。
macro
:set
変数を使わずカプセル化したいinclude
: 親のset
変数を参照したい
機能 | 引数 | 親のset 変数を参照できる |
---|---|---|
macro |
有 | ❌ |
include |
無 | ⭕ |
マクロはまとまったHTML文字列のときだけ共通化できる。たとえば以下のように。決して<h1>
と<p>
を離れた場所へ出力できない。なのでこれらが塊のときだけマクロで共通化すること。
さらにインクルードとは違い、親のset
変数を参照できない。その変わりマクロ内だけで参照できる引数が使える。
{% macro heading(heading, summary) %} <h1>{{ heading }}</h1> <p>{{ summary }}</p> {% endmacro %}
もちろんマクロ内では変数が使える。ただしマクロ内でのみ。
{% macro heading() %} {% set heading = "見出しA" %} {% set summary = "要約A" %} <h1>{{ heading }}</h1> <p>{{ summary }}</p> {% endmacro %}
インクルードは引数がない。その代わり親から継承した変数が使える。
<h1>{{ heading }}</h1> <p>{{ summary }}</p>
もちろん変数もつかえる。自分のファイル内であればどこでも。
{% set heading = "見出しA" %} {% set summary = "要約A" %} <h1>{{ heading }}</h1> <p>{{ summary }}</p>
スコープは狭くすべき
バグを減らすためにスコープは狭くすべき。スコープが狭い順は以下。
macro
for
- 他
- get-env
for
もマクロとおなじくローカルスコープだ。変数は自分の{% end* %}
タグ範囲内でしか使えない。だが、for
に限ってはset_global
で変数を外出しできる。なのでマクロよりスコープが広い。
また、get-envで環境変数を取得できる。これを使えば全ファイルで共通する変数を定義できると思う。でも、set-env
がない。つまり事前に環境変数をなんとかして設定しておかねばならない。[zola][]だけで完結できなくなってしまう。使わないほうがよさそうだ。
所感
マクロうぜぇって思ってたけど、スコープが狭いおかげで保守性が高いところはステキ。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux