やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

jinja2でテンプレファイルを作るのは苦行

pythonで書いたほうが早い気がしてくる。

問題

  • jinja2のマクロでは可変長引数が使えない
  • jinja2ではenumerate()でループカウンタを取得できない
  • jinja2で改行を削る-がウザい

jinja2をちょっと使っただけで上記の点に辟易とした。

jinja2のマクロでは可変長引数が使えない

リスト['A','B','C']を引数としたとき、A, B, Cのようにカンマ区切りテキストを出力するマクロを作りたいとする。

pythonの関数と同様に、以下のような構文でいけるだろうと思いきや、できない。

from_list.macro

{% macro To(*args) %}
{% for i, a in enumerate(args) %}{{ item }}{% if i+1 < len(args) %}, {% endif %}{% endfor %}
{% endmacro %}

test.jinja

{% import "csv/from_list.macro" as csv %}
{{ csv.To('A', 'BB', 'CCC') }}
{{ csv.To('D', 'EE', 'FFF') }}

test.py

from jinja2 import Template, Environment, FileSystemLoader
import pathlib

path_tpl = (pathlib.Path(__file__).parent.parent.parent.parent / 'res').resolve()
env = Environment(loader=FileSystemLoader(str(path_tpl )))
template = env.get_template('csv/test.jinja')
print(template.render())
$ python3 test.py
jinja2.exceptions.TemplateSyntaxError: expected token 'name', got '*'

name*が使われるのは想定外らしい。たぶんプログラミング言語の変数名と同様、[_a-zA-Z][_a-zA-Z0-9]*の書式のみ受け付けるのだろう。

python構文の関数なら引数名の先頭に*をつければ可変長引数になる。それと同じことを期待したのだが、jinja2のマクロではできないらしい。

jinja2ではenumerate()でループカウンタを取得できない

len(), enumerate()などpython組込関数が使えない。かわりにjinja2独自変数のloop.length, loop.index0を使う。

http://jinja.pocoo.org/docs/2.10/templates/#for

{%- macro To(args) %}
{%- for a in args -%}
{{ a }}{% if loop.index0+1 < loop.length %}, {% endif %}
{%- endfor %}
{%- endmacro %}

A, B, Cみたいにカンマ区切りのテキストを出力するマクロ。長い。汚い。

jinja2で改行を削る-がウザい

気づいただろうか。{%-, -%}のように-がついている箇所と、{%, %}のように-がついていない箇所があることに。

じつはこれ、前後の改行を消すマーク。これをうまいこと設置してやらないと出力結果が改行だらけになってしまう罠がある。

非常に面倒くさい。テンプレートをパッと見てもイメージできない。何度も出力結果を確認しながら、チマチマ修正する。まさに苦行。テンプレもテンプレで書きたくなってくる。あれ、逆に仕事増えてない?

こうすればできる

  • 変数名*argsでなくargsにする
  • len(), enumerate()関数は使えないのでjinja2専用変数を使う

from_list.macro

{%- macro To(args) %}
{%- for a in args -%}
{{ a }}{% if loop.index0+1 < loop.length %}, {% endif %}
{%- endfor %}
{%- endmacro %}
  • 上記のマクロ呼出。引数を渡す時に[]で囲んで配列にする。

test.jinja

{% import "csv/from_list.macro" as csv %}
{{ csv.To(['A', 'BB', 'CCC']) }}
{{ csv.To(['D', 'EE', 'FFF']) }}

出力結果は以下。

A, BB, CCC
D, EE, FFF

以上。コードの汚さと膨大なタイプ量にウンザリ。

Pythonなら簡単

もし、これをpythonコードで書くなら、たった1行で済む。

def ToCsv(*args): return ', '.join(list(args))
ToCsv('A','BB','CCC')
ToCsv('D','EE','FFF')

jinja2って、どう使うのが効果的?

string.Templateとかで十分では?

それ以上の機能を使うと、たちまちテンプレが汚くなる。制御文、include,, extends, macro を使えばDRYに書けて管理しやすくなるが、肝心のテンプレ自体が汚くなり、出力結果が見えなくなる。もはやテンプレの原型をとどめていない。テンプレと言えないグチャっとしたテキスト。異なる文脈や内容は分離したい。

もっと簡単に見やすくできないの? どう使えば効果的なの?

むしろ、テンプレという解決方法ってどうなの? ほかにどんな方法があって、何がベスト?

Pythonコードで書いたほうが早い? 一体、どこからがテンプレエンジンを使うべきところなのか。境界線がわからん。