Apple のオープンソースライブラリ ArgumentParser

[Swift\.org \- Announcing ArgumentParser](https://swift.org/blog/argument-parser/)

2月末に Swift.org でアナウンスされた [ArgumentParser](https://github.com/apple/swift-argument-parser) をいろいろと触ってみたので紹介的なものを書いてみようと思います。

基本的にはわかりやすい英語とサンプルコードで書かれている [本家 Documentation](https://github.com/apple/swift-argument-parser/blob/master/Documentation/) が一次情報なのでそちらを読んでください。リポジトリには [動作するサンプルコード](https://github.com/apple/swift-argument-parser/tree/master/Examples) もあるのでそちらを動かすのもおすすめです。

この記事の執筆時点では [ver 0\.0\.2](https://github.com/apple/swift-argument-parser/releases/tag/0.0.2) がリリースされていますが僕が試したのは v0.0.1 なのでご了承ください。

## はじめに

3rd Party 製の Command Line Tool 向けの Argument Parser だったりフレームワークのようなものは以前からありました（[参考](https://qiita.com/moriai/items/e77238487290b7f61867)）。Apple 自身も Swift Package Manager 内部で使われていた ArgumentParser （名前が同じでややこしい）は存在しており、[apple/swift\-tools\-support\-core](https://github.com/apple/swift-tools-support-core) （以下 TSC）として切り出されていました(参考: [\[Obsoleted\] ArgumentParserが便利すぎる件 \[SwiftPM\] ](https://qiita.com/masakihori/items/7dfd189bfc83819277be)\) 。今回アナウンスされたものは TSC の ArgumentParser とも別物の単体のオープンソースライブラリとしてリリースされました。

特徴としては Swift 5.1 で追加された Property Wrapper を使ってコマンドのパラメータとプロパティの対応が宣言的に書けるようなライブラリになっています。ある種のフレームワーク的なものとしても捉えられ、仕組みに沿ってコードを書いていけば簡単にコマンドラインツールが書けるようになっています。おかげで本質的な処理部分に時間をかけられるのはうれしいところです。

Apple が作っているいくつかのオープンソースプロダクトは TSC から [ArgumentParser](https://github.com/apple/swift-argument-parser) に乗り換える方向のようです。（[`indexstore-db`](https://github.com/apple/indexstore-db/pull/72) と [`swift-format`](https://github.com/apple/swift-format/pull/154) で PR が出されていてまだマージはされていない）

今回は僕がドキュメントを読んだり少し触った範囲で気になったことを書いていきます。

僕の背景として、公開はしていませんが5個ほど TSC を使ったコマンドラインツールを個人的に書いたことがあります。他の言語だと Go と Node.js で少しコマンドラインツールを使ったことがあります。

## できること

- よくあるパターンの引数のパース処理を Property Wrapper を使って簡単に書ける
	- フラグ
        - `--verbose` みたいなただのフラグ・スイッチ的なオプションの指定方法
	- オプション
        - `swift build --configuration debug` の `--configuration debug` のように名前付きの引数みたいなオプションの指定方法
	- 引数
        - `ls ~/` みたいに何もつかない引数
- help もデフォで最低限は生成される
    - `-h` `--help` がデフォルトで実装される
- エラーメッセージもデフォで最低限を返してくれる
    - 型チェックとか必須パラメータの不足など
- ParsableCommand に準拠した型でコマンドを定義する
    - `func run() throws` メソッド内にコマンドの処理を書く
- 実行自体は static メソッド `main` を呼ぶだけで、引数のパースや ParsableCommand の run をよしなに呼んでくれる
- オプションのショートネームも定義できる
    - Swift で一般的な Lower camel case は自動的に Kebab case に変換されたオプション名になる
        - `@Flag() var printSupportedFile: Bool` は `--print-supported-file`
    - `@Flag(name: .shortAndLong)  var printSupportedFile: Bool` とすれば自動的に頭文字を使ったショートネームが追加されて `-p`, `--print-supported-file` が使えるようになる
    - `@Flag(name: [.customShort("s") .customLong("support-file")])  var printSupportedFile: Bool` とすれば自動生成じゃなく自分で任意のパラメータ名にできる。この場合は `-s`, `--support-file` が使えるようになる
- long オプションは基本的に `--` だけど `withSingleDash: true` すれば `-` 形式にできるけど推奨はされていない
- 独自の型もパースできる（してもらえる）
    - `ExpressibleByArgument` に準拠して `init?(argument:)` を実装する
    - enum も RawRepresentable に対応してるような奴は `ExpressibleByArgument` に準拠してると宣言するだけで済む。
    - [transform 関数](https://github.com/apple/swift-argument-parser/blob/master/Documentation/02%20Arguments%2C%20Options%2C%20and%20Flags.md#parsing-custom-types)を実装していればパースできたりもする
- フラグの反転も指定できる
    - `@Flag(inversion: .prefixedNo) var index: Bool` だと `--index` と `--no-index` が定義される
    - `@Flag(inversion: .prefixedEnableDisable)` だと `--enable-hogehoge` と `--disable-hogehoge`
- CaseIterable と String の RawRepresentable に対応した enum を使えばフラグをまとめたりとかもできる
- Flag オプションを Int 型に適用すると呼び出された回数が入ってくる
    - `-vvvv` とかしたら verbose プロパティの値は 4 になってるみたいなことができる
- 引数のパース方法は選べるけどほとんどの場合ややこしいことになる
    - `@Option(parsing: . upToNextOption) var file: [String]` のケースだけはちょっと便利かもしれない。毎回 --file hoge ってしなくても連続して --file のオプションを渡せるから。
- サブコマンドの実装ができる
    - ツリー上にパースされていく
    - コマンド名は基本的に型名が使われるが以下のように明示もできる
     ```swift
     static var configuration = CommandConfiguration(
            commandName: "stdev",
            abstract: "Print the standard deviation of the values.")
     ```
    - サブコマンドのサブコマンドも作れる
    - 共通のオプション（設定）は `ParsableArguments` に準拠した Struct とかを用意して、`@OptionGroup() var options: Math.Options` と書くことでまとめられる
        - ちょっとオプションの多いコマンドを作ったときにはオプションのカテゴライズをしてまとめられてコードが見やすくなるし、排他処理も書きやすくなる
- ヘルプのカスタマイズ
    - @Flagとかの help 引数に文字列を明示的にあたえることでコメントを追加できる
    - より細かいカスタムは上記で渡していた文字列の代わりに `ArgumentHelp` のインスタンスを渡すことで可能
    - コマンドやサブコマンドのヘルプは `static var configuration = CommandConfiguration( XXXX )` でできる
    - デフォルトの `-h`, `--help` のパラメータ名はカスタム可能。Root レベルのコマンドに対する CommandConfiguration で helpName: で指定可能
    - ヘルプに出力したくない場合には `@Flag(help: .hidden)` とかすれば、試験的に追加した機能とかをヘルプに出力せずにすませられる
- バリデーション
    - `ParsableCommand` と `ParsableArguments` に `validate()` を実装することでバリデーションできる
    - throw すればそのメッセージが標準エラーに出力されてエラーコードで終了する
    - runメソッド内でも実行時エラーを throw できる
        - v0.0.2 では `ExitCode` という Error が用意されたので自前でエラーメッセージを出力して `throw ExitCode.failure` することで綺麗にエラーからの終了が実装できるようになった
- マニュアルでパース
    - きっちりとコマンドラインツールとして作るのではなくスクリプトっぽく書きたいとき
        - `ParsableArguments` に準拠した struct にこれまど同様にオプションを書いておく
        - static method の `parseOrExit()` を呼べば、成功してればインスタンスが返ってくるし失敗したら終了してくれる
        - `parse` メソッドもあってこちらは throw されるので自分でエラーハンドリングができる。
    - コマンドやサブコマンドもマニュアルでパースできるけどややめんどくさい `parseAsRoot()` とかつかえばいい

## できないこと(提供されてないこと)

- コマンドラインツール的に便利なパスの処理
- 外部のコマンドの呼び出しのサポート

これらはライブラリの名前から連想する対応範囲を超えているように思えるので別のライブラリなどを使った方が良いかもしれないです。僕は個人的にパス周りは [Path.swift](https://github.com/mxcl/Path.swift), パスまわりや外部のコマンド呼び出しは前述の TSC を使ったりしています。

それ以外は特別困っていません。

## まとめ

コマンドラインツールはいろんな宗派があると思うので好みが分かれると思います。細かいことを言えば切りがないかも知れません。とはいえ

- Apple 純正
- サブコマンドも含めてコマンドラインツール作成における大枠の仕組み、パース処理が提供されている
- 独特な実装ではなく Swift らしい素直な実装

Swift でコマンドラインツールを書く場合、これらは利点だと思うのでぜひ皆さんも使って見たらいいと思います。