commandpostのBuckleScript bindingを書いた

これは元ドワンゴ Advent Calendarの22日目の記事です……遅刻しましたけど22日ということで。

今年に入ってから、私はBuckleScriptで自作ノベルゲームエンジン用のトランスパイラhttps://github.com/cowlick/cowlick-sexpr-compilerを開発しています(最近さぼってたけどぼちぼち再開予定)。 このツールを開発する際に、option parserとしてこれまでは https://github.com/jaredly/minimist.reを使用していました。 しかし、このライブラリには同じオプション名が複数回指定されると配列で受け取る、といったAPIが用意されていないので最近機能追加をしようとしたタイミングで困ってしまいました。

さて、では他のライブラリを……使おうにも選択肢がないです。 このあたりにユーザー数の問題が現れますねぇ。

ではしょうがないので自作か、という話になりますが、それよりは既存のライブラリをbindingしてしまえという話に落ち着くので書いてしましましょう。 今回は個人的によく使うhttps://github.com/vvakame/commandpostを選択しました。

成果物

publish済みです。

https://www.npmjs.com/package/@pocketberserker/bs-commandpost https://github.com/pocketberserker/bs-commandpost

方針

今回はCommandクラスとcreate関数、exec関数に型をつけました。 ArgumentOptionは使う機会がなさそうなのでひとまず後回しにしています。

Commandクラスのラップ方針はexternalbs.sendを使ってモジュールに関数を定義する形にしました。 Command型は('a, 'b) tで定義し、第1引数にはこのオブジェクトを渡すようにします。 例えば、create関数とdescriptionメソッドの定義はこんな感じになります。

module Command = struct
  type ('a, 'b) t

  external description: ('a, 'b) t -> string -> ('a, 'b) t = "" [@@bs.send]
  (* 色々略 *)
end

external create: string -> ('a, 'b) Command.t = "Command" [@@bs.new] [@@bs.module "commandpost"]

bs.newはコンストラクタ呼び出しであることを示します。 bs.moduleを指定するとその関数を呼び出すときに記述したモジュールがrequireされます。

BuckleScriptは|.で第1引数をパイプラインの対象にできるので、第1引数を対象のオブジェクトにしていてもそんなに困ることはないです。

create "test"
|. Command.description "test"

パイプライン便利。

型の話

悩んだ点がふたつあります。 ひとつはコンストラクタの型パラメータCommand<Opt, Arg>、もうひとつはanyです。

型パラメータは、今回に関してはletで束縛する際に明示して型をあわせる方向でなんとかしました。 これがあっているのかはちょっとわかりませんが…。

type root_options = RootOptions
type root_args = RootArgs

let root: (root_options, root_args) Command.t = create "root"

anyについてはてきとーに型があえばいいので使ってない型パラメータを指定する方向で調整しました。

まとめ

BuckleScriptでは比較的簡単にbindingが書けます。 というわけで皆さんもbindingに挑戦してみましょう。