Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

この記事は、Clojure Adven Calendar 2013 12/07日の記事です。

今日は、leiningen (github) を使って、自分の独自のプロジェクトのテンプレート(いわゆる オレオレテンプレート )を作る話をします
(leiningen のテンプレート作成に関するドキュメント(ここ) を見れば終わり、という話もありますが、自習メモの意味でもまとめておきたいと思います)。

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

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

$ lein new my-project

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

$ lein new $TEMPLATE_NAME my-project

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

独自テンプレートの作成

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

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

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

上記はシンプルすぎる例ですが、より具体的なサンプルは、デフォルトのテンプレート(ここ) を見るとよいでしょう。

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

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

(def {{name}} :foo)

namespacerequire の指定とか、デフォルトで用意しておくべき関数(単独アプリケーションなら-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
(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 も入れています。

(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
(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 テンプレートがインストールされます。

$ 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 さんの記事になります。よろしくお願いします。

ponkore
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away