前章まではファイルで babashka を触って来ました。ここからはプロジェクトで babashka を使っていきましょう。
プロジェクト構成
Babashkaでのプロジェクトも、他のClojureプロジェクトと同じように、コードを別々のファイルに分割し、名前空間を定義し、名前空間がファイル名に対応するように整理することができます。
ディレクトリ構成
このような構成で作っていきます。
.
├── bb.edn
├── entries.edn
├── journal
└── src
└── journal
├── add.clj
└── utils.clj
src
以下は 他のClojureプロジェクトでも見かける構成です。
Babashkaプロジェクトで異なるのは、プログラムの実行可能なエントリーポイントとして、 ./src/journal/core.clj
などを使用する慣習ではなく、 ./journal
を使用していることです。
また、名前空間を使う場合は、 bb.edn
で以下のように記述してBabashkaに src
ディレクトリを探すように指示します。
{:paths ["src"]}
これを記述することで、他の名前空間のrequire ((require '[journal.add])
など)ができるようになります。
エントリーポイントファイルの整理
journal
ファイルは、コマンドライン引数の解析と関数へのディスパッチを定義するファイルとして使います。よって read-entries
関数やadd-entry
関数は対応する名前空間に移動させることにして、以下の様に修正します。
#!/usr/bin/env bb
(require '[babashka.cli :as cli])
(require '[journal.add :as add])
(def cli-opts
{:entry {:alias :e
:desc "新しい日記"
:require true}
:timestamp {:alias :t
:desc "unix timestamp"
:coerce {:timestamp :long}}})
(defn help [_]
(println
(str "add\n"
(cli/format-opts {:spec cli-opts}))))
(def table
[{:cmds ["add"] :fn add/add-entry :spec cli-opts}
{:cmds [] :fn help}])
(cli/dispatch table *command-line-args*)
Namespaces
名前空間を分けた場合、requireは、 (require)
ではなく、通常のClojure namespace 宣言に戻します。(正直、ちょっとホッとしますよね...)
src/journal/add.clj
(ns journal.add
(:require [journal.utils :as utils]))
(defn add-entry [{:keys [opts]}]
(let [entries (utils/read-entries)]
(spit utils/ENTRIES-LOCATION
(conj entries
(merge
{:timestamp (System/currentTimeMillis)}
opts)))))
src/journal/utils.clj
(ns journal.utils
(:require [babashka.fs :as fs]
[clojure.edn :as edn]))
(def ENTRIES-LOCATION "entries.edn")
(defn read-entries []
(if (fs/exists? ENTRIES-LOCATION)
(edn/read-string (slurp ENTRIES-LOCATION))
[]))
Babashkaは、
- すべてのコードが一つにまとまっている場合は名前空間宣言を省略可
- 複数のファイルに分割する場合Clojureプロジェクト構成の通常のルールを適用
になります。
ファイルを分割する場合は、名前空間はファイルシステムパスに対応していなくてはいけません。また前述の通り、bb.edn
にファイルパスを記述しておかなくてはいけません。
コマンドを実行してみましょう。期待通り成功すると思います。
./journal add --entry "名前空間にわけました"
名前空間の追加の練習がてら、日記をリスト表示する関数追加しましょう。作業は
- ネームスペース追加
- 関数記述
- エントリーポイント更新
の3つです。
- ネームスペース追加
touch src/journal/list.clj
- 関数記述
src/journal/list.clj
(ns journal.list
(:require [journal.utils :as utils]))
(defn list-entries [_]
(let [entries (utils/read-entries)]
(doseq [{:keys [timestamp entry]} (reverse entries)]
(println timestamp)
(println entry "\n"))))
- エントリーポイント更新
#!/usr/bin/env bb
(require '[babashka.cli :as cli])
(require '[journal.add :as add])
(require '[journal.list :as list]) ;; 追記
(def cli-opts
{:entry {:alias :e
:desc "新しい日記"
:require true}
:timestamp {:alias :t
:desc "unix timestamp"
:coerce {:timestamp :long}}})
(defn help [_]
(println
(str "add\n"
(cli/format-opts {:spec cli-opts}))))
(def table
[{:cmds ["add"] :fn add/add-entry :spec cli-opts}
{:cmds ["list"] :fn list/list-entries} ;; 追記
{:cmds [] :fn help}])
(cli/dispatch table *command-line-args*)
確認
./journal list
いったん仕組みを覚えたら、ほんとに簡単で幸せですね 👍