電動ひやむぎ

2012-12-23

CL-HAML

このエントリーをはてなブックマークに追加

(このエントリは Common Lisp Libraries Advent Calendar 2012 23日目の記事です。)

@nitro_idiotさんによる、テンプレートエンジン連続紹介に続いて またテンプレートエンジンのご紹介です。

今日はCL-HAMLというテンプレートエンジンを紹介します。僕が作りました。

https://github.com/Unspeakable/cl-haml

特徴

CL-HAMLは RubyのHamlを Common Lispで実現しようと 僕が作りました。 Pythonのようにインデントによって階層を表現します。個人的には結構簡潔な記法で好きです。ちなみに僕は本家HAMLを 使ったことがありません

HAMLファイルを読み込んでS式に変換し、それをCL-WHOに渡して関数化しているため、生成されるコードはそこそこ高速な気がします。

以下のコードは……

%table{:border "0" :cellpadding "4"}  
  - loop for i below 25 by 5 do  
    %tr{:align "right"}  
      - loop for j from i below (+ i 5) do  
        %td{:bgcolor (if (oddp j) "pink" "green")}= (format nil "~@R" (1+ j)) 

以下のようなコードとして解釈します。

(CL-WHO:WITH-HTML-OUTPUT-TO-STRING (out NIL :PROLOGUE "" :INDENT NIL)  
  (:|table| :|border| "0" :|cellpadding| "4"  
    (LOOP FOR I BELOW 25 BY 5 DO (CL-WHO:HTM  
      (:|tr| :|align| "right"  
        (LOOP FOR J FROM I BELOW (+ I 5) DO (CL-WHO:HTM  
          (:|td| :|bgcolor| (IF (ODDP J)  
                                "pink"  
                                "green")  
            (CL-WHO:STR (FORMAT NIL "~@R" (1+ J))))))))))) 

これは @nitro_idiotさんの CL-WHOの紹介にあったコードとほぼ同じでして、つまり結構高速なコードが生成されることが期待出来ますね。

同じファイルを指定したときに、ファイルのタイムスタンプが前回と同じ場合には 前回作った関数を呼び出すようになっているため、2回目以降の呼び出し時には変換のコストがかからないようにしてあります。よって、変換にかかる時間もあまり大きな問題にはならないんじゃないかなーと思ってます。

タイムスタンプが変わっていれば、自動的に変換しますので、明示的にコンパイルする必要はありません。デザインの調整などしているときには、こういうちょっとした差が嬉しかったりしませんか。僕は嬉しいです。

HTMLの自動エスケープですが、デフォルトでは しない設定にしています。cl-haml:*escape-html*という変数を tにしておけば自動エスケープするようになります。これは本家HAMLに合わせようかなーと思った結果です。

インストール

quicklispでインストールすることが出来るのですが、何と最新の状況では 実行時にエラーになることが、先ほど判明しました。なので次回のquicklisp更新までは 上に書いたgithubのリポジトリから直接ソースを取得して欲しいです。

とりあえず、quicklispでは

(ql:quicklisp :cl-haml) 

でインストール出来ます。CL-WHOの内部関数を使っていた部分が その内部関数の消滅により動かなくなっています。

使い方

execute-hamlという関数に、テンプレートファイルのパスネームと、:envでProperty Listで引数を渡すことでテンプレート内で変数として使えます。テンプレート内では、@$で始まったシンボルを変数として認識するようにしていた気がします(記憶があいまい)。

%h1= @name 

という内容のテンプレートを ~/sample1.hamlに保存している場合、

(cl-haml:execute-haml (merge-pathnames "sample1.haml"  
                                       (user-homedir-pathname))  
                      :env '(:name "inuzini-jiro"))  
;=> "<h1>inuzini-jiro</h1>" 

ループも出来ます。が、CL-EMBのように dolist的なListに対するループをしても、ループ内でListの中身を変数が自動的に参照するような賢い機能は(今のところ)付いていません。-で始まる行は Common Lispのコードとして解釈されます。

%ul  
  - dolist (man @people)  
    %li= (getf man :name)  
 
(cl-haml:execute-haml (merge-pathnames "sample2.haml"  
                                       (user-homedir-pathname))  
                      :env '(:people ((:name "inuzini-jiro")  
                                      (:name "inuzini-taro"))))  
;=> "<ul><li>inuzini-jiro</li><li>inuzini-taro</li></ul>" 

コードの埋め込みももちろん出来ます。

%ul  
  - dolist (man @people)  
    %li{:data-age (format nil "~X" (getf man :age))}= (getf man :name)  
 
(cl-haml:execute-haml (merge-pathnames "sample2.haml"  
                                       (user-homedir-pathname))  
                      :env '(:people ((:name "inuzini-jiro" :age 28)  
                                      (:name "inuzini-taro" :age 30))))  
;=> "<ul><li data-age='1C'>inuzini-jiro</li><li data-age='1E'>inuzini-taro</li></ul>" 

良いところ

  • テンプレートを変更してもコンパイル不要
  • Ruby界にはHamlをガリガリ書けるデザイナがいる

微妙なところ

  • いまのところ、改行のコントロールを提供していない、改行はなし
  • 拡張性が低いっていうか、そもそもの機能に不足感が…
  • 黙ってたけど、ifの書き方が相当キツい
  • デザイナが~とか言っておきながら、ちょっとしたことをしようと思ったらCLの関数呼ばないといけない
  • 作ってる本人が、動かないことに気付いてないことがある

おわりに

nitro_idiotさんのCL-EMBの紹介を丸パクリして書いた今回のエントリー。すみません、ほんとすみません。

CL-HAMLは他のテンプレートエンジンと比較すると、正直 完成度がイマイチです。作ってる本人が、自分のブログを作るときには使用してない辺り、困ったものです。

でも、結構面白いテンプレートエンジンだと勝手に思っています。改善したいのですが、なかなかスキルが足りないのと改善のアイディアが無いところが辛いところ。僕自身が一番好きなテンプレートエンジンであることは確かです(なぜか自分のブログに使ってないけど)。