この記事は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ライフを!!