PasteScriptのテンプレートエンジンにJinja2を使う


## PasteScript

[PasteScript](http://pythonpaste.org/script/)は以下の2つの機能を提供するコマンドラインツール.今回話題とするのは`paster create`の方.

- 指定したテンプレート(自作可能)を元に、プロジェクトの雛形を作成する(`paster create`)
- [PasteDeploy](http://pythonpaste.org/deploy/)により設定されたWSGIアプリケーション(WSGIサーバ、関連ミドルウェアの設定含む)を起動する(`paster serve`)

長らくPython3に対応していないのがネックだったが、先月リリースしたPasteScript 2.0によりついにPython3がサポートされた。

## `paster create`コマンド

以下は、同梱されている`basic_package`テンプレートを指定した例である。`basic_package`テンプレートはsetup.pyを含んだPythonパッケージプロジェクトを生成する。


```bash
$ paster create -t basic_package test
Selected and implied templates:
  PasteScript#basic_package  A basic setuptools-enabled package

Variables:
  egg:      test
  package:  test
  project:  test
Enter version (Version (like 0.1)) ['']: 0.1
Enter description (One-line description of the package) ['']: test
Enter long_description (Multi-line description (in reST)) ['']: test
Enter keywords (Space-separated keywords/tags) ['']: python
Enter author (Author name) ['']: FGtatsuro
Enter author_email (Author email) ['']:
Enter url (URL of homepage) ['']:
Enter license_name (License name) ['']:
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]:
Creating template basic_package
Creating directory ./test
  Recursing into +package+
    Creating ./test/test/
    Copying __init__.py to ./test/test/__init__.py
    Recursing into __pycache__
      Creating ./test/test/__pycache__/
  Copying setup.cfg to ./test/setup.cfg
  Copying setup.py_tmpl to ./test/setup.py
Running /Users/tatsuro/.homesick/repos/dotfiles/home/.virtualenvs/flask-boilerplate/bin/python setup.py egg_info
# setup.pyを含むPythonパッケージプロジェクト
$ ls test
setup.cfg     setup.py      test          test.egg-info
```

指定するテンプレートは自作できる。
詳細については説明しない([ドキュメント](http://pythonpaste.org/script/developer.html#templates)参照)がおおまかな流れは以下の通り。

1. テンプレートを定義するPythonパッケージプロジェクトを作成する。
2. `paste.script.templates.Template`クラスを継承したクラス(以下、カスタムテンプレート)を作成する。
3. 1のプロジェクトの`setup.py`のエントリポイントから、2で定義したクラスを指定する。

## デフォルトで提供されるテンプレートエンジン

雛形生成時に与える引数により、指定したテンプレートのファイル名や内容をカスタマイズできる。

```bash
# 対話式に与えた引数が反映されている(ex. version, description)
$ cat test/setup.py
from setuptools import setup, find_packages
import sys, os

version = '0.1'

setup(name='test',
      version=version,
      description="test",
      long_description="""\
test""",
      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
      keywords='python',
      author='FGtatsuro',
      ...
      )
```

この際、内容の書き変えにテンプレートエンジンが用いられる。使用するテンプレートエンジンはドキュメントを見ると以下の通り。

1. カスタムテンプレートの`use_cheetah`属性がTrueの場合、[Cheetah](https://github.com/cheetahtemplate/cheetah)が使用される。
2. それ以外の場合、[string.Template](https://docs.python.org/2/library/string.html#template-strings)が使用される。

## 他のテンプレートエンジンを使用する

この2つのどちらかでも悪くはないが、自分の使い慣れているテンプレートエンジンを使ってカスタムテンプレートを作成したいという要求もあるだろう。
個人的には多少使い慣れている[Jinja2](https://github.com/mitsuhiko/jinja2)が使いたかった。

カスタムテンプレートの`template_renderer`属性に一定の条件を満たすstaticメソッドを紐付けることで上記の要望が実現できる。以下はテンプレートエンジンに`Jinja2`を使用した例である。

```python:
# https://github.com/FGtatsuro/flask-boilerplate/blob/master/flask_boilerplate/templates.py
class Boilerplate(Template):
    _template_dir = 'templates'
    summary = 'A boilerplate for Flask project'
    required_templates = []
    vars = [
        var('app_name', 'Flask application name', default=NoDefault),
        var('description', 'One-line description of the package'),
        var('author', 'Author name'),
        var('author_email', 'Author email'),
    ]
    @staticmethod
    def template_renderer(body, context, filename=None):
        return Jinja2Template(body).render(context)
    ...
```

staticメソッドが満たさなければいけない条件は下記の通り。

- 書き換え対象文字列を第一引数に、書き換えに使用される変数の辞書を第二引数に、置き変え対象のファイル名を第三引数に取る。なおファイル名が必要ないならば、ファイル名のデフォルトは`None`でかまわない。
- 書き換え後の文字列をreturnする。テンプレートエンジンを使って文字列を書き換えてreturnするだけでOKのケースがほとんどだろう。

今回の例は`Jinja2`だが、上記の条件を満たすことができる、ようは与えられた3つの引数から書き換え後の文字列を生成できるテンプレートエンジンであれば同様に使用できる。
他のエンジンは未確認のため断言できないが、大多数のテンプレートエンジンは大丈夫ではないか。