作って学ぶmix、echoコマンド


ElixirでCLI(Command Line Interface)を作るには、mixを使う必要があるようです。

今回はmixを使ってechoコマンド作り、
「mixとは何で、CLIを作るのに最低限必要なことは何か」を理解しようと思います。
 
※mixを使わずにCLIを作る方法があったら教えてください:bow:

# mixとは何か

mixのhelpを見てみます。

~~~sh
$ mix --help
Mix is a build tool for Elixir

Usage: mix [task]

Examples:

    mix             - Invokes the default task (mix run) in a project
    mix new PATH    - Creates a new Elixir project at the given path
    mix help        - Lists all available tasks
    mix help TASK   - Prints documentation for a given task

The --help and --version options can be given instead of a task for usage and versioning information.
~~~

まま訳すと、

- mixはElixirのビルドツール
- 「mix」でデフォルトのtaskを実行する
- 「mix new PATH」で、projectを作る
- 「mix help」で、すべてのtaskを一覧する
- 「mix TASK」で、taskのドキュメントを出力する

ということみたいです。ざくざく進みたいので、早速mix newを実行してみます。

## mix new

mix newでechoプロジェクトを作ります。

~~~sh
$ mix new echo
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/echo.ex
* creating test
* creating test/test_helper.exs
* creating test/echo2_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd echo
    mix test # mixのTASKにはtestがあるということが分かります。

$ cd echo
$ tree -a
.
├── .formatter.exs
├── .gitignore
├── README.md
├── lib
│   └── echo.ex # echoモジュール
├── mix.exs
└── test # テストがデフォルトで組み込まれているようです。
    ├── echo_test.exs # テストのスクリプト
    └── test_helper.exs # テストのヘルパー
~~~

次にecho.exを見てみます。

~~~sh
$ cat lib/echo.ex
defmodule Echo do
  @moduledoc """
  Documentation for Echo.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Echo.hello()
      :world

  """
  def hello do
    :world
    IO.puts "hello" # 追加！！！
  end
end
~~~

Echoモジュールが定義されており、hello関数があります。
※出力させたいので、一行追加しています。

実行してみましょう。

~~~sh
$ mix run -e 'Echo.hello()' # mix run help または プログラミングElixirのp.136 参照
Compiling 1 files (.ex)
Generated echo app
hello # 出力されました。
~~~

## echoコマンドの実装

echoコマンド作るには以下を行う必要があります。

- 「引数をうけたら、それをそのまま標準出力に吐く」ロジックを実装する
- それを実行できるファイルを作る

それを行う上で分かっていないことは以下です。

1. echoコマンド用のロジックはどこで実装すべきか
1. 実行ファイルをどのように作るか、その引数はどのようにロジックに渡されるか

順番に解決しましょう。

### どこで実装すべきか

プログラミングElixirのp.133から抜粋すると（IssuesをEchoに置換しています）

> Elixirには規約がある。lib/ディレクトリの中に、プロジェクトと同じ名前のサブディレクトリを作る（つまり、lib/echo/を作る。）。このディレクトリは、アプリケーションの主なソースコードが、一つのモジュールごとに一つのファイルとしておかれる。それぞれのモジュールは、Echoモジュールの中にネームスペースを確保することになる。つまり、モジュールの名前は、ディレクトリ名に対応する。
> 今回の場合、作りたいモジュールは、Echo.CLI、つまりEchoモジュールの下に入れ子になったCLIモジュールだ。これをディレクトリ構造に反映して、cli.exをlib/echoディレクトリに置こう。

※この規約に関してWEB上のドキュメントを探しましたが見つけることはできませんでした。

規約ということなので以下に実装します。

~~~sh
├── lib
│   ├── echo
│   │   └── cli.ex
│   └── echo.ex
~
~~~

### 実行ファイルと引数について

調べると、実行ファイルを作るには「[mix escript.build](https://hexdocs.pm/mix/master/Mix.Tasks.Escript.Build.html)」を使うようです。
プログラミングElixir p.149参照

実行前に以下を追加します。

- mix.exsにescriptの設定を追加します。
- cli.exにmain関数

~~~mix.exs
defmodule Echo.MixProject do
  use Mix.Project

  def project do
    [
      app: :echo,
      version: "0.1.0",
      elixir: "~> 1.9",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      escript: escript() # 追加！！！
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
    ]
  end

  # 追加！！！
  def escript do
    [main_module: Echo.CLI]
  end
end
~~~

~~~cli.ex
defmodule Echo.CLI do
  # main関数の引数にコマンドライン呼び出し時の引数が渡るようです。
  # 確認しましょう。
  def main(argv) do
    IO.inspect argv 
  end
end
~~~

実行ファイルを作成してみます。

~~~sh
$ mix escript.build
Compiling 1 file (.ex)
Generated escript echo with MIX_ENV=dev
$ ls -lht
合計 1.1M
-rwxrwxr-x 1 pojiro pojiro 1.1M 11月  7 21:08 echo # できました
drwxrwxr-x 3 pojiro pojiro 4.0K 11月  7 16:41 lib
-rw-rw-r-- 1 pojiro pojiro  643 11月  7 15:36 mix.exs
drwxrwxr-x 2 pojiro pojiro 4.0K 11月  7 15:20 test
drwxrwxr-x 4 pojiro pojiro 4.0K 11月  7 15:12 _build
-rw-rw-r-- 1 pojiro pojiro  482 11月  7 14:01 README.md
~~~

引数を渡して実行してみます。

~~~sh
$ ./echo test test
["test", "test"]
~~~

コマンドライン呼び出しの引数は文字列のリストとして渡るようです。
cli.exを修正しましょう。

~~~cli.ex
defmodule Echo.CLI do
  def main(argv) do
    argv |> Enum.join(" ") |> IO.puts
  end
end
~~~

確認します。

~~~sh
$ mix escript.build
Compiling 1 file (.ex)
Generated escript echo with MIX_ENV=dev
$ ./echo test test
test test # ばっちり
~~~

いきなり、echoコマンドが完成しました！やった！

# まとめ

## 分かったこと

CLIをつくるためには最低限以下を知れば十分ということを理解しました。

- mix newでプロジェクトをつくる
- lib/Module/cil.exにコマンドラインの引数を受けるmain関数と動作ロジックを実装する
- mix escript.buildを実行し、実行ファイルを作る

## 取り扱わなかったこと

echoコマンドを作ることを優先するために以下をあえて取り扱いませんでした。

- 引数のparseライブラリ、OptionParser
- テストの実装、実行するmix test

## わからなかったこと

残念ながら以下が追いきれませんでした。教えていただけると嬉しいです。

- mixを使わない実行ファイルの作成方法が別にあるのか
- echo.exはどのような機能を実装すべきファイルなのか
- mixのプロジェクト構造の規約のドキュメントはあるのか

# おわり

**「いいね」よろしくお願いします。**:wink: