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
関数に型をつけました。
Argument
とOption
は使う機会がなさそうなのでひとまず後回しにしています。
Command
クラスのラップ方針はexternal
とbs.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に挑戦してみましょう。