52
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elixirでいい感じのCLIをパッと作ってサッと共有(できるようになる。そう、Elixir 1.3ならね)

Last updated at Posted at 2016-06-21

ElixirでCLIを書く

[翻訳]Elixirでコマンドラインアプリケーションを書くで紹介されている通り、Erlang/Elixirにはescriptという便利なツールがあり、CLIを簡単に作れる。

今回自分でも作ってみたので、いくつか気づいたポイントを紹介。

基礎知識

  • mix.exsで以下のように定義しておく
  def project do
    [
      ...

      escript: [
        main_module: Your.CLI,
        name: "yourcli",
        path: Path.expand(Path.join(["~", ".mix", "escripts", "yourcli"])), # 後述
      ],

      ...
     ]
  end
  • Your.CLI.main/1の中身を実装する。その名の通りCLIのメインエントリポイントで、コマンドライン引数が配列として渡る
    • 最初はIO.puts("Hello world!")とかで
  • mix deps.get
  • mix escript.build
  • (追記)Elixir 1.3以降では:pathは不要。後段の追記参照

バイナリを置く場所

いきなりこの記事のポイントに入る。

mix escript.buildはデフォルト(前節で:pathキーを設定しない状態)だと、カレントディレクトリにyourcliという名前の実行可能バイナリをポンと設置してくれる。従って、

$ mix escript.build
  (snip)
$ chmod +x yourcli
$ ./yourcli
Hello world!

こうなるわけだ。簡単でよろしい。パッと作れそうだ。

さて、近々来る予定のElixir 1.3では~/.mix/escriptsというパスがデフォルトのescriptバイナリ置き場として制定され、新設のmix escript.installコマンドがビルドしたバイナリはここに置かれる。1

つまり今から~/.mix/escriptsPATHを通しておき、バイナリをそこに吐き出すようにしておけばスムースに移行できる。前節の設定はそういう意味。

ちなみにElixir 1.3ではmix escript.install <URL>一発でリモートのプロジェクトをフェッチしてインストールできる。「サッと共有」ができるというのはそういうわけ。2

ただし、escriptはコンパイル済みバイナリによる提供であればErlangさえあれば動くが、mixでインストールするにはElixirが必要になる。というかmixはElixirについてくる。

(追記)というわけで、Elixir 1.3が出てしばらく立ち、もう1.6になった今現在は、:pathは指定せずにおいて、mix escript.install <url>などとするのがよい。まだリリースはしておらず、ローカルディレクトリからインストールしたい場合もmix escript.install~/.mix/escriptsに配置されるし、重複チェックやescript.uninstallなどもある。

また、1.3ローンチ直後はできなかったが、最近は選択肢が増えて、

  • mix escript.install hex <package>
  • mix escript.install github <user>/<repo>

などとできるようになった。今はこちらをinstall instructionとすべき。

OptionParser超便利

冒頭でリンクした記事でも紹介されているOptionParserはElixirのビルトインモジュールで超便利である。

コマンドライン引数は配列としてmain/1に渡ってくるわけだが、OptionParserはその配列をよろしく解釈してくれる。

OptionParser.parse(~W(-v --hoge --foo Foo Bar Baz))
# {[hoge: true, foo: "Foo"], ["Bar", "Baz"], [{"-v", nil}]}

返り値は{opts, args, errors}で、--つきのオプションであれば自動でoptsとして解釈される。true, false以外の文字列や数値の値がなければ真偽値を、あればその値をオプションに紐付ける。

-つきのオプションは短縮形扱いなので、基本形が定義されていないとerrorsに入ってしまうが、その設定もaliasesを指定するだけでいい。

OptionParser.parse(~W(-v --hoge --foo Foo Bar Baz),
  aliases: [v: :verbose])
# {[verbose: true, hoge: true, foo: "Foo"], ["Bar", "Baz"], []}

switchesstrictといったその他のオプションもある。switchesはオプションの取る値のTypeを指定できて、

OptionParser.parse(~W(-v --hoge --foo Foo Bar Baz),
  aliases: [v: :verbose], 
  switches: [foo: :integer])
# {[verbose: true, hoge: true], ["Bar", "Baz"], [{"--foo", "Foo"}]}

このようにvalidateできる。strictは読んで字のごとく定義したモノ以外受け付けなくする。

OptionParser.parse(~W(-v --hoge --foo Foo Bar Baz), 
  aliases: [v: :verbose], 
  strict: [verbose: :boolean])
# {[verbose: true], ["Foo", "Bar", "Baz"], [{"--hoge", nil}, {"--foo", nil}]}

システムコマンドの使用

System.cmd/3を使うと別のコマンドをElixirコードから呼び出して結果を取得できる。世の中にあるElixirモジュールだけではやりづらい処理を補うことができる。

ただし、System.cmd/3は単一のコマンドに引数を渡して実行させることしかできない。例えば以下のようなことをしたい場合に困る。

$ echo "some string" | pbcopy

Macだとよくやるクリップボードコピーだ。他にもjqにパイプしてJSONをPrettyPrintしたいなんてこともあるかもしれない。3

上記Docにも下の方にちょろっと書いてあるが、こういったパイプやリダイレクトを利用したい場合、Erlangの:os.cmd/1が使える。

contents = "some string"
:os.cmd('echo #{contents} | pbcopy')

渡すのはchar listになる点が要注意。Interpolationは使えるので、Elixirコード内で生成された文字列等をクリップボードに渡す処理を書くことができる。Windowsの場合もclipで同じことができる。

コマンドの終了ステータス

何かメッセージを出してコマンドを異常終了させたい場合があると思う。

単にraise(message)してもいいのだが、例外になってしまうので標準出力にスタックトレースが吐き出されてカッコ悪い。
エラーハンドリングした上で終了状態を明示して止めたいのであれば、こう書ける。

IO.puts(:stderr, message)
exit({:shutdown, 1})

Kernel.exit/1docの下の方、"CLI exits"の節に説明が書いてある。1を別のステータスコードにしてもいい。リンクされた全てのOTPプロセスには全てpolitely-shutdownするよう通知される。
ちなみにraise等の正常でないケースでは、OSプロセスとしては全てステータス1で終了するとのこと。

ghpr

他にもある気がするけど、とりあえず思いついたのはここまで。

以上の知見を活かしつつ、ghprというCLIを作ったので使ってみてください。Elixirで書いてるよという主張のためにレポジトリはymtszw/ex_ghprです

内容としてはGitHub Pull Requestをコマンド一発でオープンできるシロモノです。
github/hubもあるのですが、複数アカウントの取り扱いが面倒なのを解消しつつ、自分のチームのワークフローをCLIで補助しています。
オプションでいろいろとできるようになっている+今後も拡張する予定なので、よろしくお願いします。

(追記)筆者は今も変わらず日常的に使ってますが、現状機能に満足して2年位放置していたら、hubのほうがちょこちょこ機能追加してて、普通にhubで十分なケースが増えた気もする。。。

  1. masterなので既にバージョンは繰り上がっているが、こちらのリンク参照

  2. Elixir 1.3の新機能等は@tuvistavieさんがtokyo.ex #1で発表していたスライドにまとまっています。

  3. Poison.encode/2prettyオプションがあるけど。

52
45
1

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
52
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?