ElixirでCLIを作る時の知見をまとめました

# MagicalMakeというmake commandをかっこよく叩けるCLIを作りました。
今回はこちらの実装で得た知見を記しておきます。

https://github.com/tashirosota/magical_make

## ElixirでCLIを作る
### 1. mix new <project name>

まず、`mix new` で雛形を作成します。

```shell
$ mix new sample
$ cd sample
$ tree
├── .formatter.exs
├── .gitignore
├── README.md
├── lib # ライブラリの中身
│   └── dummy.ex
├── mix.exs # Projectのconfig本体のようなもの
└── test # テスト
    ├── sample.exs
    └── test_helper.exs
```
取り急ぎ必要そうなものを、中身が空でもいいので配置しておきましょう。
+ `TODO:` 等入れておくと良さそうです。作業進捗を管理がしやすくなる為です。

追加したファイルやディレクトリに関しては以下コメントで説明を付けておきます。

```shell
$ tree
├── .github/workflows
│   └── ci.yml # github actionsはあったほうがいい
├── CHANGELOG.md # 0.1.0 initial releaseだけ書いておく
├── Dockerfile # CLIでは必要ない気もするけど、localでのtestの環境を整えたいので
├── LICENSE.md # 正直良くわかってないけど一応
├── Makefile # ライブラリ的に必要
├── README.md
├── docker-compose.yml # makeでdocker-compose叩いておきたかったので
├── lib
│   ├── sample # ざっくりと設計方針を決めてしまうとタスク的に実装していける
│   │   ├── cli.ex # CLIを実装する時に用いる
│   │   ├── painter.ex
│   │   └── system_command.ex
│   └── sample.ex
├── mix.exs
├── priv # 静的ファイルはここで管理する https://qiita.com/sotashiro/private/4f39b07260dc3af429b2
│   └── assets
└── test # 正常系だけでもテストを書いておくと、再設計時に壊れにくくなるので、実装が楽
    ├── sample
    │   ├── cli_test.ex
    │   ├── painter_test.ex
    │   └── system_command_test.ex
    ├── sample_test.exs
    └── test_helper.exs
```

### 2. CLIが動くようにするには
Elixir標準の `escript` という機能を用いてCLIを実装します。

> escriptで実行ファイルを作るために、必要な事はほんの少ししかありません。 main/1 関数を実装し、Mixfileを更新するというものです。

https://elixirschool.com/ja/lessons/intermediate/escripts
 
```elixir
# 1. cliでの呼び出し時に通す関数を定義する
# lib/sample/cli
defmodule Sample.CLI do
  def main(args \\ []) do
    # TODO: 実装 
    args |> IO.inspect
  end
end

# 2. escriptのmain_moduleに先程の関数を指定する
# mix.exs
defmodule MagicalMake.MixProject do
  use Mix.Project

  def project do
    [
      app: :sample,
      escript: escript(), # 追加
      ...
    ]
  end

  defp escript do
    [main_module: Sample.CLI, name: :sample_cli] # nameで指定したcommandが呼び出せるようになります。d
  end
end
```

最低限の実装はこれだけでOKです
次にCLIとして使えるよう、buildしていきます。

```shell
$ mix escript.build
Generated escript mgc with MIX_ENV=dev
$ ./sample test
["test"]
```

めちゃめちゃ簡単！

### 3. 処理を実装しましょう
頑張ってCLIを実装してください。

magical_makeでの実装内容に関しては別記事にまとめましたので、
そちらで参考に出来る部分があるかもしれません。

https://qiita.com/sotashiro/private/07de02582988dac1a666

また、静的ファイルの扱いでハマった部分がありましたので、そちらは別記事にまとめさせていただきました。

https://qiita.com/sotashiro/private/4f39b07260dc3af429b2

### 4. githubで公開する
escriptを用いて作成されたCLIは `mix escript.install` コマンドを用いてinstallすることが出来ます。
この時のoptionでgithubを指定することによってCLIの配給をすぐに開始することが出来ます。

```shell
$ mix escript.install github account/sample
# あれこれinstall log
* creating /Users/user_name/.mix/escripts/sample
$ /Users/user_name/.mix/escripts/sample aaa
["aaa"]
```

このままだと呼び出し時のpathが不便です。
PATHを通すとコマンドを直接呼ぶことが出来るようになります。

```shell
$ export PATH=$PATH:~/.mix/escripts
$ sample aaa
["aaa"]
```

### 5. hexで公開する
mixが用意しているCLIを用いてUser情報を登録していきます。
こちらは対話形式で登録が出来ます。
最終的に登録したメールアドレスへ認証メールが届くので、それをクリックすれば、登録完了です。

```
$ mix hex.user register
Username: name
Email: name@example.com
Account password: 
Account password (confirm):
Registering...
Generating keys...
You are required to confirm your email to access your account, a confirmation email has been sent to name@example.com
```

