rust从0开始写项目-06-如何接受命令行参数clap-01

写web项目或者app等,必不可少的要接受参数和校验参数的准确性,基本也是项目开始的第一步,那么我们今天来看下rust提供了哪些优秀的crates 关注 vx golang技术实验室,获取更多golang、rust好文 # 一、clap_v3 本来是想用structOpt,但是看文档是这样描述的 由于 clap v3 现已发布,并且 structopt 功能已集成(几乎按原样),因此 structopt 现在处于维护模式:不会添加新功能。 错误将被修复,文档改进将被接受。 ## 1. 1 添加依赖 ``` [dependencies] clap = { version = "4.2.7", features = ["derive","cargo"] } features = "0.10.0" cargo = "0.70.1" 或者 cargo add clap -- features cargo 需要注意:如果不启用 cargo feature ,则会报如下错误。 requires `cargo` feature ``` 如果使用`command!`、`arg!` 必须在features 中添加cargo ## 1.2 快速启动 ``` use std::env::Args; /////////////////////////////////////// clap_v3 原来的structOpt ////////////////////////////////// use std::path::PathBuf; use clap::{arg, command, value_parser, ArgAction, Command}; fn test() { let matches = command!() // requires `cargo` feature .arg(arg!([name] "Optional name to operate on")) .arg( arg!( -c --config "Sets a custom config file" ) // We don't have syntax yet for optional options, so manually calling `required` .required(false) .value_parser(value_parser!(PathBuf)), ) .arg(arg!( -d --debug ... "Turn debugging information on" )) .subcommand( Command::new("test") .about("does testing things") .arg(arg!(-l --list "lists test values").action(ArgAction::SetTrue)), ) .get_matches(); // You can check the value provided by positional arguments, or option arguments if let Some(name) = matches.get_one::("name") { println!("Value for name: {name}"); } if let Some(config_path) = matches.get_one::("config") { println!("Value for config: {}", config_path.display()); } // You can see how many times a particular flag or argument occurred // Note, only flags can have multiple occurrences match matches .get_one::("debug") .expect("Count's are defaulted") { 0 => println!("Debug mode is off"), 1 => println!("Debug mode is kind of on"), 2 => println!("Debug mode is on"), _ => println!("Don't be crazy"), } // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd if let Some(matches) = matches.subcommand_matches("test") { // "$ myapp test" was run if matches.get_flag("list") { // "$ myapp test -l" was run println!("Printing testing lists..."); } else { println!("Not printing testing lists..."); } } // Continued program logic goes here... } pub fn claps(){ test() } ``` 1、默认执行情况 ``` cargo run Debug mode is off ``` 2、参看帮助文档 ``` cargo run --help Run a binary or example of the local package Usage: cargo run [OPTIONS] [args]... Arguments: [args]... Arguments for the binary or example to run Options: -q, --quiet Do not print cargo log messages --bin [] Name of the bin target to run --example [] Name of the example target to run -p, --package [] Package with the target to run -j, --jobs Number of parallel jobs, defaults to # of CPUs. --keep-going Do not abort the build as soon as there is an error (unstable) -r, --release Build artifacts in release mode, with optimizations --profile Build artifacts with the specified profile -F, --features Space or comma separated list of features to activate --all-features Activate all available features --no-default-features Do not activate the `default` feature --target Build for the target triple --target-dir Directory for all generated artifacts --manifest-path Path to Cargo.toml --message-format Error format --unit-graph Output build graph in JSON (unstable) --ignore-rust-version Ignore `rust-version` specification in packages --timings[=] Timing output formats (unstable) (comma separated): html, json -h, --help Print help -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --color Coloring: auto, always, never --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date --offline Run without accessing the network --config Override a configuration value -Z Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details Run `cargo help run` for more detailed information. ``` 3、使用 -dd 参数 ``` cargo run -- -dd test Debug mode is on Not printing testing lists... ``` ## 1.3 command 解析器 ### 1.3.1 基本使用 ``` fn command(){ let matches = Command::new("MyApp") .version("1.0") .author("ZHangQL Z ") .about("this is the test project") .args(&[//次数是args,如果单个的的arg arg!(--config "a required file for the configuration and no short"). required(true)//必须包含 .require_equals(true)//要求使用等号赋值 // .default_value() //设置默认值 , arg!(-d --debug ... "turns on debugging information and allows multiples"), arg!([input] "an optional input file to use") ]) .arg(arg!(--two ).required(true))//单个的 .get_matches(); println!( "config: {:?}", matches.get_one::("config").expect("required") ); println!( "debug: {:?}", matches.get_one::("debug") ); println!( "input: {:?}", matches.get_one::("input") ); } ``` 查看help ``` RUST_BACKTRACE=1 cargo run -- --help this is the test project Usage: my_test [OPTIONS] --config= --two [input] Arguments: [input] an optional input file to use Options: --config= a required file for the configuration and no short -d, --debug... turns on debugging information and allows multiples --two -h, --help Print help -V, --version Print version ``` 运行 ``` RUST_BACKTRACE=1 cargo run -- --config=./config.yaml --two rrr lllll config: "./config.yaml" two: Some("rrr") input: Some("lllll") ``` ### 1.3.2 使用command!构建解析器 你也可以使用 command! 宏 构建解析器,不过要想使用 command! 宏,你需要开启 cargo feature。 ``` use clap::{arg, command}; fn main() { // requires `cargo` feature, reading name, version, author, and description from `Cargo.toml` let matches = command!() .arg(arg!(--two ).required(true)) .arg(arg!(--one ).required(true)) .get_matches(); println!( "two: {:?}", matches.get_one::("two").expect("required") ); println!( "one: {:?}", matches.get_one::("one").expect("required") ); } ``` ### 1.3.3 Command::next_line_help 使用 Command::next_line_help 方法 可以修改参数打印行为 ``` use clap::{arg, command, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .next_line_help(true) .arg(arg!(--two ).required(true).action(ArgAction::Set)) .arg(arg!(--one ).required(true).action(ArgAction::Set)) .get_matches(); println!( "two: {:?}", matches.get_one::("two").expect("required") ); println!( "one: {:?}", matches.get_one::("one").expect("required") ); } ``` ``` Usage: my_test [OPTIONS] --config= --two [input] Arguments: [input] an optional input file to use Options: --config= a required file for the configuration and no short -d, --debug... turns on debugging information and allows multiples --two -h, --help Print help -V, --version Print version ``` 效果就是:参数的描述和参数是分行的,描述信息在参数下一行。 ## 1.4 添加命令行参数(Adding Arguments) 我们可以使用 Command::arg 方法来添加 Arg 对象来添加命令行参数 ``` fn adding_arg(){ let matches = command!() .arg(Arg::new("name")) .get_matches(); println!("name: {:?}", matches.get_one::("name")); } ``` 查看help ``` RUST_BACKTRACE=1 cargo run -- --help Usage: my_test [name] Arguments: [name] Options: -h, --help Print help -V, --version Print version ``` 2、使用 name 参数:默认 ```bash cargo run name: None ``` 3、使用 name 参数:blob 注意定义的时候没有是直接使用的 不需要key的 ```bash cargo run bob name: Some("bob") ``` ### 1.4.2 设置参数行为 需要注意:参数默认值是一个 Set 类型 我们可以使用 Command::action 方法来设置 参数行为。如果可以添加多个只,我们可以使用 ArgAction::Append ```rust use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg(Arg::new("name").action(ArgAction::Append)) .get_matches(); let args = matches .get_many::("name") .unwrap_or_default() .map(|v| v.as_str()) .collect::>(); println!("names: {:?}", &args); } ``` ## 1.5 参数选项 一个参数行为的标志: - 顺序无关 - 可选参数 - 意图清晰 ``` fn arg_switch(){ let matches = command!() .arg(Arg::new("name") .short('n') .long("name") ).get_matches(); println!("name: {:?}", matches.get_one::("name")); } ``` 上述代码:我们定义了一个name参数,缩写是n,全拼是name,也就是如下形式 ``` -n, --name ``` 我们使用方式就有如下几种 ``` cargo run -- --name blo cargo run -- --name=blob cargo run -- -n blob cargo run -- -n=blob cargo run -- -nblob ``` ### 1.5.1 开启/关闭标志 我们可以是 ArgAction::SetTrue 开启参数 ``` use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg( Arg::new("verbose") .short('v') .long("verbose") .action(ArgAction::SetTrue), ) .get_matches(); println!("verbose: {:?}", matches.get_flag("verbose")); } ``` ### 1.5.2参数调用计数 我们可以使用 ArgAction::Count ``` use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg( Arg::new("verbose") .short('v') .long("verbose") .action(ArgAction::Count), ) .get_matches(); println!("verbose: {:?}", matches.get_count("verbose")); } ``` 默认值是0,多次使用参数就会计数 ``` cargo run -- --verbose --verbose ``` ### 1.5.3 默认值 ``` fn default_value(){ let matches = command!() // requires `cargo` feature .arg( arg!([PORT]) .value_parser(value_parser!(u16)) .default_value("2023"), ) .get_matches(); println!( "port: {:?}", matches .get_one::("PORT") .expect("default ensures there is always a value") ); } ``` ``` cargo run port: 2023 cargo run 897 port: 897 ``` ### 1.5.4 参数校验 默认情况下,参数被认为是 String,并且使用 UTF-8 校验。 **枚举值** ``` fn enum_check(){ let matches = command!() // requires `cargo` feature .arg( arg!() .help("What mode to run the program in") .value_parser(["fast", "slow"]), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required match matches .get_one::("MODE") .expect("'MODE' is required and parsing will fail if its missing") .as_str() { "fast" => { println!("Hare"); } "slow" => { println!("Tortoise"); } _ => unreachable!(), } } ``` ``` cargo run rrr error: invalid value 'rrr' for '' [possible values: fast, slow] cargo run fast Hare ``` 如果我们开启了 derive feature, 则我们也可以实现 ValueEnum 特征实现相同的功能 ``` use clap::{arg, builder::PossibleValue, command, value_parser, ValueEnum}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum Mode { Fast, Slow, } // Can also be derived with feature flag `derive` impl ValueEnum for Mode { fn value_variants<'a>() -> &'a [Self] { &[Mode::Fast, Mode::Slow] } fn to_possible_value<'a>(&self) -> Option { Some(match self { Mode::Fast => PossibleValue::new("fast").help("Run swiftly"), Mode::Slow => PossibleValue::new("slow").help("Crawl slowly but steadily"), }) } } impl std::fmt::Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_possible_value() .expect("no values are skipped") .get_name() .fmt(f) } } impl std::str::FromStr for Mode { type Err = String; fn from_str(s: &str) -> Result { for variant in Self::value_variants() { if variant.to_possible_value().unwrap().matches(s, false) { return Ok(*variant); } } Err(format!("invalid variant: {s}")) } } fn main() { let matches = command!() // requires `cargo` feature .arg( arg!() .help("What mode to run the program in") .value_parser(value_parser!(Mode)), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required match matches .get_one::("MODE") .expect("'MODE' is required and parsing will fail if its missing") { Mode::Fast => { println!("Hare"); } Mode::Slow => { println!("Tortoise"); } } } ``` ### 1.5.5 校验值 我们可以使用 Arg::value_parser 验证并解析成我们需要的任何类型。 ``` fn validated(){ let matches = command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(value_parser!(u16).range(1..)), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: u16 = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } cargo run 0 error: invalid value '0' for '': 0 is not in 1..=65535 cargo run 1 PORT = 10 ``` ### 1.5.6 自定义解析器 我们也可以使用自定义解析器用于改进错误信息提示和额外的验证。 ``` use std::ops::RangeInclusive; use clap::{arg, command}; fn main() { let matches = command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(port_in_range), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: u16 = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } const PORT_RANGE: RangeInclusive = 1..=65535; fn port_in_range(s: &str) -> Result { let port: usize = s .parse() .map_err(|_| format!("`{s}` isn't a port number"))?; if PORT_RANGE.contains(&port) { Ok(port as u16) } else { Err(format!( "port not in range {}-{}", PORT_RANGE.start(), PORT_RANGE.end() )) } } ``` ### 1.5。7 参数关系(Argument Relations) 我们可以声明 Arg 和 ArgGroup。ArgGroup 用于声明参数关系。 ``` use std::path::PathBuf; use clap::{arg, command, value_parser, ArgAction, ArgGroup}; fn main() { // Create application like normal let matches = command!() // requires `cargo` feature // Add the version arguments .arg(arg!(--"set-ver" "set version manually")) .arg(arg!(--major "auto inc major").action(ArgAction::SetTrue)) .arg(arg!(--minor "auto inc minor").action(ArgAction::SetTrue)) .arg(arg!(--patch "auto inc patch").action(ArgAction::SetTrue)) // Create a group, make it required, and add the above arguments .group( ArgGroup::new("vers") .required(true) .args(["set-ver", "major", "minor", "patch"]), ) // Arguments can also be added to a group individually, these two arguments // are part of the "input" group which is not required .arg( arg!([INPUT_FILE] "some regular input") .value_parser(value_parser!(PathBuf)) .group("input"), ) .arg( arg!(--"spec-in" "some special input argument") .value_parser(value_parser!(PathBuf)) .group("input"), ) // Now let's assume we have a -c [config] argument which requires one of // (but **not** both) the "input" arguments .arg( arg!(config: -c ) .value_parser(value_parser!(PathBuf)) .requires("input"), ) .get_matches(); // Let's assume the old version 1.2.3 let mut major = 1; let mut minor = 2; let mut patch = 3; // See if --set-ver was used to set the version manually let version = if let Some(ver) = matches.get_one::("set-ver") { ver.to_owned() } else { // Increment the one requested (in a real program, we'd reset the lower numbers) let (maj, min, pat) = ( matches.get_flag("major"), matches.get_flag("minor"), matches.get_flag("patch"), ); match (maj, min, pat) { (true, _, _) => major += 1, (_, true, _) => minor += 1, (_, _, true) => patch += 1, _ => unreachable!(), }; format!("{major}.{minor}.{patch}") }; println!("Version: {version}"); // Check for usage of -c if matches.contains_id("config") { let input = matches .get_one::("INPUT_FILE") .unwrap_or_else(|| matches.get_one::("spec-in").unwrap()) .display(); println!( "Doing work using input {} and config {}", input, matches.get_one::("config").unwrap().display() ); } } ``` 此时 --set-ver |--major|--minor|--patch 是一个组的参数。 ### 1.5.8 自定义校验(Custom Validation) 我们可以创建自定义校验错误 Command::error 方法可以返回指定错误 Error和自定义错误信息 ``` use std::path::PathBuf; use clap::error::ErrorKind; use clap::{arg, command, value_parser, ArgAction}; fn main() { // Create application like normal let mut cmd = command!() // requires `cargo` feature // Add the version arguments .arg(arg!(--"set-ver" "set version manually")) .arg(arg!(--major "auto inc major").action(ArgAction::SetTrue)) .arg(arg!(--minor "auto inc minor").action(ArgAction::SetTrue)) .arg(arg!(--patch "auto inc patch").action(ArgAction::SetTrue)) // Arguments can also be added to a group individually, these two arguments // are part of the "input" group which is not required .arg(arg!([INPUT_FILE] "some regular input").value_parser(value_parser!(PathBuf))) .arg( arg!(--"spec-in" "some special input argument") .value_parser(value_parser!(PathBuf)), ) // Now let's assume we have a -c [config] argument which requires one of // (but **not** both) the "input" arguments .arg(arg!(config: -c ).value_parser(value_parser!(PathBuf))); let matches = cmd.get_matches_mut(); // Let's assume the old version 1.2.3 let mut major = 1; let mut minor = 2; let mut patch = 3; // See if --set-ver was used to set the version manually let version = if let Some(ver) = matches.get_one::("set-ver") { if matches.get_flag("major") || matches.get_flag("minor") || matches.get_flag("patch") { cmd.error( ErrorKind::ArgumentConflict, "Can't do relative and absolute version change", ) .exit(); } ver.to_string() } else { // Increment the one requested (in a real program, we'd reset the lower numbers) let (maj, min, pat) = ( matches.get_flag("major"), matches.get_flag("minor"), matches.get_flag("patch"), ); match (maj, min, pat) { (true, false, false) => major += 1, (false, true, false) => minor += 1, (false, false, true) => patch += 1, _ => { cmd.error( ErrorKind::ArgumentConflict, "Can only modify one version field", ) .exit(); } }; format!("{major}.{minor}.{patch}") }; println!("Version: {version}"); // Check for usage of -c if matches.contains_id("config") { let input = matches .get_one::("INPUT_FILE") .or_else(|| matches.get_one::("spec-in")) .unwrap_or_else(|| { cmd.error( ErrorKind::MissingRequiredArgument, "INPUT_FILE or --spec-in is required when using --config", ) .exit() }) .display(); println!( "Doing work using input {} and config {}", input, matches.get_one::("config").unwrap().display() ); } } ``` ## 1.6、子命令(Subcommand) 我们可以使用 Command::subcommand 方法添加子命令。每一个子命令都自己的版本、作者、参数和它的子命令。 ``` use clap::{arg, command, Command}; fn main() { let matches = command!() // requires `cargo` feature .propagate_version(true) .subcommand_required(true) .arg_required_else_help(true) .subcommand( Command::new("add") .about("Adds files to myapp") .arg(arg!([NAME])), ) .get_matches(); match matches.subcommand() { Some(("add", sub_matches)) => println!( "'myapp add' was used, name is: {:?}", sub_matches.get_one::("NAME") ), _ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"), } } ``` 我们使用 Command::arg_required_else_help 如果参数不存在,优雅的退出。 使用 Command::propagate_version 可以打印命令的版本号 ## 1.7、测试 我们可以使用 debug_assert! 宏 或者 使用 Command::debug_assert 方法。 ``` use clap::{arg, command, value_parser}; fn main() { let matches = cmd().get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: usize = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } fn cmd() -> clap::Command { command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(value_parser!(usize)), ) } #[test] fn verify_cmd() { cmd().debug_assert(); } ``` 本文由[mdnice](https://mdnice.com/?platform=6)多平台发布
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,807评论 6 518
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,284评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 169,589评论 0 363
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,188评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,185评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,785评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,220评论 3 423
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,167评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,698评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,767评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,912评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,572评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,254评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,746评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,859评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,359评论 3 379
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,922评论 2 361

推荐阅读更多精彩内容