CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: Cookiecutter でプロジェクトのテンプレートを作る

Cookiecutter は Python で実装されたプロジェクトのテンプレートを作るためのツール。 似たようなツールだと JavaScript の Yeoman とか、Python だと他にも Paste Script なんかがある。 Python で実装されているとは言ってもテンプレートを作る・使うところまでなら Python が書けなくても問題ない。

今回使う環境は Mac OS X にした。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.2
BuildVersion:   15C50

インストールする

Python のパッケージマネージャの pip を使ってインストールする。

$ pip install cookiecutter

とりあえず使ってみる

いくつかサンプルのテンプレートがあるので、それを使ってプロジェクトを作ってみよう。

ここで紹介するテンプレートは Python のパッケージを作るときに使われるもの。 Git でリポジトリをクローンしてこよう。

$ git clone https://github.com/audreyr/cookiecutter-pypackage.git

cookiecutter コマンドを使って先ほどクローンしてきたディレクトリを指定しよう。 すると、テンプレートで変数になっている部分に何を入れるか次々に聞かれる。 変数にはデフォルト値が設定されているので、ひとまず今はエンターキーを連打しているだけでも構わない。

$ cookiecutter cookiecutter-pypackage
full_name [Audrey Roy Greenfeld]:
email [aroy@alum.mit.edu]:
github_username [audreyr]:
project_name [Python Boilerplate]:
project_slug [python_boilerplate]:
project_short_description [Python Boilerplate contains all the boilerplate you need to create a Python package.]:
release_date [2015-12-13]:
pypi_username [audreyr]:
year [2015]:
version [0.1.0]:
use_pypi_deployment_with_travis [y]:

コマンドの実行が終わると、次のようなディレクトリツリーができあがった。 いくつかのファイル名には先ほど入力した変数の値が使われていることも分かる。

$ tree python_boilerplate
python_boilerplate
├── AUTHORS.rst
├── CONTRIBUTING.rst
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
│   ├── Makefile
│   ├── authors.rst
│   ├── conf.py
│   ├── contributing.rst
│   ├── history.rst
│   ├── index.rst
│   ├── installation.rst
│   ├── make.bat
│   ├── readme.rst
│   └── usage.rst
├── python_boilerplate
│   ├── __init__.py
│   └── python_boilerplate.py
├── requirements_dev.txt
├── setup.cfg
├── setup.py
├── tests
│   ├── __init__.py
│   └── test_python_boilerplate.py
├── tox.ini
└── travis_pypi_setup.py

3 directories, 26 files

テンプレートを自作する

大体の使い方がつかめたので、次はテンプレートを自作してみることにしよう。 まずは、ごくごくシンプルな Python パッケージを作ってみることにする。

テンプレートは example というディレクトリの中に作ることにする。 まずは example ディレクトリを作って、その中にさらに二重の波括弧で囲まれた名前をもったディレクトリを作る。 二重の波括弧で囲まれて cookiecutter. から始まる名前を持ったファイルは Cookiecutter で置換対象になる。 cookiecutter. に続く名前が先ほど入力した変数名になる。

$ mkdir -p example/{{cookiecutter.package_name}}

作るのが Python のパッケージのつもりなので、ディレクトリの中に init.py モジュールも用意しておく。

$ touch example/{{cookiecutter.package_name}}/__init__.py

次に example ディレクトリの中に cookiecutter.json という名前のファイルを用意しよう。 ここで変数名と、それに対応するデフォルト値を定義する。

$ cat << 'EOF' > example/cookiecutter.json
{
    "package_name": "helloworld"
}
EOF

作ったテンプレートを使ってみる

これで Cookiecutter 用のテンプレートの準備ができた。 cookiecutter コマンドで作成したテンプレートである example ディレクトリを指定しよう。 すると先ほどと同じように変数名について聞かれる。

$ cookiecutter example
package_name [helloworld]:

コマンドの実行がおわると先ほど入力した変数名でディレクトリが作られる。

$ tree helloworld
helloworld
└── __init__.py

0 directories, 1 file

ばっちりだ。

ファイルの中身を置換する

先ほどの例ではディレクトリ名が入力した変数で置換された。 次はファイルの中身も置換されることを確認しておこう。

