leiningen でプロジェクトのテンプレートを作ってみる

leiningen でプロジェクトのテンプレートを作ってみる
============================================

この記事は、[Clojure Adven Calendar 2013](http://qiita.com/advent-calendar/2013/clojure) 12/07日の記事です。

今日は、[leiningen](http://leiningen.org/) ([github](https://github.com/technomancy/leiningen)) を使って、自分の独自のプロジェクトのテンプレート(いわゆる **オレオレテンプレート** )を作る話をします
（leiningen のテンプレート作成に関するドキュメント([ここ](https://github.com/technomancy/leiningen/blob/stable/doc/TEMPLATES.md)) を見れば終わり、という話もありますが、自習メモの意味でもまとめておきたいと思います)。

### プロジェクトのテンプレートについて

`leiningen` で新規にプロジェクトを作成する場合、

```shell
$ lein new my-project
```
というように実行します。実行すると、`my-project`というディレクトリが作成され、その配下に以下のように各種ファイルが作成されます。また、`lein new` コマンドには、 **テンプレート** を指定することができます。

```shell
$ lein new $TEMPLATE_NAME my-project
```
上記 `$TEMPLATE_NAME` のところにテンプレート名を指定すると、テンプレートに従った形で `project.clj` や初期設定済みのソースファイル等がひな形として作成されます。テンプレートはデフォルトで、`default`、`plugin`、`template`、`app`、の４つが利用できますが、ローカルリポジトリもしくは maven、clojars といったリポジトリにあれば、それらのテンプレートも利用できます。有名なところでは、`heroku` 向けのテンプレートがあります(試しに `lein new heroku heroku-proj` とやってみれば、heroku へデプロイできる様に準備された heroku-proj というプロジェクトが作成されます)。

### 独自テンプレートの作成

今回は**オレオレテンプレート**を作るのが目的なので、以下のようにしました。

```shell
$ lein new template oreore
Generating fresh 'lein new' template project.
$ 
```

作成されたものを見てみます。

* ディレクトリの構造

```
oreore/
  | .gitignore
  | LICENSE
  | README.md
  | project.clj
  +- src/
      +- leiningen/
          + new/
             | oreore.clj
             +- oreore/
                 | foo.clj
```

* project.clj

```clojure
(defproject oreore/lein-template "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :eval-in-leiningen true)
```

この`project.clj`は、 **このプロジェクトが leiningen のプラグインであることを宣言するためのもの** 、であることに注意してください。 **oreore** というテンプレートから作成されたプロジェクトの `project.clj` のひな形ではありません。

注意点としては、`defproject` のバージョン記述に気をつける必要があります。`-SNAPSHOT` を付けると、後でローカルリポジトリにインストールするときに参照するのが面倒なので、今回は`-SNAPSHOT`を消す、という方針で行きます。

* src/leiningen/new/oreore.clj

```clojure
(ns leiningen.new.oreore
  (:require [leiningen.new.templates :refer [renderer name-to-path ->files]]
            [leiningen.core.main :as main]))

(def render (renderer "oreore"))

(defn oreore
  "FIXME: write documentation"
  [name]
  (let [data {:name name
              :sanitized (name-to-path name)}]
    (main/info "Generating fresh 'lein new' oreore project.")
    (->files data
             ["src/{{sanitized}}/foo.clj" (render "foo.clj" data)])))
```

これが `lein new oreore` コマンドで実行される処理の本体になります。`(defn oreore [name] ...)` が実際にテンプレート展開に使われる関数になります。パラメータ`name` には、`lein new oreore` に続く引数、すなわち新規のプロジェクト名が設定されます。

このソースは`foo.clj`というファイル１つのみを生成するシンプルな例となっています。処理の要は`->files` 関数です。この関数の第１パラメータには、`mustache`形式で指定された文字列テンプレートのキーと値の対応付けを行う map を指定し、第２パラメータ以降で具体的に生成するファイル(群)を指定します。サブディレクトリについては自動で作成されます。

Clojure では、名前空間に `-` が含まれていると、実際に配置されるディレクトリもしくはソースファイル名は `_` に置き換える必要がありますが、`name-to-path` 関数がその処理を行っています。

上記はシンプルすぎる例ですが、より具体的なサンプルは、デフォルトのテンプレート([ここ](https://github.com/technomancy/leiningen/blob/master/src/leiningen/new/default.clj)) を見るとよいでしょう。

* src/leiningen/new/oreore/foo.clj

これは、`oreore.clj`ファイルから`->files`関数で展開される元となるテンプレートファイルです。

```clojure
(def {{name}} :foo)
```

`namespace` や `require` の指定とか、デフォルトで用意しておくべき関数(単独アプリケーションなら`-main`とか、Compojure をつかったWebアプリケーションなら`defroute` とか)を記述しておくと良いでしょう。

最終的には、以下のような感じにしました。

* ディレクトリの構造

```
oreore/
  | .gitignore
  | LICENSE
  | README.md
  | project.clj
  +- src/
      +- leiningen/
          + new/
             | oreore.clj      (1)
             +- oreore/
                 | .gitignore
                 | README.md
                 | core.clj    (2)  <-- 元々あった foo.clj の代わりに作成
                 | project.clj (3)
```

* (1) src/leiningen/new/oreore.clj

```clojure
(ns leiningen.new.oreore
  (:require [leiningen.new.templates :refer [renderer name-to-path ->files]]
            [leiningen.core.main :as main]))

(defn oreore
  "おれおれテンプレート"
  [name]
  (let [render (renderer "oreore")
        data {:name name
              :sanitized (name-to-path name)}]
    (main/info "Generating fresh 'lein new' 俺俺 project.")
    (->files data
             ["src/{{sanitized}}/core.clj" (render "core.clj" data)]
             ["project.clj" (render "project.clj" data)]
             ["README.md" (render "README.md" data)]
             [".gitignore" (render ".gitignore" data)])))
```

* (2) src/leiningen/new/oreore/core.clj

コマンドラインツールとして作る目的で、tools.cli を使うコードを入れています。また、後に STANDALONE jar を作るつもりで `:gen-class` も入れています。

```clojure
(ns {{name}}.core
  (:gen-class)
  (:require [clojure.string :as str]
            [clojure.tools.cli :refer [cli]]))

(defn -main
  [& args]
  "{{name}} のメイン処理"
  (let [[opts args banner]
        (cli args
             "FIXME: help banner message"
             ["-h" "--help" "Show Help" :default false :flag true]
             ;; additional options
             )]
    (when (:help opts)
      (println banner)
      (System/exit 0))
    (println "Hello, {{name}}")))
```

* (3) src/leiningen/new/oreore/project.clj

```clojure
(defproject {{name}} "0.1.0-SNAPSHOT"
  :description "オレオレプロジェクト({{name}})"
  :license "Eclipse Public License 1.0"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/tools.cli "0.2.4"]]
  :aot :all
  :main {{name}}.core)
```

コマンドラインからの起動を考えて、`:main`の指定と `:aot :all` を入れています。

### 独自テンプレートのインストール

ファイルの準備ができたら、`oreore` プロジェクトのところで、`lein install` とすると、ローカルリポジトリに `oreore` テンプレートがインストールされます。

```shell
$ lein install
Created /Users/masao/oreore/target/lein-template-0.1.0.jar
Wrote /Users/masao/oreore/pom.xml
$ 
```

### 動かしてみる

```
$ lein new oreore fuga
Generating fresh 'lein new' 俺俺 project.
$ cd fuga
$ lein run
Retrieving org/clojure/tools.cli/0.2.4/tools.cli-0.2.4.pom from central
Retrieving org/clojure/tools.cli/0.2.4/tools.cli-0.2.4.jar from central
Compiling fuga.core
Hello, fuga
$ lein uberjar
Created /Users/masao/fuga/target/fuga-0.1.0-SNAPSHOT.jar
Created /Users/masao/fuga/target/fuga-0.1.0-SNAPSHOT-standalone.jar
$ java -jar target/fuga-0.1.0-SNAPSHOT-standalone.jar
Hello, fuga
$
```

上手く行ったようです。

## 所感など

テンプレート化すると、基本的にコピペ開発になってしまうのでよろしくない面も多々あるのですが、「ちょっとした日常の業務」で Clojure を使いたい、というときには、それなりの俺俺テンプレートを作っておくと、役立つ場面があると思っています。

本日の記事はこんなところで。

明日は、puriketu99 さんの記事になります。よろしくお願いします。