こんにちは。この記事はNE Advent Calendar2日目の記事です。一年は早いですね。今年も色んなことがありました。ベースを再始動したり、ピアノを買ったり、比較的登山とかもできた気がしますが、何一つ身になっていない気もします。
とはいえ、食生活を変えたり、運動をし始めたりという良い習慣の流れはつくられ始めたので、また来年も続けられるといいなぁ、健康のトピックが家族親族で増えてきたのはやはり加齢のせいかもなぁと淡々と感じています。
さて、Rustのコマンドオプション解析のCrateである Clap の設定方法ががよく分からなくなるのでチップス的にアトリビュートの情報を書いておきます。
clapとは
Rustで作られたコマンドもよく使われるようになったかと思いますが、必ず必要なのに引数のオプション解析が面倒くさいです。その解析部分の面倒をよく見てくれるCrateです。デフォルトでは std::env::args
、同じようなCrateに getopts
などがあります。
clapにはメソッドチェーン形式でオプションの定義を書いていく Builder形式
(リファレンス) と、マクロで宣言的に書いていく Derive形式
(リファレンス) が使えます。Derive形式の方が冗長にならなくていいのかな、という所感で、本記事でもそちらしか書いていません。
clapの最新は2023/12/02時点の最新、4.4.10を使っています。
準備
cargo new clap_test
などで適当なプロジェクトを作り、 cargo add clap --features derive
でderive形式も含めて使えるようにしておきます。さらにこの example を src/main.rc
に書いて、色々変えていきながら表示を試します。
use clap::Parser;
/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Name of the person to greet
#[arg(short, long)]
name: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 1)]
count: u8,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Hello {}!", args.name)
}
}
これをcargo runするともう現時点でオプションのフォーマットを整えて出力してくれます。
$ cargo run -- --help
Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
Finished dev [unoptimized + debuginfo] target(s) in 3.59s
Running `target/debug/clap_test --help`
Simple program to greet a person
Usage: clap_test [OPTIONS] --name <NAME>
Options:
-n, --name <NAME> Name of the person to greet
-c, --count <COUNT> Number of times to greet [default: 1]
-h, --help Print help
-V, --version Print version
多幸感ある。
#[derive(Parser)]
Args構造体に clap の Parserトレイト を実装します。これによってこの構造体が引数の定義としておおむね扱えるようになります。
let args = Args::parse();
先の例では実装されたParserを実行し、ここで name
オプションと count
オプションを引数に取れる構造体を解析した、ということ。
#[command(author)]
このコマンドのauthorを設定します。
#[command(author = "Masakuni Ito")]
とも書けるし、書かなかった場合はCargo.tomlの authors
を自動的に参照してくれるようです。ただ、このauthorは現状、基本的に出力されない模様で、テンプレート変更時などに利用できるようです。
#[command(version)]
このコマンドのバージョンを指定します。値を指定しなければCargo.tomlの値が参照されます。
#[command(version = "0.0.100")]
上記のように値を書くこともできて、この場合、Cargo.tomlより優先されますが、まぁ、あんまりここで指定することはないんじゃないかなぁ、という感想。
masakuni@mi-air % cargo run -- --version
Blocking waiting for file lock on build directory
Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
Finished dev [unoptimized + debuginfo] target(s) in 3.67s
Running `target/debug/clap_test --version`
clap_test 0.0.100
#[command(about)]
このコマンドの概要を書きます。マクロ、Cargo.toml、ドキュメントコメントで指定することができて、それぞれ下記の優先度のようです。Cargo.tomlは about
ではなく、 description
の値が使われます。
# マクロで直接指定する
#[command(about = "一番優先されるぽい")]
# Cargo.tomlのdescription
description = "二番目に優先されるぽい"
# src/main.rc上部に書かれていたドキュメントコメントのサマリ
/// 三番目に優先されるぽい
$ cargo run -- --help
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/clap_test --help`
一番優先されるぽい
Usage: clap_test [OPTIONS] --name <NAME>
Options:
-n, --name <NAME> Name of the person to greet
-c, --count <COUNT> Number of times to greet [default: 1]
-h, --help Print help
-V, --version Print version
じゃあ全部書かれてなかった場合はどうなるんだというと、何も記載されなくなります。ドキュメントコメントまで読んでくれるあたりがとても賢い。
#[command(long_about = None)]
コマンドのヘルプが長めに書けるようになります。 --help
を指定したとき、オプションの説明がさらに丁寧なものになります。これもドキュメントコメントまで読んでくれます。
#[command(long_about = "ここに長めのコマンドの説明を書くことができます。\n様々な情熱に動かされながらこのコマンドを作ったストーリーを語ってもよいでしょう。")]
というように書くと
cargo run -- --help
Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
Finished dev [unoptimized + debuginfo] target(s) in 2.44s
Running `target/debug/clap_test --help`
ここに長めのコマンドの説明を書くことができます。
様々な情熱に動かされながらこのコマンドを作ったストーリーを語ってもよいでしょう。
Usage: clap_test [OPTIONS] --name <NAME>
Options:
-n, --name <NAME>
Name of the person to greet
-c, --count <COUNT>
Number of times to greet
[default: 1]
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
というようなオプションになります。
#[arg(short)]
短縮されたオプションを自動的に受け入れるようになります。冒頭のスクリプトのオプションだと name
と count
のオプションが自動的に -n
や -c
で受け取れるようになります。
他にもArgsの構造体を
struct Args {
#[arg(short, long)]
first: bool,
#[arg(short, long)]
second: bool,
#[arg(short, long)]
third: bool,
}
のようにboolのオプションを受け取るようなものだった場合、
cargo run -- -fst
一文字だけの連続でオプションを設定可能にできたりします。
#[arg(long)]
正式なオプション名でオプションを受け取れるようにします。つまり --
(ハイフン2つ)の形式でオプションを受け取ります。ちなみにこれがなかった場合、ハイフン2つでのオプションを受け取ることができません。へー。
$ cargo run -- --count 1 --name masa
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/clap_test --count 1 --name masa`
error: unexpected argument '--count' found
Usage: clap_test [OPTIONS] -n <NAME>
For more information, try '--help'.
#[arg(default_value_t = 1)]
オプションのデフォルト値を設定しておきます。この場合は1。 _t
は何なんだ? というと型指定されているという意味らしく、例えばこの場合は count
の u8
が指定されるようです。この辺に言及があります。
要するに
/// Number of times to greet
#[arg(short, long, default_value_t = "abcd")]
count: u8,
こういうことをやると
$ cargo run -- --count 1 --name masa
Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
error[E0308]: mismatched types
--> src/main.rs:17:42
|
17 | #[arg(short, long, default_value_t = "abcd")]
| ^^^^^^ expected `u8`, found `&str`
18 | count: u8,
| -- expected due to this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `clap_test` (bin "clap_test") due to 2 previous errors
ビルドエラーにしてくれます。しかし、
/// Number of times to greet
#[arg(short, long, default_value = "abcd")]
count: u8,
default_value
のアトリビュートを使うと、型まできっちり見てくれず、この場合でもビルドは通ってしまいます。このため default_value_t
の方が推奨、とのことです。
まだ指定できるものがありすぎるが
宣言的に書かれたオプションの構造体を Parser::Parse()
で解析することで自在に取得できるclapすごい、というところでおしまい。ドキュメントコメントまで読んでくれるところがとても良いですね。