先ほどの example テンプレートに新しい Python モジュール (のテンプレート) を追加する。 その中にファイル名と同様、二重の波括弧で囲まれて cookiecutter. から始まる変数を埋め込んでおく。 変数の名前は cookiecutter.message にした。

$ cat << 'EOF' > example/{{cookiecutter.package_name}}/introduction.py
# -*- coding: utf-8 -*-


def greet():
    msg = '{{ cookiecutter.message }}'
    print(msg)


if __name__ == '__main__':
    main()
EOF

変数が追加されたので、それを定義する設定ファイルも更新しておく。

$ cat << 'EOF' > example/cookiecutter.json
{
    "package_name": "helloworld",
    "message": "Hello, World!"
}
EOF

再度 cookiecutter コマンドを実行しよう。 追加した変数について質問が増えている。

$ cookiecutter example
package_name [helloworld]: helloworld2
message [Hello, World!]:

追加されたファイルもちゃんと展開されていることがわかる。

$ tree helloworld2
helloworld2
├── __init__.py
└── introduction.py

0 directories, 2 files

ファイルの中身を確認すると、ちゃんと変数が先ほど入力した値で置換されている。

$ cat helloworld2/introduction.py
# -*- coding: utf-8 -*-


def greet():
    msg = 'Hello, World!'
    print(msg)


if __name__ == '__main__':
    main()

Python パッケージに組み込んで使う

ちょっと特殊な使い方だけど Python パッケージにテンプレートを組み込んで使うやり方も紹介しておく。

まずはパッケージをインストールするためのセットアップスクリプトを用意する。 パッケージ名は mycutter にする。 console_scripts に登録した cutter コマンドでテンプレートを展開できるようにすることを意図してる。

$ cat << 'EOF' > setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup
from setuptools import find_packages


def main():
    description = 'mycutter'

    setup(
        name='mycutter',
        version='0.0.1',
        author='example',
        author_email='example@example.jp',
        url='www.example.jp',
        description=description,
        long_description=description,
        zip_safe=False,
        include_package_data=True,
        packages=find_packages(),
        install_requires=['cookiecutter>=1.3.0'],
        tests_require=[],
        setup_requires=[],
        entry_points={
            'console_scripts': [
                'cutter = mycutter:main',
            ],
        }
    )


if __name__ == '__main__':
    main()
EOF

パッケージを作っていく。 mycutter パッケージにテンプレートを展開するモジュールを用意する。 Python から Cookiecutter を使うときは cookiecutter.main.cookiecutter() 関数にテンプレートのディレクトリを指定すれば良い。 テンプレートはこれからパッケージ内に作るけど、それは setuptools の pkg_resources.resource_filename() 関数で取得する。

$ mkdir mycutter
$ cat << 'EOF' > mycutter/__init__.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pkg_resources

from cookiecutter.main import cookiecutter


def main():
    template_directory = pkg_resources.resource_filename('mycutter', 'template')
    cookiecutter(template_directory)


if __name__ == '__main__':
    main()
EOF

テンプレートのデータファイルをパッケージに含めるために MANIFEST.in ファイルも用意しよう。

$ cat << 'EOF' > MANIFEST.in
recursive-include mycutter/template *
EOF

mycutter パッケージの中に template ディレクトリを用意して、その中に先ほどと同じ要領でテンプレートを作っていく。

$ mkdir -p mycutter/template/{{cookiecutter.package_name}}
$ touch mycutter/template/{{cookiecutter.package_name}}/__init__.py
$ cat << 'EOF' > mycutter/template/cookiecutter.json
{
    "package_name": "helloworld"
}
EOF

作った Python パッケージを使う

以上で Cookiecutter を組み込んだ Python パッケージの用意ができた。 セットアップスクリプトを使ってインストールしよう。

$ python setup.py install

これで mycutter パッケージがインストールされた。

$ pip list | grep mycutter
mycutter (0.0.1)

mycutter パッケージをインストールすると cutter コマンドが使えるようになる。 このコマンドを実行すると、パッケージ内のテンプレートを元に Cookiecutter が実行される。

$ cutter
package_name [helloworld]: helloworld3

実行すると次のようにディレクトリツリーができた。

$ tree helloworld3
helloworld3
├── __init__.py
└── __pycache__
    └── __init__.cpython-35.pyc

1 directory, 2 files

めでたしめでたし。