2012'03.03 (Sat)
小説 電子書籍
jingooでテンプレートをコンパイル
jingooはjinja2と同じくテンプレートをコンパイルすることも出来ます。
コンパイルする際には、デフォルトで/usr/local/bin にインストールされるコマンドラインコンパイラjingooを使って、
としてください。
ただし、このままだとインデントもなにもないソースが出力されるので、ちゃんとインデントを付けたい人はcamlp4を使って
みたいにすると良いでしょう。
というわけで、試しにjingooのexample/cheatsheet.tmplをコンパイルして、テンプレート出力のベンチマークを比較するとこんな感じでした。
これだけみると約2倍以上の差が出るのでコンパイルしたほうが良いように見えますが、どうせウェブサービスではDB処理がこれよりはるかに高いコストで足を引っ張るので、あんまり意味はない気がします。
お買い物に例えるなら、1010円の商品が1005円で買えた的な感じ。1000円単位のお買い物で、10円単位が半額になっても嬉しくないですよね。
ただし、データベース周りのボトルネックは何かしらの技術で解消されているので、テンプレートの出力部分も最適化したい場合、あるいはなんらかの事情でテンプレートのソースを隠蔽したい、という場合は利用価値があるかもしれないです。
コンパイルする際には、デフォルトで/usr/local/bin にインストールされるコマンドラインコンパイラjingooを使って、
jingoo -input cheatsheet.tmpl > cheatsheel.ml
としてください。
ただし、このままだとインデントもなにもないソースが出力されるので、ちゃんとインデントを付けたい人はcamlp4を使って
jingoo -input cheatshee.tmpl > tmp.ml
camlp4 pr_o.cmo pa_o.cmo tmp.ml > cheatsheet.ml
みたいにすると良いでしょう。
というわけで、試しにjingooのexample/cheatsheet.tmplをコンパイルして、テンプレート出力のベンチマークを比較するとこんな感じでした。
| 回数 | コンパイルあり | コンパイルなし |
|---|---|---|
| 1000 | 2.3 sec | 5.19 sec |
| 2000 | 4.6 sec | 10.29 sec |
| 4000 | 9.11 sec | 20.34 sec |
これだけみると約2倍以上の差が出るのでコンパイルしたほうが良いように見えますが、どうせウェブサービスではDB処理がこれよりはるかに高いコストで足を引っ張るので、あんまり意味はない気がします。
お買い物に例えるなら、1010円の商品が1005円で買えた的な感じ。1000円単位のお買い物で、10円単位が半額になっても嬉しくないですよね。
ただし、データベース周りのボトルネックは何かしらの技術で解消されているので、テンプレートの出力部分も最適化したい場合、あるいはなんらかの事情でテンプレートのソースを隠蔽したい、という場合は利用価値があるかもしれないです。
2012'02.23 (Thu)
小説 電子書籍
jinja2互換のocaml製template engine 「jingoo」の紹介
先日公開したjinja2クローンのOcaml製テンプレートエンジン「jingoo」ですが、jinja2のことを知らない人のために、ちょっとしたgetting started & cheatsheet的な捕捉です。
より詳しいシンタックスを知りたい人は、せっかく日本語訳を書いてくれた人もいることですし、jinja2のドキュメントを読むことをあわせてお勧めします。
またテンプレートをコンパイルする方法については、exampleディレクトリを参照してください。
テンプレートの呼び出し
テンプレートパスを指定する場合は、環境変数を指定します。全変数を指定すると面倒なんで、標準の環境変数レコード(std_env)の一部を書き換えて指定するとこんな感じです。
これで、/path/to/templates/hello.html を発見できるようになりました。何にも指定しない場合はカレントディレクトリからhello.htmlを探します。
続いてテンプレートのシンタックスですが、ちょっとした例外はありますが基本的にjinja2と一緒です。
なおコード中の{# 〜 #} はコメントになります。
テンプレート変数の展開
テンプレートに割り当てた変数を展開します。
算術計算
上から加算、減算、乗算、除算、剰余、累乗です。
テンプレート内部からの変数セット
テンプレート内部で変数をセットする場合は set構文を使います。
ループ
ループです。イテレーションは、python流に for 〜 in で指定します。ここではrange関数を使って0〜10(長さ11)のリストを作ってその中身をイテレートします。
またループ中では、特殊変数「loop」が使用できます。
loop.cycleは引数の中身を循環して吐き出す関数です(サンプル内のcycleは、1,2,3,1...と出力します)。
条件分岐
{% if 〜 %}, {% elseif 〜 %}, {% else %} で分岐を指定します。
テンプレートのインクルード
デフォルトでは呼び出し元のコンテキストがインクルード先のテンプレートに引き継がれます。
引き継ぎたくない場合は without contextを指定します。
テンプレートの継承
多分この機能こそがjinja2(あるいはdjango)が広く使われる理由だと思います。
例えばサイトに共通したフォーマットを次のような感じで記述し、base.htmlとして保存したとします。
中に「block main」と「block sidemenu」という記述がありますね。でも中身は空です。
でも、このベーステンプレートをextends構文で継承すると、このmainブロックと、sidemenuブロックの中身「だけ」を記述し、残りはベーステンプレートの内容をそのまま使う……といったことが出来るのです!
これは本当に楽なので、一度経験するとこの機能がないテンプレートエンジンは使いたくなくなるほどです。
マクロ
マクロは少しややこしいですが、後に説明するcallという機能を使わない場合は単純です。
で、ここから少しややこしいですが、マクロの中身からはcallerというマクロが呼べるんです。
例えばこんな風に。
こんな感じになりますね。
で、実はsay_word2マクロを単純に読んでもcaller()の部分では何も表示されないのですが、callという構文から呼ぶと、このcaller()の中身を指定させながらsay_word2マクロを呼び出すことが出来るんです。つまり、
とか呼ぶと
って表示されます。caller()の部分が、{% call %} 〜 {% endcall %} の中身に記述された部分(this is text via call!)になってますね。
で、さらにさらにややこしいのですが、callは引数をとることも出来ます。つまり次のように元のマクロのcaller()の部分を
のように書き換えつつ、
などと呼ぶと、
と表示されます。
これ、どういう場面で使うかって言うとちょっと長くなりそうだから省略しますが、マクロをバリバリ書いてるうちに、すぐにピンと来ると思います。
マクロインポート
マクロははインクルードして使うこともできますが、インポートして名前空間を割り当てる事もできます。
こんな感じ。pythonに慣れた人ならお馴染みのシンタックスだと思います。
局所スコープ
{% with %} 〜 {% endwith %} 構文を使うと、局所スコープを作る事が出来ます。
withの外にある二番目の {{hoge}} はundefinedです。
ちなみに withは引数をとらなくてもよくて、
って書いても同じです。
raw構文
テンプレートで使用するシンタックスを展開しないでそのまま表示したい時は{% raw %} 〜 {% endraw %}を使用します。
こう書くと value に設定された値でなく、そのまま {{ value }} と表示されます。
オートエスケープの有効化、無効化
全てのテンプレート変数や展開した文字列は自動でエスケープされる設定になっていますが、これを部分的に無効にしたり有効にしたりするブロックを作る事が出来ます。
自動エスケープはデフォルトで有効になっていますが、これを無効にしたい時はテンプレートを読み込むときに与える環境変数レコードの中でautoescapeフィールドを無効にします。
色んなヘルパー関数
jinja2のヘルパー関数と標準フィルタ、テスト関数はほぼ全てサポートされています。
例えば
みたいなやつです。
ただ内部のカリー化と型の一貫性の都合で、キーワード引数のキーワード名を省略する表記は出来ません。
例えば指定した長さでリストをスライスし、あまった分はfill_withキーワードで指定できるsplice関数ですが、
は list list で [[1,2,3], [4,0,0]] を返しますが、これと同じものを
と書いてもfill_withキーワードがないので余った領域が0で埋められずに、[[1,2,3], [4]] が返ってきます。
その他諸々
えっと、長くなってきたのでこの辺にしときますが、他にも
{{ some_value is defined }}
みたいな is 構文である変数が定義されているかを知る事が出来たり、
{{ eval("{% set hoge = 'hoge' %}") }}
みたいな荒業(これはjingooオンリー)もありますが、その辺はjingooのexampleフォルダとかtestsフォルダとかを見つつ確認してみてください。
現在はgithubで公開しているのですが、バグ報告やらforkなどは大歓迎です。
ただ僕個人のgitの使い方がまだ怪しいんで、フィードバックの反映は遅いかもです。ご容赦ください。
より詳しいシンタックスを知りたい人は、せっかく日本語訳を書いてくれた人もいることですし、jinja2のドキュメントを読むことをあわせてお勧めします。
またテンプレートをコンパイルする方法については、exampleディレクトリを参照してください。
テンプレートの呼び出し
open Jg_types
Jg_template.from_file "hello.html" ~models:[
("msg", Tstr "hello, jingoo!");
]
テンプレートパスを指定する場合は、環境変数を指定します。全変数を指定すると面倒なんで、標準の環境変数レコード(std_env)の一部を書き換えて指定するとこんな感じです。
open Jg_types
Jg_template.from_file "hello.html"
~env:{std_env with template_dirs = ["/path/to/templates"]}
~models:[
("msg", Tstr "hello, jingoo!");
]
これで、/path/to/templates/hello.html を発見できるようになりました。何にも指定しない場合はカレントディレクトリからhello.htmlを探します。
続いてテンプレートのシンタックスですが、ちょっとした例外はありますが基本的にjinja2と一緒です。
なおコード中の{# 〜 #} はコメントになります。
テンプレート変数の展開
テンプレートに割り当てた変数を展開します。
{# 単純な展開 #}
{{ msg }}
{# エスケープしないで展開 #}
{{ msg|safe }}
{# 展開後にフィルタ関数upperを使って大文字に #}
{{ msg|upper }}
算術計算
上から加算、減算、乗算、除算、剰余、累乗です。
{{ 1 + 1 }}
{{ 1 - 1 }}
{{ 2 * 2 }}
{{ 4 / 2 }}
{{ 4 % 3 }}
{{ 2 ** 3 }}
テンプレート内部からの変数セット
テンプレート内部で変数をセットする場合は set構文を使います。
{% set pi = 3.14 %}
ループ
ループです。イテレーションは、python流に for 〜 in で指定します。ここではrange関数を使って0〜10(長さ11)のリストを作ってその中身をイテレートします。
またループ中では、特殊変数「loop」が使用できます。
loop.cycleは引数の中身を循環して吐き出す関数です(サンプル内のcycleは、1,2,3,1...と出力します)。
{% for x in range(0,10) %}
x = {{x}}
1から始まるindex = {{ loop.index }}
0から始まるindex = {{ loop.index0 }}
1で終わる逆方向index = {{ loop.revindex }}
0で終わる逆方向index = {{ loop.revindex0 }}
ループの長さ = {{ loop.length }}
cycle = {{ loop.cycle(1,2,3) }}
{% endfor %}
条件分岐
{% if 〜 %}, {% elseif 〜 %}, {% else %} で分岐を指定します。
{% if msg == "hoge" %}
yes, msg is hoge
{% elseif msg == "hige" %}
msg is {{ msg }}
{% else %}
oh msg is {{ msg }}
{% endif %}
テンプレートのインクルード
{% include "table.html" %}
{% include "table.html" with context %}
{% include "table.html" without context %}
デフォルトでは呼び出し元のコンテキストがインクルード先のテンプレートに引き継がれます。
引き継ぎたくない場合は without contextを指定します。
テンプレートの継承
多分この機能こそがjinja2(あるいはdjango)が広く使われる理由だと思います。
例えばサイトに共通したフォーマットを次のような感じで記述し、base.htmlとして保存したとします。
<html>
<head>
<省略>
</head>
<body>
<div class="main">
{% block main %}{% endblock %}
</div>
<div class="sidemenu">
{% block sidemenu %}{% endblock %}
</div>
</body>
</html>
中に「block main」と「block sidemenu」という記述がありますね。でも中身は空です。
でも、このベーステンプレートをextends構文で継承すると、このmainブロックと、sidemenuブロックの中身「だけ」を記述し、残りはベーステンプレートの内容をそのまま使う……といったことが出来るのです!
{% extends "base.html" %}
{% block main %}
this is main block
{% endblock %}
{% block sidemenu %}
this is side menu
{% endblock %}
これは本当に楽なので、一度経験するとこの機能がないテンプレートエンジンは使いたくなくなるほどです。
マクロ
マクロは少しややこしいですが、後に説明するcallという機能を使わない場合は単純です。
{# 単純なマクロ #}
{% macro say_word2(x,y) %}
I say {{x}} and {{y}}.
{% endmacro %}
{# マクロ呼び出し #}
{{ say_word2("hello", "world!") }}
で、ここから少しややこしいですが、マクロの中身からはcallerというマクロが呼べるんです。
例えばこんな風に。
{% macro say_word2(x,y) %}
I say {{x}} and {{y}}.
{{ caller() }}
{% endmacro %}
こんな感じになりますね。
で、実はsay_word2マクロを単純に読んでもcaller()の部分では何も表示されないのですが、callという構文から呼ぶと、このcaller()の中身を指定させながらsay_word2マクロを呼び出すことが出来るんです。つまり、
{% call say_word2("hello","world") %}
this is text via call!
{% endcall %}
とか呼ぶと
I say hello and world.
this is text via call!
って表示されます。caller()の部分が、{% call %} 〜 {% endcall %} の中身に記述された部分(this is text via call!)になってますね。
で、さらにさらにややこしいのですが、callは引数をとることも出来ます。つまり次のように元のマクロのcaller()の部分を
{% macro say_word2(x,y) %}
I say {{x}} and {{y}}.
{# 引数つきでcallerを呼ぶ #}
{{ caller(10,20) }}
{% endmacro %}
のように書き換えつつ、
{% call(x,y) say_word2("hello", "world") %}
this is text via call!
arg from say_word2 is {{x}}, {{y}}.
{% endcall %}
などと呼ぶと、
I say hello and world.
this is text via call!
arg from say_word2 is 10, 20.
と表示されます。
これ、どういう場面で使うかって言うとちょっと長くなりそうだから省略しますが、マクロをバリバリ書いてるうちに、すぐにピンと来ると思います。
マクロインポート
マクロははインクルードして使うこともできますが、インポートして名前空間を割り当てる事もできます。
こんな感じ。pythonに慣れた人ならお馴染みのシンタックスだと思います。
{# そのままインポート #}
{% import "my_form_macro.html" %}
{# 名前空間付きでインポート #}
{% import "my_form_macro.html" as form_macro %}
{# 名前を指定して特定のものだけインポート #}
{% from "my_macro.html" import hoge_macro, hige_macro %}
{# 名前を指定し、別名をつけてインポート #}
{% from "my_macro.html" import hoge_macro as hoge, hige_macro as hige %}
{{ form_macro.input("name", "no name") }}
{{ hoge_macro("hello", "world!" }}
{{ hige("hello", "jingoo") }}
局所スコープ
{% with %} 〜 {% endwith %} 構文を使うと、局所スコープを作る事が出来ます。
{% with hoge = "hoge" %}
in scope, hoge is {{ hoge }}.
{% endwith %}
out of scope, hoge is {{ hoge }}.
withの外にある二番目の {{hoge}} はundefinedです。
ちなみに withは引数をとらなくてもよくて、
{% with %}
{% set hoge = "hoge" %}
in scope, hoge is {{ hoge }}.
{% endwith %}
out of scope, hoge is {{ hoge }}.
って書いても同じです。
raw構文
テンプレートで使用するシンタックスを展開しないでそのまま表示したい時は{% raw %} 〜 {% endraw %}を使用します。
{% raw %}
{{ value }}
{% endraw %}
こう書くと value に設定された値でなく、そのまま {{ value }} と表示されます。
オートエスケープの有効化、無効化
全てのテンプレート変数や展開した文字列は自動でエスケープされる設定になっていますが、これを部分的に無効にしたり有効にしたりするブロックを作る事が出来ます。
{% set value = "<script>alert(1)</script>" %}
{% autoescape false %}
{# value はエスケープされない #}
{{ value }}
{% endautoescape %}
{# value はエスケープされる #}
{{ value }}
自動エスケープはデフォルトで有効になっていますが、これを無効にしたい時はテンプレートを読み込むときに与える環境変数レコードの中でautoescapeフィールドを無効にします。
open Jg_types
Jg_template.from_file "hello.html"
~env:{
std_env with
template_dirs = ["/path/to/templates"];
autoescape = false;
}
~models:[
("msg", Tstr "hello, jingoo!");
]
色んなヘルパー関数
jinja2のヘルパー関数と標準フィルタ、テスト関数はほぼ全てサポートされています。
例えば
{# 3と表示される #}
{{ "日本語"|length }}
{{ length("日本語") }}
{# [1,2,3]が返ってくる #}
{{ "1,2,3"|split(",") }}
{{ split(",", "1,2,3" }}
みたいなやつです。
ただ内部のカリー化と型の一貫性の都合で、キーワード引数のキーワード名を省略する表記は出来ません。
例えば指定した長さでリストをスライスし、あまった分はfill_withキーワードで指定できるsplice関数ですが、
{{ splice(3,[1,2,3,4], fill_with=0) }}
は list list で [[1,2,3], [4,0,0]] を返しますが、これと同じものを
{{ splice(3,[1,2,3,4], 0) }}
と書いてもfill_withキーワードがないので余った領域が0で埋められずに、[[1,2,3], [4]] が返ってきます。
その他諸々
えっと、長くなってきたのでこの辺にしときますが、他にも
{{ some_value is defined }}
みたいな is 構文である変数が定義されているかを知る事が出来たり、
{{ eval("{% set hoge = 'hoge' %}") }}
みたいな荒業(これはjingooオンリー)もありますが、その辺はjingooのexampleフォルダとかtestsフォルダとかを見つつ確認してみてください。
現在はgithubで公開しているのですが、バグ報告やらforkなどは大歓迎です。
ただ僕個人のgitの使い方がまだ怪しいんで、フィードバックの反映は遅いかもです。ご容赦ください。
2012'02.18 (Sat)
小説 電子書籍
jingoo - ocaml template engine compatible with jinja2
縦書き文庫をリニューアルする際に開発したテンプレートエンジン「jingoo」をgithubに公開しました。ライセンスはGPLv3です。
jingoo はOcaml製のテンプレートエンジンで、jinja2と全く同じシンタックスで、ほぼ全ての機能をクローンしています。
去年あたりにjinja2をgaeで使っていて本当に便利だなあと思ったので「同じものがOcamlにも欲しい!」と思って作りました。
一応、テンプレートをOCamlコードにコンパイルすることも出来ます。
ちなみにコンパイルして動かした場合、まだ単純な比較しかしてませんが、おおよそ二倍速くなりました。
が、ゼロコンマの世界で二倍になってもどうせDBの処理時間でチャラなのであんまり意味は無いと思います。
ただテンプレートの内容は非公開にしたい、みたいなケースでは使えるかもしれません。
jingoo はOcaml製のテンプレートエンジンで、jinja2と全く同じシンタックスで、ほぼ全ての機能をクローンしています。
去年あたりにjinja2をgaeで使っていて本当に便利だなあと思ったので「同じものがOcamlにも欲しい!」と思って作りました。
一応、テンプレートをOCamlコードにコンパイルすることも出来ます。
ちなみにコンパイルして動かした場合、まだ単純な比較しかしてませんが、おおよそ二倍速くなりました。
が、ゼロコンマの世界で二倍になってもどうせDBの処理時間でチャラなのであんまり意味は無いと思います。
ただテンプレートの内容は非公開にしたい、みたいなケースでは使えるかもしれません。

