この記事はElmアドベントカレンダー 20日目の記事です。
みなさん、Elmを使ってアプリケーションを開発していると思います。Webアプリケーションを
実際、Elmは Webフロントエンド に特化したプログラミング言語であることに間違いは無いのでそれで間違い無いと思います。
ですが、実はElmにはユーザーインターフェイスのないヘッドレスプログラムを作成する機能が提供されています。実際、 elm-test のCLIはそれを使って実装されています。
今回はそれを紹介しようと思います。
Platform.worker
普段Elmでアプリケーションを作るときmain関数で Browser module に入っている sandbox, element, document, application を使っていると思います。
ですが今回はCLIアプリケーションを作成するので Platform.worker を使います。
この関数の型を見てみると
worker :
{ init : flags -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
}
-> Program flags model msg
となっているように view 関数が無いと分かります。
それ以外は普段のTEAと同じように使うことができます ![]()
一つ注意してほしいところは、TEAは何かしらの Msg を受け取って Model を更新していくアーキテクチャなので Msg を発行しなければいけません(Msgが発行されないとアプリケーションが終わってしまいます!)。Webアプリケーションの場合は view から Msg が発行されるので問題ないのですが、CLIの場合だと view が無いので自分で Msg を発行する必要があります。方法としては、
-
init関数のCmd - JS側からportで値を送る
の2つが主な手段だと思います。
CLIアプリケーションを作る
アプリケーションの作り方は詳しくは紹介しません。主にElmでCLIツールを作る際のTipsのようなものを書いていきたいと思います。
実際に作ったCLIツールは こちら にあるので詳しいコードをみたい人は見てみてください。
また、JS部分の実装は省略するのでリポジトリを参考にしてください。ports でインターフェースは定義するのでそれにあった実装ならなんでもいいと思います。
elm/http を使えるようにする
リポジトリを取得するためにHTTPリクエストを送るわけですが、 elm/http では XMLHttpRequest を利用しているためnode環境では動かないです。なので、使えるようにします。
nodeで XMLHttpRequest を使えるようにするために xhr2というpackageを利用します。
こちらを
$ npm i xhr2
したら index.ts 内に
global.XMLHttpRequest = require('xhr2');
と記述します。これで new XMLHttpRequest() が使えるようになるのでnode環境でもelm/httpが使えるようになりました ![]()
入出力
CLIのライフサイクルとして、入力 → 何らかの処理 → 出力 → 入力 ・・・ というようなサイクルができると思います。
Elmでは入力、出力(Debug.logを使えば可能ですがそれは除いて)ができないのでJSに任せなければいけません。JSに任せるといえば port ですね
出力
今回は、出力・入力をワンセットと考えます。Elm側から出すCmdは出力をお願いするものにしたいので
port output : String -> Cmd msg
という型にします。JS側のコードは
app.ports.output.subscribe(str => {
process.stdout.write(str);
// 入力
});
JS側のコードを見るとわかるのですが、output をしたあとに入力を受け付けるようにします。
入力
入力側では以下のようなデータを受け取るようにします
type alias KeyEvent =
{ sequence : String
, ctrl : Bool
, meta : Bool
, shift : Bool
}
キーが1つ押されるたびにデータをElm側に送るという形にします。
portではプリミティブな型以外は引数として取れないので
import Json.Decode as JD
port keyDown : (JD.Value -> msg) -> Sub msg
という形にしてDecodeするようにします。
JSの方は
app.ports.output.subscribe(async str => {
process.stdout.write(str);
app.ports.keyDown.send(await keyDown());
});
Promise を返す keyDown 関数をよしなに作って、それをawaitした値をElm側に送るようにします。
当然、awaitして取得できる型は KeyEvent と一致するものです。
これで入出力ができるようになりました。
文字に色をつけたい
CLIで文字に色を付けるさい\e[30mほげ\e[mみたいな感じで書きますが、ちょっと面倒だったりするので便利moduleを作っておくといいと思います。
foreGroundColor : Color -> Style
foreGroundColor color =
Style
{ color = color
, ground = Fore
}
backGroundColor : Color -> Style
backGroundColor color =
Style
{ color = color
, ground = Back
}
text : List Style -> String -> String
text style str =
styleString style ++ str ++ "\u{001B}[m"
こんな感じで text [ foreGroundColor Red ] "hoge" みたいにして書くことができます。
コマンドライン引数
コマンドライン引数は flags で最初に渡してあげるのがいいと思います。
また、コマンドライン引数をパースする パッケージ もあるので使ってみるといいかもしれません(僕は今回使わないアプリケーションを作ったので使いませんでした)
まとめ
Elmに慣れている人だったらかなり簡単にCLIツールをつくれるのでは!?っという印象でした。実際、1時間位でこのくらいのアプリケーションだったら作れたのでぜひ作ってみてください。
環境構築や Elm ↔ JS のあたりがちょっと面倒だったのテンプレートなどがあったら便利だなーっとおもいました。
みなさんもCLIをElmで作って楽しいElmライフを!!