clapでサブコマンドを再利用する
clap-verbosity-flagというログ出力を変更するフラグ(-v
や--debug
)を再利用するcrateがRust CLI WGにあるのを見つけたので、そのアイデアを使ってみようという話です。
clap-verbosity-flag crate
ほとんどのCLIアプリケーションはその出力を制御するフラグとして大まかに次のようなオプションを提供します:
-
-q
(--quiet
): ほとんどの情報を出さない -
-v
(--verbose
): なるべく情報を出す -
--debug
: 可能な限り多くの情報を出す
Rustではライブラリはlog crateを通してユーザーに知らせるべき情報を表示させますが、CLIアプリケーションを書く際はこれをその重要度に応じてフィルターする事が常です。多くのプロジェクトではこのフィルターの設定を上記のフラグによって調整しています。この処理はいつでも同じなので、再利用できるはずです。
use clap::Parser;
use clap_verbosity_flag::Verbosity;
/// Foo
#[derive(Debug, Parser)]
struct Cli {
#[clap(flatten)]
verbose: Verbosity,
}
fn main() {
let cli = Cli::parse();
env_logger::Builder::new()
.filter_level(cli.verbose.log_level_filter())
.init();
log::error!("Engines exploded");
log::warn!("Engines smoking");
log::info!("Engines exist");
log::debug!("Engine temperature is 200 degrees");
log::trace!("Engine subsection is 300 degrees");
}
これによってCLIにオプション引数が追加され、さらにその結果をVerbosity::log_level_filter
関数で適切な引数に変換しています。
clap-vergen crate
CLIアプリケーションに問題がある時、それがいつのコードをどうやってコンパイルしたものかを調べる事が問題の解決への近道です。そのためにはアプリケーションにビルド時の情報を含めておく必要があります。それを行ってくれるのがvergen crateです:
これはbuild.rs
中でその時点の時刻やrustcやGitの情報を読み取ってcargo:rustc-env
を使って以降のビルドプロセス中の環境変数に書き込みます。アプリケーションはそれをenv!
を使って実行バイナリに埋め込みます。
埋め込まれた情報をCLIアプリケーションに表示させるには他の機能に混ぜ込むより独立したサブコマンドを用意するといいでしょう。この目的の為にversion
サブコマンドを作るのがclap-vergenの目的です。
clapのDerive APIを使って次のように書きます:
use clap::Parser;
use clap_vergen::Version;
#[derive(Debug, clap::Parser)]
enum Cli {
Version(Version),
}
fn main() {
match Cli::from_args() {
Cli::Version(version) => {
version.print().unwrap();
}
}
}
Cli
enumに他のサブコマンドを作る想定です。これでversion
サブコマンドが作られます:
$ ./target/debug/main version --help
main-version
Output detail version of executable
USAGE:
main version [OPTIONS]
OPTIONS:
-h, --help Print help information
--json Output version info as JSON
現在は標準の出力方法とJSON出力がサポートされています:
$ ./target/debug/main version
Build Timestamp: 2022-08-06T08:16:05.843030928Z
Build Version: 0.1.0
Commit SHA: f1af7e4b9fc58b7aa73b1e14a617d9a341a9880d
Commit Date: 2022-08-06T08:12:22Z
Commit Branch: main
rustc Version: 1.63.0-beta.8
rustc Channel: beta
rustc Host Triple: x86_64-unknown-linux-gnu
rustc Commit SHA: 7410ebb8f69516d0034cc99793bc3dcbc84d4a9b
cargo Target Triple: x86_64-unknown-linux-gnu
cargo Profile: debug
$ ./target/debug/main version --json
{
"build_timestamp": "2022-08-06T08:16:05.843030928Z",
"build_semver": "0.1.0",
"rustc_channel": "beta",
"rustc_commit_date": "2022-08-04",
"rustc_commit_hash": "7410ebb8f69516d0034cc99793bc3dcbc84d4a9b",
"rustc_host_triple": "x86_64-unknown-linux-gnu",
"rustc_llvm_version": "14.0",
"rustc_semver": "1.63.0-beta.8",
"cargo_features": "default",
"cargo_profile": "debug",
"cargo_target_triple": "x86_64-unknown-linux-gnu",
"git_branch": "main",
"git_commit_timestamp": "2022-08-06T08:12:22Z",
"git_semver": "0.1.0",
"git_sha": "f1af7e4b9fc58b7aa73b1e14a617d9a341a9880d"
}
このようにアプリケーション側のコードではclap_vergen::Version
の定義を全く見ること無く、CLIオプションの生成とそれを受け取った後の処理まで実装できます。
Discussion