この記事の要点
- Swift でコマンドラインツールの引数を解析するための新しいオープンソースライブラリ ArgumentParser(swift-argument-parser)が発表されました。
- 引数として受け取りたい値を、型に対する
@Argument/@Option/@Flagといったプロパティラッパーとして宣言するだけで、解析・型変換・バリデーション・ヘルプ表示が自動で組み立てられます。定型コードを手で書く必要がありません。 - 単純なスクリプトから、サブコマンドを持つ複雑なツールまで段階的に対応できます。Swift プロジェクト自体(SwiftPM など)への採用も進められる予定です。
何が発表されたのか
ArgumentParser は、Swift でコマンドライン引数を解析するためのオープンソースライブラリです。引数の解析は多くのコマンドラインツールに共通して必要になりますが、従来は手作業で書くことが多く、定型コードや記述ミスの温床になりがちでした。ArgumentParser は、Swift の型システムやプロパティラッパー、リフレクションといった機能を活用し、利用者が宣言した型から自動的にインターフェースを組み立てることで、この手間を取り除きます。
設計にあたっては次の目標が掲げられています。
- コマンドラインインターフェースのベストプラクティスを促し、ライブラリの段階的な理解(progressive understanding)を支援すること。
- 単純な使い捨てスクリプトから、ネストしたサブコマンドや豊富なヘルプ情報を備えた複雑なツールまで、幅広いプロジェクトに対応できること。
- 引数解析につきものの定型コードをなくし、繰り返しとエラーの余地を減らすこと。
何に使えるのか
たとえば、1 から指定した値までの乱数を表示する random というツールを考えます。ParsableCommand に適合する型を定義し、引数にしたいプロパティに @Argument を付けるだけで実装できます。
import ArgumentParser
struct Random: ParsableCommand {
@Argument() var highValue: Int
func run() {
print(Int.random(in: 1...highValue))
}
}
Random.main()
@Argumentプロパティラッパーは、そのプロパティをコマンドライン引数から取り込むことを示します。ParsableCommand型でmain()を呼ぶと解析が始まり、成功すればツールが実行されます。- ヘルプやエラーメッセージは、プロパティ名・型・コマンド型名などの情報から自動生成されます。
highValueがIntとして宣言されているため、手動での解析やキャストなしに、有効な入力だけが受け付けられます。
> random 20
17
> random ZZZ
Error: The value 'ZZZ' is invalid for '<high-value>'
Usage: random <high-value>
バリデーションとヘルプのカスタマイズ
CommandConfiguration で説明文を加え、validate() メソッドを実装すると、入力の検証と豊富なドキュメントを備えられます。
struct Random: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Chooses a random number between 1 and your input.")
@Argument(help: "The highest value to pick.")
var highValue: Int
func validate() throws {
guard highValue >= 1 else {
throw ValidationError("'<high-value>' must be at least 1.")
}
}
func run() {
print(Int.random(in: 1...highValue))
}
}
これにより、不正な値を弾きつつ、--help で次のような整形済みのヘルプ画面が自動表示されます。
> random --help
OVERVIEW: Chooses a random number between 1 and your input.
USAGE: random <high-value>
ARGUMENTS:
<high-value> The highest value to pick.
OPTIONS:
-h, --help Show help information.
サブコマンド
Git や Swift Package Manager のように、関連する機能をサブコマンドとしてコマンドツリーにまとめることもできます。各サブコマンドを個別の型として宣言し、ルートコマンドの configuration の subcommands に列挙します。
たとえば乱数生成のロジックを Number 型に移し、入力した一覧から要素を選ぶ Pick 型を追加します。@Option プロパティラッパーは、プロパティ名をキーとしてキーと値のペアの形で引数を読み取ることを示します。
struct Random: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Randomness utilities.",
subcommands: [Number.self, Pick.self])
// ...
struct Pick: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Picks random elements from your input.")
@Option(default: 1, help: "The number of elements to choose.")
var count: Int
@Argument(help: "The elements to choose from.")
var elements: [String]
func validate() throws {
guard !elements.isEmpty else {
throw ValidationError("Must provide at least one element.")
}
}
func run() {
let picks = elements.shuffled().prefix(count)
print(picks.joined(separator: "\n"))
}
}
}
ArgumentParser はユーザーが指定したサブコマンドを自動で判別し、その引数を解析して run() を呼び出します。サブコマンドを省略した場合は、ルートコマンドの既定の run() 実装が呼ばれ、ヘルプ画面が表示されます。
> random number 100
79
> random pick --count 3 Fuji Gala Cameo Honeycrisp McIntosh Braeburn
Honeycrisp
Cameo
Braeburn
このほか、ArgumentParser は真偽値や列挙型のプロパティに対する --flag 形式の引数、オプションやフラグへの複数名の付与、引数グループのカプセル化なども備えています。
今後の位置づけ
Swift プロジェクトには Swift で書かれたコマンドラインツールがいくつもあり、その引数解析は SwiftPM の TSCUtility ライブラリ内のパーサーなどが担ってきました。これは SwiftPM の必要に応じて育ってきたもので、広く使われることを意図したものではありませんでした。今後は ArgumentParser を Swift プロジェクト全体に採用していく計画で、SwiftPM での採用が完了したら、Swift コンパイラドライバの Swift による書き直しでの採用も予定されています。
あわせて、サーバーや Windows など多様な環境での広範な採用に向けて、1.0 リリースに必要な要件をコミュニティとともに定義していくことが目指されています。