HowTo シリーズは、一般的なエンジニアリングの問題の解決策を提供することを目的としており、多くのサンプルコードを含んでいます。今回のテーマは、コマンドライン解析ツール clap です。
依存関係の追加#
cargo add clap -F derive -F env
環境変数からパラメータ値を読み取る#
env 属性を使用すると、clap が環境変数から読み取ることができます。
use clap::Parser;
#[derive(Parser)]
struct Cmd {
#[arg(env="USER")]
user: String,
}
fn main() {
let cmd = Cmd::parse();
println!("user: {}", cmd.user);
}
echo $USER
rookie
cargo run
user: rookie
# コマンドラインで渡された値は環境変数の値を上書きします
cargo run -- xd
user: xd
パラメータの検証 / 変換#
一般的なシナリオでは、パスが渡された場合にパスの存在を検証する必要があります。
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
struct Cmd {
#[arg(value_parser=ensure_file)]
file: PathBuf,
}
fn ensure_file(path: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(path);
if path.exists() {
Ok(path)
} else {
Err("file not exists".into())
}
}
fn main() {
let cmd = Cmd::parse();
dbg!(cmd.file);
}
cargo run -- Cargo.toml
[src/main.rs:22] cmd.file = "Cargo.toml"
# ファイルが存在しない場合はエラーが発生します
cargo run -- Cargo.tomx
error: invalid value 'Cargo.tomx' for '<FILE>': file not exists
もちろん、パスを直接読み取り、特定の型にデシリアライズすることもできます。これは、設定ファイルを読み取る場合に非常に便利です。
cargo add serde -F derive
cargo add toml
use std::fs::read_to_string;
use clap::Parser;
use serde::Deserialize;
#[derive(Parser)]
struct Cmd {
#[arg(value_parser=load_conf)]
conf: Config,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub name: String,
}
fn load_conf(path: &str) -> Result<Config, String> {
let s = read_to_string(path).map_err(|e| e.to_string())?;
toml::from_str(&s).map_err(|e| e.to_string())
}
fn main() {
let cmd = Cmd::parse();
dbg!(cmd.conf);
}
echo 'name = "rookie"' > demo.toml
cargo run -- demo.toml
[src/main.rs:24] cmd.conf = Config {
name: "rookie",
}
cargo run -- demo.tomx
error: invalid value 'demo.tomx' for '<CONF>': No such file or directory (os error 2)
自動展開#
複数のサブコマンドを持つプログラムを作成する場合、異なるサブコマンドはしばしば同じパラメータを共有します。これらのパラメータを同じ構造体にまとめて管理し、#[command(flatten)]
属性を使用することで、解析時に対応するコマンドラインパラメータを自動的に展開することができます。
use std::path::PathBuf;
use clap::Parser;
#[derive(Debug, Parser)]
enum Cmd {
Fetch(FetchCmd),
Search(SearchCmd),
}
#[derive(Debug, Parser)]
struct FetchCmd {
name: String,
#[command(flatten)]
fmt: FmtOptions,
}
#[derive(Debug, Parser)]
struct SearchCmd {
pattern: String,
limit: u64,
offset: u64,
#[command(flatten)]
fmt: FmtOptions,
}
#[derive(Debug, Parser)]
struct FmtOptions {
#[arg(short, long)]
format: String,
#[arg(short, long)]
wide: u64,
#[arg(short, long)]
output: PathBuf,
}
fn main() {
let cmd = Cmd::parse();
dbg!(cmd);
}
cargo run -- fetch -h
Usage: demo fetch --format <FORMAT> --wide <WIDE> --output <OUTPUT> <NAME>
Arguments:
<NAME>
Options:
-f, --format <FORMAT>
-w, --wide <WIDE>
-o, --output <OUTPUT>
-h, --help
cargo run -- search -h
Usage: demo search --format <FORMAT> --wide <WIDE> --output <OUTPUT> <PATTERN> <LIMIT> <OFFSET>
Arguments:
<PATTERN>
<LIMIT>
<OFFSET>
Options:
-f, --format <FORMAT>
-w, --wide <WIDE>
-o, --output <OUTPUT>
-h, --help Print help
enum 値の解析#
enum は、有限の選択肢を表すために非常に便利です。clap は ValueEnum マクロを提供しており、enum の解析を簡略化することができます。
use clap::{Parser, ValueEnum};
#[derive(Debug, Parser)]
struct Cmd {
color: Color,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
let cmd = Cmd::parse();
dbg!(cmd);
}
cargo run -- -h
Usage: demo <COLOR>
Arguments:
<COLOR> [possible values: red, green, blue]
Options:
-h, --help Print help