pecoと見ると、pacoを思い出しそうになる私です。(pacoは名前が変わってporgとなっていたようです。気づかなかった・・・)
pacoは今でも便利に使ってますが、まぁ普通は使わずにパッケージマネージャを使ったほうがいいと思います。emacsを手でビルドしていると手放せないんだ・・・
前書き
pecoって?という方はこちらを。 https://github.com/peco/peco
機能としては、標準入力から受け取ったものを任意に絞り込んで、標準出力に出力する、というかなりシンプルですが、emacsでhelm/anythingを、Vimならば・・・なんだっけ。そういった絞りこみインターフェースをシェル上で実現する、という感じのものです。
私も以前、zshで似たような(ものすごい機能劣化版)を、zshの機能だけを使って作ったりしたことがありますが、これはコマンドなのでシェルに関係なく使えます。
qiitaでも検索すれば色々利用法は出てきます。ものがシンプルなので、応用が非常に効く、というのも人気の一つのようです。
peco自体は、percolというpython製のツールにインスパイアされて作った(とpecoのreadmeに書いてます)、ということなので、そのさらに前のツールを作った方に先見の明があったということでしょう。知っている人は知っている、Keysnailを作った方です。
本題
さて、pecoはwindowsも含めた複数環境をサポートするため(なのかどうかはしりませんが)、golangで作成されています。最近気づきましたが、golangってインデントがタブのせいか、github上で非常にインデントが広くて読みづらいですね。
個人的にgolangは仕事で使うんならやらんでもないけど、個人的には積極的に利用したくないなー、という感じです。書き方が冗長すぎると感じたもんで。
(そもそもツールだからええやん、というのはそのとおり。私もpeco入れて便利に使ってます)
OCamlで何かしらターミナルを利用したツールを作ってみたかった、というのもあっていつものノリで、OCamlで似たものに作ってみました。
利用したライブラリ
ライブラリとしては、以下を利用しました。
-
lambda-term
- Lwt
- React
- これらは、lambda-termがReactを利用したZedと、Lwtを盛大に利用しているため、必須です
lambda-termには、read_lineという入力インターフェースがすでに実装されているため、これを思いっきり利用して、面倒な部分を省くことにしました。
ドハマりしたところ
あまりこういったコマンドラインツールとか書いたこと無いので、ところどころでドハマりしてめちゃ時間取られたりしました。単純にLwtの使い方を調べるのにもすごい時間かかった・・・。
ttyのとき/パイプのときのハンドリング
単純に私の知識がなかったのが原因ですが、パイプで繋いだ時の標準入力、標準出力の使い方に非常に悩まされました。ttyかどうか、というところについても同様に、 何やっても動かねぇぇ みたいな感じにもなったりしました。
これについては、OCamlでのシステムプログラミングをまとめたGithub pagesがあったので、これを熟読することでなんとか解決できました。
非常に役立ちました。
複数のReact.Eのハンドリング
今回、絞り込んだものを選択するキーシーケンスと、編集された時のキーシーケンスとに別々にReact.Eを割り当てて、それらに対するハンドラを設定する、という形にしましたが、これがまたうまく行かない。
let e1, _ = React.E.create ()
and e2, _ = React.E.create () in
(* f1, f2はそれぞれ別の処理をtermにするものだと思ってください *)
React.E.map e1 f1 |> ignore;
React.E.map e2 f2 |> ignore;
みたいな感じにすると、f1が動いた後にf2が動かなく、f2が動いた後にはf1が動かない、みたいな形になります。解決としては、きれいじゃないかもしれないけれども、こんな形に落ち着きました。
type t = {mutable v: int}
let i = {v = 0} in
let e1, _ = React.E.create ()
and e2, _ = React.E.create () in
let e = React.E.select [React.E.stamp e1 (); React.E.stamp e2 ()] in
React.E.map e (fun () -> i.v <- succ i.v) |> ignore;
mutableを使うのはなんかシャクですが、無理して複雑怪奇にするよりはこっちのほうがすっきりしました。
ひとまず作ってみて
とりあえずどんなものか、感触を掴みたかったんで作ってみましたが、意外としんどいです。ただ、一回軌道に乗ってしまえば、リスト処理とかoptionの処理とか、若干冗長でもザクザク書いていくことはできました。
だいぶ行き当りばったりで作ったので、モジュール分けとかあまり考えてないし、何よりテストなんて書いてませんが、それでもほとんど実行時エラーには(IOに関するところを除いて)ぶち当たりませんでした。この辺はOCamlとかHaskellを使っていて気持ちがいいところですね。
まぁ、OCamlでわざわざツールを書くか?と言われればウーン・・・となりますが。速度を気にしないのならRuby/Pythonとかでトライ&エラーしたりしたほうが速いケースもあるかと思いますので、その辺は臨機応変で。
(相変わらずまとまりないな)