Elixirでコマンドラインツールを作る

# Elixirでコマンドラインツールを作る

[How to create a command line utility with Elixir and mix](http://abstraction.killedthecat.net/create-command-line-utility-elixir-mix/)というサイトを参考にしてElixirでコマンドラインツールを作ってみる．

自分で書いた日記

- [Elixirでコマンドラインツールを作る](http://niku.name/articles/2014/07/17/Elixir%E3%81%A7%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%83%84%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E3%82%8B)

をまとめて再構成している．

# 宣伝

[Sapporo.beam](http://sapporo-beam.github.io/)という集まりがある．

毎週木曜日にErlangVMの話題について札幌やインターネットでわいわいするところ．

この記事に興味がある人なら楽しめると思う．きてね．

# 実行できるようにする

## プロジェクトを作成する

```zsh
/Users/niku/projects% elixir -v
Elixir 0.14.3
/Users/niku/projects% mix new timer
```

## ビルド

何もない状態でビルドしてみる．以前は`mix escriptize`というコマンド前だったのだけど，[v0.14.3になる前に変更された](https://github.com/elixir-lang/elixir/commit/2453501c8361a605682cdfb0ec4127b0b4c1a453#diff-c2c2d76e08e73ec404be79f44fa4dfef)ようだ．

```zsh
/Users/niku/projects% cd timer
/Users/niku/projects/timer% mix escript.build
Compiled lib/timer.ex
Generated timer.app
** (Mix) Could not generate escript, please set :main_module in your project configuration (under `:escript` option) to a module that implements main/1
```

うむ．`escript`の中に`main_module`を指定してねと書いてある．
これも以前は `escript_main_module`という名前だったのだけど，[v0.14.3になる前に変更された](https://github.com/elixir-lang/elixir/commit/18572678721efec22461aa85ecb0fb9ba43a13c2)ようだ．

そこで`mix.exs`に足してやる．

```ex
# in mix.exs
  def project do
    [app: :timer,
     version: "0.0.1",
     elixir: "~> 0.14.3",
     escript: [ main_module: Timer ], # add here
     deps: deps]
  end
```

もういちどビルドしてみる．

```zsh
/Users/niku/projects/timer% mix escript.build
Compiled lib/timer.ex
Generated timer.app
Generated escript timer
```

通った．

## 実行

ビルドにより`timer`というファイルができているので実行してみる．

```zsh
/Users/niku/projects/timer% ./timer
** (UndefinedFunctionError) undefined function: Timer.main/1
    (timer) Timer.main([])
    (elixir) lib/kernel/cli.ex:70: anonymous fn/3 in Kernel.CLI.exec_fun/2
```

`Timer.main(引数1)`が定義されていないとのこと．はい．定義します．

`lib/timer.ex`に足す．

```ex
# in lib/timer.ex
defmodule Timer do
  def main(args) do
    IO.puts("Hello world!")
  end
end
```

再コンパイル，再実行してみる．

```zsh
/Users/niku/projects/timer% mix escript.build
Compiled lib/timer.ex
Generated timer.app
Generated escript timer
/Users/niku/projects/timer% ./timer
Hello world!
```

おっ，よしよし．Hello worldが表示された．

# 利用

[v1.3](http://elixir-lang.org/docs/v1.3/mix/Mix.Tasks.Escript.Install.html)からはURL経由での`escript.install`ができるようになった．

`mix escript.install https://example.com/myescript`

2016-07-01現在は未リリースだが，[v1.4](http://elixir-lang.org/docs/master/mix/Mix.Tasks.Escript.Install.html)からはGit,Github,パッケージマネージャーの[hex.pm](https://hex.pm/)経由でのインストールができるようになった．

`mix escript.install git https://path/to/git/repo`
`mix escript.install github github user/project`
`mix escript.install hex hex_package`

これで公開されているコマンドラインツールを簡単に利用できるようになる．
以下では `github/niku` の `escript_sandbox` に `n` というコマンドを定義した[コードを置き](https://github.com/niku/escript_sandbox) それを利用した例だ．
(n は `Hello~` という内容を標準出力するプログラムにしている)

```zsh
/Users/niku/src/elixir-head% bin/mix --version
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] 
[kernel-poll:false] [dtrace]

Mix 1.4.0-dev (b10c4c1)
/Users/niku/src/elixir-head% bin/mix escript.install github niku/escript_
sandbox
* Getting new package (https://github.com/niku/escript_sandbox.git)
Cloning into '/private/var/folders/3x/_15gn8l57x57gr8g6cbtz6lr0000gn/T/mix-local-installer-fetcher-jzVCoQ/deps/new package'...
remote: Counting objects: 30, done.        
remote: Compressing objects: 100% (19/19), done.        
remote: Total 30 (delta 7), reused 25 (delta 5), pack-reused 0        
Checking connectivity... done.
Compiling 1 file (.ex)
Generated n app
Generated escript n with MIX_ENV=prod
Are you sure you want to install escript "n"? [Yn] 
* creating /Users/niku/.mix/escripts/n
/Users/niku/src/elixir-head% echo $PATH
/Users/niku/.mix/escripts:(...snip...)
/Users/niku/src/elixir-head% n
Hello~
```

このくらい手軽になるとコマンドラインツールとしても使いやすい．