上篇我们介绍了 如何在app内使用clap接受参数,本文介绍下如何使用struct 优雅的接收和校验数据
关注 vx golang技术实验室,获取更多golang、rust好文
二、derive feature方式
个人更喜欢这种方式,代码看起来更简洁
2.1 添加依赖
cargo add clap --features derive
这里是不需要cargo的
2.2 快速开始
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Clis {
/// Optional name to operate on
name: Option<String>,
/// Sets a custom config file
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
/// Turn debugging information on
#[arg(short, long, action = clap::ArgAction::Count)]
debug: u8,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// does testing things
Test {
/// lists test values
#[arg(short, long)]
list: bool,
},
}
fn quick_test(){
let cli = Clis::parse();
if let Some(name) = cli.name.as_ref(){
println!("value for name {name}")
}
if let Some(config_path) = cli.config.as_deref(){
println!("value for config: {:?}",config_path)
}
match cli.debug {
0 => println!("debug mode is off"),
1=> println!("debug mode is kind of on"),
2=>println!("debug mode is on"),
_=> println!("dont be crazy")
}
match &cli.command {
Some(Commands::Test {list})=>{
if *list{
println!("printing testing lists...")
}else{
println!("not printing testing lists...")
}
}
None=>{
}
}
}
查看help
RUST_BACKTRACE=1 cargo run -- --help
Compiling my_test v0.1.0 (/Users/zhangqiuli24/Desktop/rust/my_test)
Finished dev [unoptimized + debuginfo] target(s) in 5.63s
Running `target/debug/my_test --help`
Usage: my_test [OPTIONS] [NAME] [COMMAND]
Commands:
test does testing things
help Print this message or the help of the given subcommand(s)
Arguments:
[NAME] Optional name to operate on
Options:
-c, --config <FILE> Sets a custom config file
-d, --debug... Turn debugging information on
-h, --help Print help
-V, --version Print version
调用
RUST_BACKTRACE=1 cargo run -- name -c ./config.toml -d test --list
value for name name
value for config: "./config.toml"
debug mode is kind of on
printing testing lists...
2.2 配置解析器
我们可以是 Parse 属性开启构建解析器
#[derive(Parser)]
#[command(name="MyApp")]
#[command(author="ZHangQL")]
#[command(version="1.0")]
#[command(about="这是测试的一些东西",long_about=None)]
struct Clic{
#[arg(long)]
two:String,
#[arg(long)]
one:String
}
fn parse(){
let cli = Clic::parse();
println!("value for two {:?}",cli.two);
println!("value for one {:?}",cli.one)
}
help
RUST_BACKTRACE=1 cargo run -- --help
Compiling my_test v0.1.0 (/Users/zhangql/Desktop/rust/my_test)
Finished dev [unoptimized + debuginfo] target(s) in 6.34s
Running `target/debug/my_test --help`
这是测试的一些东西
Usage: my_test --two <TWO> --one <ONE>
Options:
--two <TWO>
--one <ONE>
-h, --help Print help
-V, --version Print version
cargo run -- -V
MyApp 1.0
我们也可使用使用 #[command(author, version, about)] 形式从 Cargo.toml 读取配置消息
读取的是cargo.toml的[package]
信息
#[derive(Parser)]
#[command(author, version, about, long_about = None)] // Read from `Cargo.toml`
struct Cli {
#[arg(long)]
two: String,
#[arg(long)]
one: String,
}
fn main() {
let cli = Cli::parse();
println!("two: {:?}", cli.two);
println!("one: {:?}", cli.one);
}
2.3 Command::next_line_help 换行
我们可以使用 #[command(next_line_help = true)] 方法替代 Command::next_line_help
RUST_BACKTRACE=1 cargo run -- --help
Finished dev [unoptimized + debuginfo] target(s) in 0.98s
Running `target/debug/my_test --help`
Usage: my_test --two <TWO> --one <ONE>
Options:
--two <TWO>
--one <ONE>
-h, --help
Print help
-V, --version
Print version
cargo run -- -V
my_test 0.1.0
2.4 添加可选参数
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(next_line_help = true)]
struct CliW{
name:Option<String>
}
fn switch(){
let cli = CliW::parse();
println!("value for name {:?}",cli.name);
}
cargo run -- --help
Usage: my_test [NAME]
Arguments:
[NAME]
Options:
-h, --help
Print help
-V, --version
Print version
RUST_BACKTRACE=1 cargo run --
value for name None
RUST_BACKTRACE=1 cargo run -- kkk
value for name Some("kkk")
2.5 添加多值参数
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(next_line_help = true)]
struct CliW{
name:Option<String>,
more : Vec<String>
}
fn switch(){
let cli = CliW::parse();
println!("value for name {:?}",cli.name);
println!("value for more {:?}",cli.more);
}
RUST_BACKTRACE=1 cargo run -- --help
Usage: my_test [NAME] [MORE]...
Arguments:
[NAME]
[MORE]...
Options:
-h, --help
Print help
-V, --version
Print version
RUST_BACKTRACE=1 cargo run -- kkk more1 more2 m3
value for name Some("kkk")
value for more ["more1", "more2", "m3"]
2.6 长短名
我们可以使用 #[arg(short = ‘n’)] 和 #[arg(long = “name”)] 属性设置参数的短名称和长名称
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short, long)]
name: Option<String>,
}
fn main() {
let cli = Cli::parse();
println!("name: {:?}", cli.name.as_deref());
}
2.7 开启和关闭
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short, long)]
verbose: bool,
}
fn main() {
let cli = Cli::parse();
println!("verbose: {:?}", cli.verbose);
}
需要注意我们默认调用的是clap::ArgAction::SetTrue
2.8 参数计数
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
fn main() {
let cli = Cli::parse();
println!("verbose: {:?}", cli.verbose);
}
2.9 参数默认值
我们使用 #[arg(default_value_t)] 属性来给参数设置默认值
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(default_value_t = 2020)]
port: u16,
}
fn main() {
let cli = Cli::parse();
println!("port: {:?}", cli.port);
}
2.10 参数枚举
我们使用 #[arg(value_enum)] 设置参数枚举 结合枚举类
use clap::{Parser, ValueEnum};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// What mode to run the program in
#[arg(value_enum)]
mode: Mode,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
/// Run swiftly
Fast,
/// Crawl slowly but steadily
///
/// This paragraph is ignored because there is no long help text for possible values.
Slow,
}
fn main() {
let cli = Cli::parse();
match cli.mode {
Mode::Fast => {
println!("Hare");
}
Mode::Slow => {
println!("Tortoise");
}
}
}
2.11 参数校验
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Network port to use
#[arg(value_parser = clap::value_parser!(u16).range(1..))]
port: u16,
}
fn main() {
let cli = Cli::parse();
println!("PORT = {}", cli.port);
}
2.12 自定义解析
use std::ops::RangeInclusive;
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Network port to use
#[arg(value_parser = port_in_range)]
port: u16,
}
fn main() {
let cli = Cli::parse();
println!("PORT = {}", cli.port);
}
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
fn port_in_range(s: &str) -> Result<u16, String> {
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()
))
}
}
2.13 参数关系
use clap::{Args, Parser};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(flatten)]
vers: Vers,
/// some regular input
#[arg(group = "input")]
input_file: Option<String>,
/// some special input argument
#[arg(long, group = "input")]
spec_in: Option<String>,
#[arg(short, requires = "input")]
config: Option<String>,
}
#[derive(Args)]
#[group(required = true, multiple = false)]
struct Vers {
/// set version manually
#[arg(long, value_name = "VER")]
set_ver: Option<String>,
/// auto inc major
#[arg(long)]
major: bool,
/// auto inc minor
#[arg(long)]
minor: bool,
/// auto inc patch
#[arg(long)]
patch: bool,
}
fn main() {
let cli = Cli::parse();
// 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 vers = &cli.vers;
let version = if let Some(ver) = vers.set_ver.as_deref() {
ver.to_string()
} else {
// Increment the one requested (in a real program, we'd reset the lower numbers)
let (maj, min, pat) = (vers.major, vers.minor, vers.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 let Some(config) = cli.config.as_deref() {
let input = cli
.input_file
.as_deref()
.unwrap_or_else(|| cli.spec_in.as_deref().unwrap());
println!("Doing work using input {input} and config {config}");
}
}
2.14 自定义校验
use clap::error::ErrorKind;
use clap::{CommandFactory, Parser};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// set version manually
#[arg(long, value_name = "VER")]
set_ver: Option<String>,
/// auto inc major
#[arg(long)]
major: bool,
/// auto inc minor
#[arg(long)]
minor: bool,
/// auto inc patch
#[arg(long)]
patch: bool,
/// some regular input
input_file: Option<String>,
/// some special input argument
#[arg(long)]
spec_in: Option<String>,
#[arg(short)]
config: Option<String>,
}
fn main() {
let cli = Cli::parse();
// 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) = cli.set_ver.as_deref() {
if cli.major || cli.minor || cli.patch {
let mut cmd = Cli::command();
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) = (cli.major, cli.minor, cli.patch);
match (maj, min, pat) {
(true, false, false) => major += 1,
(false, true, false) => minor += 1,
(false, false, true) => patch += 1,
_ => {
let mut cmd = Cli::command();
cmd.error(
ErrorKind::ArgumentConflict,
"Can only modify one version field",
)
.exit();
}
};
format!("{major}.{minor}.{patch}")
};
println!("Version: {version}");
// Check for usage of -c
if let Some(config) = cli.config.as_deref() {
let input = cli
.input_file
.as_deref()
// 'or' is preferred to 'or_else' here since `Option::as_deref` is 'const'
.or(cli.spec_in.as_deref())
.unwrap_or_else(|| {
let mut cmd = Cli::command();
cmd.error(
ErrorKind::MissingRequiredArgument,
"INPUT_FILE or --spec-in is required when using --config",
)
.exit()
});
println!("Doing work using input {input} and config {config}");
}
}
2.15子命令
我们使用 #[command(subcommand)] 属性和#[derive(Subcommand)] 联合起来使用声明子命令。
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Adds files to myapp
Add { name: Option<String> },
}
fn main() {
let cli = Cli::parse();
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Commands::Add { name } => {
println!("'myapp add' was used, name is: {name:?}")
}
}
}
本文由mdnice多平台发布