缘起 主要对go每日一库 和Rust每周一库 的拾人牙慧,不过学习嘛,不寒碜。哈哈。
今天是第一天,我们从命令行参数解析库——Clap
开始。
简介 相较于golang
,rust
的解析命令参数等非语言核心能力并非是在核心库中直接提供的,而是由三方库提供。
三方库就会有一个选择问题,特别是对于不熟悉的新手。
我们可以通过lib.rs
网站中Categories
中选择Command-line interface
来查看对应的库排名来初步选择。
如:https://lib.rs/keywords/argument
对于参数解析,使用量最大的就是Clap
仓库:https://github.com/clap-rs/clap
文档:https://docs.rs/clap/3.0.0-beta.5/clap/
视频教程:https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U
快速使用 将clap
作为依赖项加进Cargo.toml
中
1 2 [dependencies] clap ="2.33.3"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 extern crate clap;use clap::{App, Arg};fn main () { let matched = App::new("MyApp" ) .arg(Arg::with_name("config" ) .short("c" ) .long("config" ) .value_name("FILE" ) .help("demo arg for cmd" ) ) .get_matches(); if let Some (c) = matched.value_of("config" ) { println! ("value of config: {}" , c); } println! ("hello" ); }
不用参数运行
带参数运行
1 2 3 $ cargo run -- --config haha value of config: haha hello
自带的帮助信息
1 2 3 4 5 6 7 8 9 10 11 12 $ cargo run -- -h MyApp USAGE: basic [OPTIONS] FLAGS: -h, --help Prints help information -V, --version Prints version information OPTIONS: -c, --config <FILE> demo arg for cmd
可以看出来自带的帮助信息还可以安放更多的元信息,接下来就让我们来尽量尝试一下(篇幅过长,请选择性使用)
1 2 3 4 5 6 7 App::new("MyApp" ) .version("1.0" ) .author("Chaos Mo" ) .before_help("===haha before help===" ) .after_help("===haha after help===" ) .about("This is demo app for clap" ) .help_message("Print custom help information" )
运行效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ cargo run --bin basicmeta -- -h ===haha before help=== MyApp 1.0 Chaos Mo This is demo app for clap USAGE: basicmeta [OPTIONS] FLAGS: -h, --help Print custom help information -V, --version Prints version information OPTIONS: -c, --config <FILE> demo arg for cmd ===haha after help=== $ cargo run --bin basicmeta -- -V MyApp 1.0
更多的方法,请参看文档。
参数类型 Rust是一门强类型且类型安全的静态语言
在clap
中,只要你的目标参数类型是实现了std::str::FromStr
,就可以使用转换宏将其转换成目标类型。同时支持单个,多个(Vec<T>
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 extern crate clap;use clap::{App, value_t, values_t};fn main () { let matches = App::new("myapp" ) .args_from_usage( "<seq>... 'A sequence of whole positive numbers, i.e. 20 25 30' --len -l [len] 'A length to use, defaults to 10 when omitted' --offline -o [isoffline] 'is computer offline'" , ) .get_matches(); let len: u32 = value_t!(matches, "len" , u32 ).unwrap_or(10 ); println! ("len ({}) + 2 = {}" , len, len + 2 ); let online: bool = value_t!(matches, "offline" , bool ).unwrap_or(false ); if online { println! ("computer is online" ); } else { println! ("computer is offline" ); } for v in values_t!(matches, "seq" , u32 ).unwrap_or_else(|e| e.exit()) { println! ("Sequence part {} + 2: {}" , v, v + 2 ); } }
运行结果
1 2 3 4 5 6 $ cargo run --bin typedvalue -- 1 2 3 --len 12 -o true len (12) + 2 = 14 computer is online Sequence part 1 + 2: 3 Sequence part 2 + 2: 4 Sequence part 3 + 2: 5
测试不满足的类型安全的输入参数
1 2 3 4 $ cargo run --bin typedvalue -- 1 2 3.1 --len 12 -o true len (12) + 2 = 14 computer is online error: Invalid value: The argument '3.1' isn't a valid value
总结:
利用value_t
及values_t
可以将对应的参数转化为需要的类型。若类型不满足,则需要通过对Result
进行判断。
选项格式 clap库支持三种命令
1 2 3 -o --optimize=3 --optimize 3
-表示短选项,–表示长选项,同时支持多个重复参数,重复参数格式支持-o -o -o
、-oo
等多种形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 extern crate clap;use clap::{App, Arg};fn main () { let matches = App::new("DemoApp" ) .version("1.0" ) .author("Chaos Mo" ) .about("Demo App About Information" ) .arg( Arg::with_name("config" ) .short("c" ) .long("config" ) .value_name("FILE" ) .help("demo arg for cmd" ), ) .arg( Arg::with_name("optimize" ) .short("o" ) .long("optimize" ) .multiple(true ) .help("Sets the level of optimize" ), ) .get_matches(); if let Some (c) = matches.value_of("config" ) { println! ("value of config: {}" , c); } match matches.occurrences_of("optimize" ) { 0 => println! ("No optimize" ), 1 => println! ("Basic optimize" ), 2 => println! ("Better optimize" ), 3 | _ => println! ("Are you kidding me?" ), } }
运行效果
1 2 3 $ cargo run --bin styledargs -- --config=haha -o --optimize value of config: haha Better optimize
clap支持多重子参数嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 extern crate clap;use clap::{App, Arg, SubCommand};fn main () { let matches = App::new("DemoApp" ) .subcommand( SubCommand::with_name("install" ) .about("controls install features" ) .version("1.2" ) .author("Chaos Mo" ) .arg( Arg::with_name("force" ) .short("f" ) .long("force" ) .help("install with force arg" ), ), ) .get_matches(); if let Some (matches) = matches.subcommand_matches("install" ) { if matches.is_present("force" ) { println! ("force is not right..." ); } else { println! ("general install..." ); } } }
运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ cargo run --bin subdemo -- install -h subdemo-install 1.2 Chaos Mo controls install features USAGE: subdemo install [FLAGS] FLAGS: -f, --force install with force arg -h, --help Prints help information -V, --version Prints version information $ cargo run --bin subdemo -- install -f force is not right...
另外,clap
也支持直接使用dsl
形式的参数字符串解析模式。
1 [explicit name] [short] [long] [value names] [help string]
另一种定义选项的方式 clap
支持使用YAML
文件来构建CLI
并保持Rust
源代码整洁或者通过为每个本地化使用不同的YAML
文件来支持多个本地化翻译,但是此方法会有略微损失一些性能。
这里的依赖与其他例子有稍许区别
1 clap ={version="2.33.3" , features=["yaml" ]}
先创建一个cli.yml
保存选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 name: ymldemo version: "1.1" author: Chaos Mo about: Demo App for Yaml Cli args: - config: short: c long: config value_name: FILE help: demo arg for cmd takes_value: true subcommands: - install: about: controls install features version: "1.2" author: Chaos Mo args: - force: short: f long: force help: force to install features
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #[macro_use] extern crate clap;use clap::App;fn main () { let yaml = load_yaml!("cli.yml" ); let matches = App::from_yaml(yaml).get_matches(); let config = matches.value_of("config" ).unwrap_or("default.conf" ); println! ("Value for config: {}" , config); if let Some (matches) = matches.subcommand_matches("install" ) { if matches.is_present("force" ) { println! ("force is not right..." ); } else { println! ("general install..." ); } } }
测试 一个优秀的代码,都是可以进行良好测试的。
虽然解析逻辑应该不属于业务核心逻辑,但Clap
提供了良好的测试方式来进行必要的测试途径。
默认的get_matches
实际上是接受 env::args_os
输入作为参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 extern crate clap;use std::{error::Error, str ::Matches};use clap::{App, Arg};fn main () { let app = construct_app(); let matches = app.get_matches(); let config = matches.value_of("config" ).unwrap_or("default.conf" ); println! ("Value for config: {}" , config); match matches.occurrences_of("optimize" ) { 0 => println! ("No optimize" ), 1 => println! ("Basic optimize" ), 2 => println! ("Better optimize" ), 3 | _ => println! ("Are you kidding me?" ), } println! ("hello" ); } fn construct_app () -> App<'static , 'static > { App::new("testclap" ) .about("about test clap information" ) .author("Chaos Mo" ) .arg( Arg::with_name("config" ) .short("c" ) .long("config" ) .takes_value(true ) .value_name("FILE" ) .help("config information" ), ) .arg( Arg::with_name("optimize" ) .short("o" ) .long("optimize" ) .multiple(true ) .help("optimize level" ), ) }
对于这块代码的单元测试,我们可以使用get_matches_from
来进行手动灌注参数调用。
1 2 3 4 5 6 7 8 #[test] fn test_parse () { let arg_vec = vec! ["--" , "-oo" , "-c" , "haha" ]; let matches = construct_app().get_matches_from(arg_vec); assert! (matches.is_present("optimize" )); assert_eq! (matches.occurrences_of("optimize" ), 2 ); assert_eq! (matches.value_of("config" ), Some ("haha" )); }
参考 【Rust每周一库】Clap - 强大的命令行参数解析+帮助说明生成库