自分用のj2cliをkamidanaという名前で作りはじめた
何でj2cliを使わないの?
何でj2cliを使わないのかというと、以下の様な理由。
- j2cliのforkがいっぱいあってカオス
- (一番star数が多いforkは)python3.xに対応していない
- おもったよりも機能が多くない
- (正直そんなに良いコードに見えない)
- (init.pyで
import pkg_resources
とかつらい)
そんなわけで自分用の物を作り始めた。
何でkamidanaなの?
jinja2は神社なので、もう少し手軽なお社なら、家のどこかに存在する(事もある)神棚かなーと。
どういうことしたいの?
設定ファイル中の値をループして何かしたいということがけっこうあった。例えば以前のswagger specからgoのmodelを生成するスクリプトのswagger2goがあるとして。生成先のファイルがたくさんある場合に一度に生成できるようにしたい。例えばMakefileを書くなどでも良いのだけれど。そのMakefileを書くのが面倒くさい。
以下のような設定ファイルは存在するとする。
apps: fooAppX: port: 8000 barAppX: port: 8001 booAppX: port: 8002 beeAppY: port: 8003
この設定ファイルから以下のようなMakefileを生成したい。
genFoo: swagger2go swagger/foo_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/fooAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_foo.go genBar: swagger2go swagger/bar_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/barAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_bar.go genBoo: swagger2go swagger/boo_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/booAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_boo.go
正確にいうと末尾がXで終わっているものだけをコード生成の対象にしたい。末尾がyで終わっているものはコード生成の対象から除外したい。
設定ファイルの名前をあれこれいじれば生成できるもののこれをただのpythonスクリプトにしてしまうとだるさが一気に増してしまう。 これをどうにか綺麗なjinja2テンプレートを使って生成したい。
どういう風に使うの?
コマンドラインでkamidanaを使う。 --data
には設定ファイル を --additionals
には追加したい述語(test)や出力形式(filter)を定義したファイルを渡せる。そんなわけで以下の様なjinja2テンプレートで十分になる。
Makefile.jinja2
{% for name in apps.keys() %}{% if name is x %} gen{{name|prefix}}: swagger2go {{name|swagger_path}} --package github.com/podhmo/foo/model --ref="{{name|swagger_ref}}" --file {{name|model_path}} {% endif %}{% endfor %}
まだまだ読める範囲なのではという感じ。実際以下の様な形で使う。
$ kamidana Makefile.jinja2 --data apps.yaml --additionals=additionals.py genFoo: swagger2go swagger/foo_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/fooAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_foo.go genBar: swagger2go swagger/bar_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/barAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_bar.go genBoo: swagger2go swagger/boo_app_x.yaml --package github.com/podhmo/foo/model --ref="#/definitions/booAppData" --file ${GOPATH}/src/github.com/podhmo/model/gen_x_boo.go
ここでそれぞれ渡したファイルは以下の様な感じ。
apps.yaml(再掲)
apps: fooAppX: port: 8000 barAppX: port: 8001 booAppX: port: 8002 beeAppY: port: 8003
kamidana.as_filter
や kamidana.as_test
のデコレータがついた関数が自動的にテンプレート上で使えるようになる。
additionals.py
from kamidana import as_filter, as_test import re @as_filter def prefix(v): return titleize(snakecase(v).split("_", 1)[0]) @as_filter def swagger_path(v): return "swagger/{}.yaml".format(snakecase(v)) @as_filter def swagger_ref(v): name = snakecase(v).rsplit("_", 1)[0] return "#/definitions/{}Data".format(camelcase(name)) @as_filter def model_path(v): xs = snakecase(v).split("_") tag = xs[-1] name = xs[0] return "${{GOPATH}}/src/github.com/podhmo/model/gen_{}_{}.go".format(tag, name) @as_test def x(v): return "x" == snakecase(v).rsplit("_", 1)[1].lower() # このあたりのコードはどこかライブラリに持っておきたい def snakecase( name, rx0=re.compile('(.)([A-Z][a-z]+)'), rx1=re.compile('([a-z0-9])([A-Z])'), separator="_" ): pattern = r'\1{}\2'.format(separator) return rx1.sub(pattern, rx0.sub(pattern, name)).lower() def camelcase(name): return untitleize(pascalcase(name)) def pascalcase(name, rx=re.compile("[\-_ ]")): return "".join(titleize(x) for x in rx.split(name)) def titleize(name): if not name: return name name = str(name) return "{}{}".format(name[0].upper(), name[1:]) def untitleize(name): if not name: return name return "{}{}".format(name[0].lower(), name[1:])
全然関係ないけれど、上で使っているcamelcaseやkebabcaseなどに変換する関数群をどこに置こうか迷ったりしている(zenmaiでも使いたいし。kamidanaでも使いたい。でも両者はわりと独立しているような気がする。一方で新しいパッケージは作りたくない)。