LoginSignup
22
5

More than 3 years have passed since last update.

Elmで作るCLIアプリケーション

Last updated at Posted at 2019-12-19

この記事は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と同じように使うことができます :raised_hands:

一つ注意してほしいところは、TEAは何かしらの Msg を受け取って Model を更新していくアーキテクチャなので Msg を発行しなければいけません(Msgが発行されないとアプリケーションが終わってしまいます!)。Webアプリケーションの場合は view から Msg が発行されるので問題ないのですが、CLIの場合だと view が無いので自分で Msg を発行する必要があります。方法としては、

  • init 関数の Cmd
  • JS側からportで値を送る

の2つが主な手段だと思います。

CLIアプリケーションを作る

スクリーンショット 2019-12-11 6.34.12.png

アプリケーションの作り方は詳しくは紹介しません。主に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が使えるようになりました :raised_hands:

入出力

スクリーンショット 2019-12-14 7.30.29.png

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ライフを!!

22
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
5