[Common Lisp] ros templateの紹介
cl-web-2d-game *1 のようなWebアプリ向けのライブラリなどを作っていると、使うまでに色々とサーバ側の設定コードが必要で、中々気軽にプロジェクトを起こせなかったりします(単にインタフェースが悪いのではという議論は置いておきます)。そうした課題の解決方法としてプロジェクトテンプレートがあると思います。Common Lispにも汎用的なものとしてはCL-Projectがありますし、WebフレームワークであるCaveman2にもテンプレート(skelton)が用意されています(CL-Projectベース)。ただ、自分で気軽にテンプレートを量産したり、もしくは人の書いた色々なテンプレートを使ったりしたいと思うと、テンプレートを統一的に管理する仕組みが欲しくなります。
そういうことをするのであればRoswellの周りだろう…と思って、まずは既存のテンプレートシステムがないのかと見てみると、ros template
というサブコマンドがあることが分かりました。ただ、ほぼアンドキュメントな状態で、ソースを見ながら使い方を探る必要がありました。また、使うにあたってこういう機能も欲しいというものもあったので、ポツポツとプルリク出したりしてました。その辺りも含めて使い方の紹介をする記事です。
目次
コマンドの一覧
ros template
コマンドの一覧は空でコマンドを打てば(もしくはros template help
で)下記のように見ることができます。他、ドキュメントとしてはdocuments/ros-template.mdがあります。プルリク出してクイックスタートを追加したので少し充実しました。
$ ros template Usage: ros template [subcommand] init Create new template deinit Remove a template list List the installed templates checkout Checkout default template to edit. add Add files to template. cat Show file contents edit Edit file contents rm Remove (delete) files from template. delete Remove (delete) files from template. type Set template type for a file. chmod Set mode for a file. rewrite Set path rewrite rule for a file export Export template to directory import Import template help Print usage and subcommands description
なお、deinit
, export
, import
は最近プルリクを入れてもらったものです。またedit
も最近入ったものなので、これらの利用には最新版(master)が必要です。
基本的な利用方法
テンプレートの作成やテンプレートエンジンの適用方法など基本的な使い方を見ていきます。
テンプレートを作成する
まずはinit
サブコマンドで空のテンプレートを作成します。
$ ros template init sample
特に出力はありませんが、checkout
サブコマンドを空で打ってみると確かに作成されていることが分かります。
$ ros template checkout current default is "default" candidates: default sample # ← これ
作成したテンプレートはros init <template名> <プロジェクト名>
のようにして利用することができます。ただし、まだ空なので何もできません。
$ ros init sample some-project ; compiling file "/root/dev/roswell/lisp/util-template.lisp" (written 07 FEB 2018 11:55:59 AM): # ~以下略~ $ ls # まだ何もない
ファイルの追加
空のままではしょうがないので、ファイルの追加を行っていきます。
ほとんどのサブコマンドは第1引数に対象とするテンプレート名をとります。ただし、事前にcheckout
サブコマンドでテンプレートを指定しておくと第1引数を省略できます。例えば下記のようにすると、以降ファイルの追加や削除、その他の操作はsample
テンプレートに対してなされるようになります(なお、default
テンプレートに実体はありません。したがってcurrent default is "default"
は未選択と同義です)。
$ ros template checkout sample $ ros template checkout # デフォルトがsampleになっていることを確認 current default is "sample" candidates: default sample
次にファイルの追加ですが、ひとまず追加するファイルtest
を適当に作っておきます。
$ echo "Hello Template!!" > test
add
サブコマンドで今作ったファイルをテンプレートに追加します(フォルダの指定はできません)。
$ ros template add test
list
サブコマンドでテンプレートの内部を概観してみます。すると、先ほど指定したtest
が無事追加されていることが分かります。copy
については後で触れます。
$ ros template list
copy test
また、cat
サブコマンドで中を見てみると、確かに先ほど作成したファイルと同じ内容であることが分かります。
$ ros template cat test
Hello Template!!
さて、ここで改めてros init
コマンドでテンプレートを起こしてみます。
$ mkdir sample $ cd sample/ $ ros init sample some-project $ ls test $ cat test Hello Template!!
以上で、ひとまず自作テンプレートからプロジェクトを作成することができました。
テンプレート変数の利用
ファイルを追加して取り出せるようにはなったものの、変数を利用した部分的な書き換えることができないことにはテンプレート機構としては不十分です。
ros template
ではDjulaをテンプレートエンジンとして利用した書き換えをサポートしています。Djulaは中々機能が豊富でほとんど把握できていないのですが…ここでは"{{ variable }}
"といった形式で変数を埋め込むことができるという所だけ抑えておけば十分です。
ファイル名とファイル内容では変数の適用方法が異なるのでそれぞれ見ていきます。
ファイル名への変数適用:ros template rewrite
ファイル名については、rewrite
サブコマンドを使うことで、リライトルールに変数を埋め込むことができます。
例えば、先ほどのtest
というファイルを<プロジェクト名>.txt
という形式で出力したい場合は次のように設定します。
$ ros template rewrite test "{{ name }}.txt" $ ros template list # ルールが設定されていることの確認 copy test -> "{{ name }}.txt"
name
はデフォルトで利用可能な変数でros init <template> <project name>
としたときに<project name>
が入ります。デフォルトで利用できる変数は下記の4つです。
name
: プロジェクト名author
: 作者名。git config
から拾われます(なければ$(whoami)
)email
: メールアドレス。git config
から拾われます(なければ$(whoami)@$(hostname)
)get_universal_time: 実行時点の時間。[
get-universal-time`](http://clhs.lisp.se/Body/f_get_un.htm)関数の結果です
さて、実際にros init
すると、リライトルールに沿ってファイル名の書き換えが行われていることが分かります。なお、リライトルールにdoc/{{ name }}.txt
のようにフォルダパスを含めると、フォルダを作成した上でその配下に置いてくれます。
# ※以降、ros initは適当な空ディレクトリで実行しているものとします $ ros init sample sample-project $ ls sample-project.txt $ cat sample-project.txt Hello Template!!
ファイルの中身に対する変数適用:ros template type
次にファイルの中身での変数適用を見るため、まずはtest
ファイルを次の内容に更新しておきます。
$ cat<<EOF > test Hello {{ sample }}!! name: {{ name }} author: {{ author }} email: {{ email }} universal time: {{ universal_time }} EOF $ ros template add test # testファイルを上書き $ ros template cat test Hello {{ sample }}!! name: {{ name }} author: {{ author }} email: {{ email }} universal time: {{ universal_time }}
name
, author
, email
, universal_time
は上記で触れたようにデフォルトの変数として利用できます。sample
のような独自の変数はros init
の引数として--sample value
(間に"="を入れるのはNG)のようにして指定できます(ファイルのリライトルールでも同じように独自の変数を利用できます)。
実際に試してみますが・・・
$ ros init sample some-project --sample Ikaruga $ ros template cat test Hello {{ sample }}!! name: {{ name }} author: {{ author }} email: {{ email }} universal time: {{ universal_time }}
このままでは変数は適用されません。ここで関係してくるのが、先ほどlist
サブコマンドの表示の中で説明を飛ばしたcopy
です。
$ ros template list copy test -> "{{ name }}.txt"
これはtype
サブコマンドで指定できるもので、copy
とdjula
の2種類があります。デフォルトのcopy
はその名の通りファイルをそのままコピーします。一方のdjula
は、単にコピーするのではなくDjula
で処理をしたものを書き出します*2。
$ ros template type djula test $ ros template list djula test -> "{{ name }}.txt"
この状態で改めてros init
してみると、意図通り変数の書き換えが行われました。
$ ros init sample some-project --sample Ikaruga $ ros template cat test Hello Ikaruga!! name: some-project author: eshamster email: hamgoostar@gmail.com universal time: 3727259379
なお、デフォルトのタイプはcopy
になっていますが、テンプレートごとに変更することもできます。そのためには、type
サブコマンドにファイル名を与えずに実行します。設定したデフォルトタイプは、以降に新規追加するファイルに影響します*3。
$ ros template type # 引数なしで現在の設定を確認 current default type is "copy" $ ros template type djula $ ros template type current default type is "djula"
テンプレートのエクスポート・インポート
ros template
は基本的にテンプレートの情報を内部的に管理するように作られています。そのため、Gitでテンプレートを管理したかったり、それを人に配布したかったりといった用途では少々不便です。そこで、テンプレートの実体をローカルに取り出したり、逆にローカルのテンプレートを一式取り込むためのサブコマンドが、export
やimport
になります。
まず、export
サブコマンドは指定した(もしくはチェックアウトしている)テンプレートを一式ローカルフォルダに持ってきます。なお、同名のファイルが存在する場合は上書きします。一方で、テンプレート内に存在しないファイルがあった場合は単に無視します。
# 適当な空のディレクトリ $ ros template checkout sample $ ros template export $ ls roswell.init.sample.asd test $ cat test Hello {{ sample }}!! name: {{ name }} author: {{ author }} email: {{ email }} universal time: {{ universal_time }}
sampleテンプレート内のファイルtestを取り出せたことが分かります。なお、roswell.init.sample.asd
はテンプレートの管理実体 兼 作成スクリプト片です。中身は次のようになっていて、*params*
内でパラメータリストとして様々な情報を管理しています。
$ cat roswell.init.sample.asd (DEFPACKAGE :ROSWELL.INIT.SAMPLE (:USE :CL)) (IN-PACKAGE :ROSWELL.INIT.SAMPLE) (DEFVAR *PARAMS* '(:COMMON (:DEFAULT-METHOD "djula") :FILES ((:NAME "test" :METHOD "djula" :REWRITE "{{ name }}.txt")))) (DEFUN SAMPLE (_ &REST R) (ASDF/OPERATE:LOAD-SYSTEM :ROSWELL.UTIL.TEMPLATE :VERBOSE NIL) (FUNCALL (READ-FROM-STRING "roswell.util.template:template-apply") _ R *PARAMS*))
そして、import
サブコマンドは指定されたフォルダ内のroswell.init.xxx.asd
に従ってテンプレートを作成します。このとき、同名(名称はroswell.init.xxx.asd
のxxx
を抽出)のテンプレートがあった場合は上書きされるので注意が必要です。
# まだテンプレートがない別のマシンにexportしたファイルを持ってきた想定 $ ros template checkout current default is "default" candidates: default $ ls downloaded/ roswell.init.sample.asd test $ ros template import downloaded/ $ ros template list sample 0600 djula test -> "{{ name }}.txt"
このように、export
, import
サブコマンドを使うことで、自分で作ったテンプレートをGitで管理したり、人の作ったテンプレートを落としてきて試してみる、ということが(それなりに)気楽にできるようになりました。
その他
その他のコマンド
ここまでで説明していないコマンドには下記があります…が名前と説明からおおむね推測がつくと思いますので割愛します。
deinit Remove a template edit Edit file contents rm Remove (delete) files from template. delete Remove (delete) files from template. chmod Set mode for a file.
余談:テンプレートの実体の管理場所
余談ですが、作成したテンプレートは~/.roswell/local-projects/templates/<テンプレート名>
というフォルダで管理されます。
追加したファイルは同フォルダ内の<テンプレート名>-template
フォルダに入っています。下記のようにファイル名はエンコーディングされています。
$ ls -F ~/.roswell/local-projects/templates/sample/ roswell.init.sample.asd sample-template/ $ ls ~/.roswell/local-projects/templates/sample/sample-template/ %38%2T%37%38 $ cat ~/.roswell/local-projects/templates/sample/sample-template/%38%2T%37%38 Hello {{ sample }}!! name: {{ name }} author: {{ author }} email: {{ email }} universal time: {{ universal_time }}