次にmix.exsに必要な情報を書き込みます。

```elixir
# mix.exs
defmodule Sample.MixProject do
    use Mix.Project
  @versoin "0.1.0"
  @source_url "https://github.com/account/sample"
  @description "Description"

  def project do
    [
      app: :magical_make,
      version: @versoin,
      elixir: "~> 1.12",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      escript: escript(),
      description: @description, # 追加
      name: "MagicalMake", # 追加
      source_url: @source_url, # 追加
      package: package(), # 追加
      docs: [ # docsでmainを指定してあげると、hexでのtopを変更することが出来る。hexの都合上小文字
        main: "readme",
        extras: ["README.md"]
      ]
    ]
  end

  def application do
    [
      extra_applications: [:logger]
    ]
  end

  defp escript do
    [main_module: Sample.CLI, name: :sample_cli]
  end

  defp package() do # 追加
    [
      licenses: ["Apache-2.0"],
      maintainers: ["your name"],
      links: %{"GitHub" => @source_url}
    ]
  end
end
```

準備完了です。
次にリリースコマンドを叩きます。

```
$ mix hex.publish
# ...build log
# 上書きしたい場合
$ mix hex.publish --replace
```

インストールから先はgithubを用いた方法を同様なので割愛します。

```
$ mix escript.install hex sample
```

### 5.5 `escript.install` の方法色々
githubやhex以外にもlocal pathからのinstallも対応しているようです。
debugはlocal pathで事前に済ませておくのが良さそうですね。

```shell
$ mix help escript.install 
...
mix escript.install escript
mix escript.install path/to/escript
mix escript.install git https://path/to/git/repo
mix escript.install git https://path/to/git/repo branch git_branch
mix escript.install git https://path/to/git/repo tag git_tag
mix escript.install git https://path/to/git/repo ref git_ref
mix escript.install github user/project
mix escript.install github user/project branch git_branch
mix escript.install github user/project tag git_tag
mix escript.install github user/project ref git_ref
mix escript.install hex hex_package
mix escript.install hex hex_package 1.2.3
...
```

### 6. Homebrewで公開する
前述の方法ですと、Elixirが使える環境がある前提での配給になってしまいます。
やっぱりHomebrewでも出したいです。
正式な方法ですと、Homebrew本体にPRを出してリリースすることになります。
ハードルも、かかる時間も増えてしまいます。今回は`brew tap`を用いてライブラリを提供する方法（公式外の提供方法）をご紹介致します。
公式外の提供とは言え、brewコマンドは普通に使えますので、ご安心ください。
#### 1. githubでタグを作成し、archiveのURLを取得します

```shell
$ git tag -f -a v0.1.0 -m "Version 0.1.0" # localでタグ付け
$ git push origin --tags # githubへ反映
```

#### 2. gihtubのブラウザ画面右部 > Releases > Tags > tar.xzのURLをコピー

![スクリーンショット 2022-01-01 14.55.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253317/875a4872-effd-159a-c127-ea983ee8f86c.png)


#### 3. localでhomebrew用のconfigファイルの作成

```shell
$ brew create https://github.com/account_name/sample/archive/refs/tags/v0.1.0.tar.gz # 先程コピーしたURL
```

sample.rbというファイルが開かれるので必要項目を入力していきます。
以下magical_makeを例として記載します。

```rb
class MagicalMake < Formula
  desc "Command-line tool to decorate your make commands with Magic Circle"
  homepage "https://github.com/tashirosota/magical_make"
  url "https://github.com/tashirosota/magical_make/archive/refs/tags/v0.1.1.tar.gz"
  sha256 "e1ad8a7e347efc0d05ae06e996b4204b8892062172a69bab049d78a9595e0635"
  license "Apache-2.0"
  
  # 依存関係がある場合は以下記述.
  depends_on "elixir" => :build
  depends_on "erlang"

  def install # install時に実行される
    system "mix", "local.hex", "--force"
    system "mix", "deps.get"
    system "mix", "escript.build"
    bin.install "mgc"
  end

  test do
    system "mgc", "--help"
  end
end
```

#### 4. github上でhomebrew-<任意の文字列>でリポジトリーを作成

```shell
$ tree
.
├── Formula
│   └── magical_make.rb # 3で作成したファイルをこちらに移動
└── README.md
```

#### 5. brew install
準備完了です。
後はbrew installを用いて入れてみてください。

```shell
$ brew install account_name/homebrew-<任意の文字列>/sample
...
$ sample aaa
["aaa"]
```

注意として、mix escript.installで作った同名のコマンドが存在する場合は、事前にuninstallするようにしておきましょう

```
$ mix escript.uninstall sample
```

## 以上。
Elixirでも比較的簡単にCLIの作成が行えます。皆でライブラリを沢山作って、携わってElixirを盛り上げていきましょう。
brew tapに関しては他言語でも同様ですので参考になりましたら幸いです。