読者です 読者をやめる 読者になる 読者になる

kamoというテンプレートエンジンを作ろうとしてみました

python kamo

kamoというテンプレートエンジンを作ってみました

kamo というテンプレートエンジンを作ってみました。まだ開発は途中なのですが、だいたい1日位でそれなりに動くテンプレートエンジンが作れるものなんだな〜と思ったりしました。文法的にはおおよそmako に似せていてmakoのsubsetのような感じです。

名前のkamoもアナグラムなのでてきとうに付けた名前です。

発端というか動機はあまり無く、なんとなく素朴にテンプレートエンジンが作りたくなり、1日でどれ位作れるのかちょっと気になったので、作ったという流れです。

以下のようなテンプレートが動きます。

<%doc>
cal.kamo
</%doc>
<%
import calendar
from datetime import date
month = ["","睦月","如月","弥生","卯月","皐月","水無月","文月","葉月","長月","神無月","霜月","師走"]

def paren(i):
    return "({})".format(i)
%>
# ${today.year}年
========================================

%for i in range(1, 13):
${i}月${month[i]|paren}
----------------------------------------

  %for d in range(1, calendar.monthrange(today.year, i)[1]):
    %if date(today.year, i,  d) <= today:
- ${d} ☓
    %else:
- ${d}
    %endif
  %endfor
%endfor

機能な話

機能は以下の様な最小限のもの。

  • makoのようにテンプレート中にpython codeを直接書ける機能がついています。
  • 条件分岐(if),ループ(for)の構文があります
  • コメントがあります
  • フィルター("${xxx|foo}"的なもの)があります
  • 実行する度にテンプレートを走査して実行せず、初回呼び出し時にpython code(関数)を生成してそれを利用します。

直接python codeを書ける部分は他のテンプレートエンジンとは異なるかもしれないです(これにより変数参照の管理をしないとダメになった)。 あと、最近のテンプレートエンジンと同じように、実行毎にテンプレートを走査して描画せずに、出力されたpython codeを呼び出す感じで実行されます。

使い方はだいたい他のテンプレートエンジンと同じ感じです。

from kamo import TemplateManager
from datetime import date

tm = TemplateManager(directories=["."])
tm.lookup("cal.kamo").redner(today=date.today())

以下の様な実行結果になります。

# 2015年
========================================
1月(睦月)
----------------------------------------
- 1 ☓
- 2 ☓
# skip ...
- 29 ☓
- 30 ☓
# skip
12月(師走)
----------------------------------------
- 1
- 2
- 3
# skip ...
- 28
- 29
- 30

内部的な話

ちなみに先ほどのテンプレートは内部的には以下のようなコードに変換されます。これはloggerのdebugオプションを付けると見ることができます。

def render(io, **c):
    write = io.write
    ########################################
    # cal.kamo
    ########################################

    import calendar
    from datetime import date
    month = ["","睦月","如月","弥生","卯月","皐月","水無月","文月","葉月","長月","神無月","霜月","師走"]
    def paren(i):
        return "({})".format(i)

    write('# ')
    write(str(c['today'].year))
    write('年\n========================================\n')
    for i in range(1, 13):
        write(str(i))
        write('月')
        write(str(paren(month[i])))
        write('\n----------------------------------------\n')
        for d in range(1, calendar.monthrange(c['today'].year, i)[1]):
            if (date(c['today'].year, i, d) <= c['today']):
                write('- ')
                write(str(d))
                write(' ☓\n')
            else:
                write('- ')
                write(str(d))
                write('\n')

わりと式のパースがだるくて、結局pythonのastモジュールの力を借りました。あと、python codeの生成は以前作っていたprestringを利用したら結構手軽に出来ました。