Clojureでシンプルなテンプレートエンジンを作りました
cuma: Extensible micro template engine for Clojure
動機†
もともと、misakiなどではclostacheというテンプレートエンジンを使ってました。
これはmustacheのClojure版で、これはこれでとても素晴らしいものなのですが misaki-markdown を開発する上で 以下2つの問題にぶつかりました以下2つの問題にぶつかりました
- 置き換える文字列中にテンプレートの表記があると正しく置き換えられない
- 機能拡張ができない
前者については送ったpull-requestがマージされたので解消されていますが、後者についてはそもそもmustacheが Logic-less templates を謳っているので 方向性が違うのかと思います。
そこでシンプルなフォーマットを保ちつつ、clojureで機能拡張できるものとしてcumaを作りました。
なぜ「cuma」なのかというと、別に自分がクマ好きでカバンとか携帯にクマのぬいぐるみを付けてるからとかそういうのではなく 'CloUreのteMplAte'からテキトーに文字をピックアップしただけです。偶然です。
特徴†
Clostacheと同じことは同じようにできます。インラインでは$(...)、複数行にまたがるものは@(...) ... @(/...) というフォーマットになっています。他の例はgithubのページを参照してください。
(render "$(x)" {:x "hello"})
;=> hello
(render "$(escape x)" {:x "<h1>hello</h1>"})
;=> <h1>hello</h1>
(render "@(if flag) hello @(/if)" {:flag true})
;=> hello
(render "@(for arr) $(.) @(/for)" {:arr [1 2 3]})
;=> 1 2 3拡張機能†
cumaでは上記フォーマットを保ったまま機能拡張することができます。
具体的には上記の escape, if, for にあたるものを自分で作ることができますし、
escape, if, for 自体も本体に組み込まれていますが拡張機能として実装されています。
cumaでは実行時に cuma.extension.* という名前空間を検索し、その中のpublicな関数を
拡張機能としてロードし、テンプレート内で使うことができます。
(ns cuma.extension.hello) (defn hello [data s] (str "hello " s))
(render "$(hello x)" {:x "world"})
;=> hello worldパフォーマンス†
シンプルなテンプレートなのに動作が遅かったら論外かと思います。cumaでは、misaki, misaki-markdownで使っているclostacheをcumaに移行することを考え、clostacheより高速に動作するようにしています。

なお計測に使ったコードはgistに貼り付けておきました。正確でない点があればご指摘いただければと思います。
最後に†
自分用に作ったので小規模向けではありますが、フォーマットはシンプル、かつ拡張機能による柔軟性を意識して作ったので、そこそこ使いやすいのではないかなと思っています。 ご意見、要望などあればgithubのissueなりtwitterなりでご連絡ください(・(ェ)・)
