Chapter 04

プロジェクト作成

しんせいたろう
しんせいたろう
2024.02.12に更新

前章まではファイルで 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 "名前空間にわけました"

名前空間の追加の練習がてら、日記をリスト表示する関数追加しましょう。作業は

  1. ネームスペース追加
  2. 関数記述
  3. エントリーポイント更新

の3つです。

  1. ネームスペース追加
touch src/journal/list.clj
  1. 関数記述 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"))))
  1. エントリーポイント更新
#!/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

いったん仕組みを覚えたら、ほんとに簡単で幸せですね 👍