13章はビルドツールのMixの話。最近は言語ごとにビルドツールがあるのが当たり前になった。
このタイミングで自分のMacにElixirをインストールすることにする。brew install elixir
するだけ。
いろいろ依存で入るので、そこそこ時間がかかる。というかPython3.8と3.9を両方インストールしようとするのは何故なのか。
そして、Rubyはクリスマスに出たばっかりの3.0.0_1が入るw
では、さっそくP.143に従って、mix new issues
する。issuesというプロジェクト名なのは、
GitHubからIssueのリストを取ってくるアプリを作ってみようというサンプルだから。
libとconfig, testというディレクトリが作られる。
まず、コマンドライン引数の処理を作る。 規約として、プロジェクト名.CLIというモジュールを作るらしい。 ファイルとしては、lib/$project_name/cli.exになる。なるほど。
とりあえず、今は本のcli.exをそのまま写経する。写経しているときに浮かんだ理解や疑問点をメモっておく。
- javadoc的なものをモジュールの属性として書くのは面白い。
- モジュールのスタートポイントをrunという関数にするのは慣習?
- parse_argsがdefpじゃないのは何でだろう。
- OptionParseの戻り値の扱いが荒っぽくみえるけど、これで十分なのかな?
そして、作ったものをExUnitというテストフレームワークでテストする。testディレクトリ以下にcli_test.exsを作る。 こっちは.exじゃなくて.exsなのね。面白い。
- doctestは何を意味しているんだろう?
- importでparse_args/1だけを対象にしている。parse_argsがdefpじゃなかったのはこのため?
そして、テストが出来た時点で、リファクタリングする。caseのパターンマッチ1つ1つを関数にする。 これがわかりやすいのかどうかはわかんないけど、そういう流儀なんだね。
次に、GitHubにアクセスする部分を作る。httpクライアントが必要だ。 RubyのRubyGemsやNode.jsのnpmみたいなものとして、Hexがある。 ただし、これはErlangとElixirの両方にとってのパッケージみたい。 全然シンタックスの違う言語だけど、共用できるものなのかしらん?
それはともかく、ここからhttpクライアントを探してインストールする。 著者はHTTPoisonというのを使うといっているのでそれを選ぶ。 Mix用の依存記述があるので、mix.exsのdepsのところにコピペする。 このあたりは、Maven Reposなんかと同じなので、戸惑うことはない。
mix depsで依存性チェックが行われる。実際に取得するにはmix deps.getする。
> mix deps Could not find Hex, which is needed to build dependency :httpoison Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn] Y * creating /Users/tambara/.mix/archives/hex-0.20.6 * httpoison (Hex package) the dependency is not available, run "mix deps.get" > mix deps.get Resolving Hex dependencies... Dependency resolution completed: New: certifi 2.5.3 hackney 1.17.0 httpoison 1.7.0 idna 6.1.1 metrics 1.0.1 mimerl 1.2.0 parse_trans 3.3.0 ssl_verify_fun 1.1.6 unicode_util_compat 0.7.0 * Getting httpoison (Hex package) * Getting hackney (Hex package) * Getting certifi (Hex package) * Getting idna (Hex package) * Getting metrics (Hex package) * Getting mimerl (Hex package) * Getting parse_trans (Hex package) * Getting ssl_verify_fun (Hex package) * Getting unicode_util_compat (Hex package)
なるほど。
というわけで、これを使ってIssues.GithubIssues.fetchを実装する。また写経。
写経ができたら、今度はIExで動作を確かめる。iex -S mix
でmixでの依存性を解決した上で起動される。
> iex -S mix Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Could not find "rebar3", which is needed to build dependency :parse_trans I can install a local copy which is just used by Mix Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn] Y * creating /Users/tambara/.mix/rebar * creating /Users/tambara/.mix/rebar3 ===> Compiling parse_trans ===> Compiling mimerl ===> Compiling metrics ===> Compiling unicode_util_compat ===> Rebar3 detected a lock file from a newer version. It will be loaded in compatibility mode, but important information may be missing or lost. It is recommended to upgrade Rebar3. ===> Compiling idna ==> ssl_verify_fun Compiling 7 files (.erl) Generated ssl_verify_fun app ===> Compiling certifi ===> Rebar3 detected a lock file from a newer version. It will be loaded in compatibility mode, but important information may be missing or lost. It is recommended to upgrade Rebar3. ===> Compiling hackney ==> httpoison Compiling 3 files (.ex) Generated httpoison app ==> issues Compiling 3 files (.ex) == Compilation error in file lib/issues/github_issues.ex == ** (SyntaxError) lib/issues/github_issues.ex:18:29: keyword argument must be followed by space after: status_code: (elixir 1.11.2) lib/kernel/parallel_compiler.ex:314: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
途中でインストールを要求されているrebar3はErlangのビルドツール。素直に入れる。 おっと、コンパイルエラーになってるわ。直してリトライ。
> iex -S mix Erlang/OTP 23 [erts-11.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Compiling 3 files (.ex) Generated issues app Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Issues.GithubIssues.fetch("elixir-lang", "elixir") {:ok, "[{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621\",\"repository_url\":\"https://api.github.com/repos/elixir-lang/elixir\",\"labels_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/comments\",\"events_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10621/events\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10621\",\"id\":777551967,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NTQ3ODE2NzU5\",\"number\":10621,\"title\":\"Update to a more descriptive typespec in Macro.Env\",\"user\":{\"login\":\"eksperimental\",\"id\":9133420,\"node_id\":\"MDQ6VXNlcjkxMzM0MjA=\",\"avatar_url\":\"https://avatars2.githubusercontent.com/u/9133420?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/eksperimental\",\"html_url\":\"https://github.com/eksperimental\",\"followers_url\":\"https://api.github.com/users/eksperimental/followers\",\"following_url\":\"https://api.github.com/users/eksperimental/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/eksperimental/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/eksperimental/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/eksperimental/subscriptions\",\"organizations_url\":\"https://api.github.com/users/eksperimental/orgs\",\"repos_url\":\"https://api.github.com/users/eksperimental/repos\",\"events_url\":\"https://api.github.com/users/eksperimental/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/eksperimental/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2021-01-03T01:04:21Z\",\"updated_at\":\"2021-01-03T01:04:21Z\",\"closed_at\":null,\"author_association\":\"MEMBER\",\"active_lock_reason\":null,\"pull_request\":{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/pulls/10621\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10621\",\"diff_url\":\"https://github.com/elixir-lang/elixir/pull/10621.diff\",\"patch_url\":\"https://github.com/elixir-lang/elixir/pull/10621.patch\"},\"body\":\"\",\"performed_via_github_app\":null},{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620\",\"repository_url\":\"https://api.github.com/repos/elixir-lang/elixir\",\"labels_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/comments\",\"events_url\":\"https://api.github.com/repos/elixir-lang/elixir/issues/10620/events\",\"html_url\":\"https://github.com/elixir-lang/elixir/pull/10620\",\"id\":777551630,\"node_id\":\"MDExOlB1bGxSZXF1ZXN0NTQ3ODE2NDk2\",\"number\":10620,\"title\":\"Fix cryptic error when defimpl is defined without :for option\",\"user\":{\"login\":\"eksperimental\",\"id\":9133420,\"node_id\":\"MDQ6VXNlcjkxMzM0MjA=\",\"avatar_url\":\"https://avatars2.githubusercontent.com/u/9133420?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/eksperimental\",\"html_url\":\"https://github.com/eksperimental\",\"followers_url\":\"https://api.github.com/users/eksperimental/followers\",\"following_url\":\"https://api.github.com/users/eksperimental/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/eksperimental/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/eksperimental/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/eksperimental/subscriptions\",\"organizations_url\":\"https://api.github.com/users/eksperimental/orgs\",\"repos_url\":\"https://api.github.com/users/eksperimental/repos\",\"events_url\":\"https://api.github.com/users/eksperimental/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/eksperimental/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":0,\"created_at\":\"2021-01-03T01:01:34Z\",\"updated_at\":\"2021-01-03T01:01:34Z\",\"closed_at\":null,\"author_association\":\"MEMBER\",\"active_lock_reason\":null,\"pull_request\":{\"url\":\"https://api.github.com/repos/elixir-lang/elixir/pulls/10620\",\"html_url\":\"https://github.com/elixir-la" <> ...}
なんか動いているっぽい。今回はここまで。