两种使用方式:
buidler 示例:
fn main() {
let cmd = clap::Command::new("cargo")
.bin_name("cargo")
.styles(CLAP_STYLING)
.subcommand_required(true)
.subcommand(
clap::command!("example").arg(
clap::arg!(--"manifest-path" <PATH>)
.value_parser(clap::value_parser!(std::path::PathBuf)),
),
);
// 从 os:env::args() 获得输入
let matches = cmd.get_matches();
// 匹配子命令
let matches = match matches.subcommand() {
Some(("example", matches)) => matches,
_ => unreachable!("clap should ensure we don't get here"),
};
// 获得子命令的选项参数
let manifest_path = matches.get_one::<std::path::PathBuf>("manifest-path");
println!("{manifest_path:?}");
}
pub const CLAP_STYLING: clap::builder::styling::Styles = clap::builder::styling::Styles::styled()
.header(clap_cargo::style::HEADER)
.usage(clap_cargo::style::USAGE)
.literal(clap_cargo::style::LITERAL)
.placeholder(clap_cargo::style::PLACEHOLDER)
.error(clap_cargo::style::ERROR)
.valid(clap_cargo::style::VALID)
.invalid(clap_cargo::style::INVALID);
derive macro 示例:
use clap::Parser;
#[derive(Parser)] // requires `derive` feature
#[command(name = "cargo")]
#[command(bin_name = "cargo")]
#[command(styles = CLAP_STYLING)]
enum CargoCli {
ExampleDerive(ExampleDeriveArgs),
}
// See also `clap_cargo::style::CLAP_STYLING`
pub const CLAP_STYLING: clap::builder::styling::Styles = clap::builder::styling::Styles::styled()
.header(clap_cargo::style::HEADER)
.usage(clap_cargo::style::USAGE)
.literal(clap_cargo::style::LITERAL)
.placeholder(clap_cargo::style::PLACEHOLDER)
.error(clap_cargo::style::ERROR)
.valid(clap_cargo::style::VALID)
.invalid(clap_cargo::style::INVALID);
#[derive(clap::Args)]
#[command(version, about, long_about = None)]
struct ExampleDeriveArgs {
#[arg(long)]
manifest_path: Option<std::path::PathBuf>,
}
fn main() {
let CargoCli::ExampleDerive(args) = CargoCli::parse();
println!("{:?}", args.manifest_path);
}
1 ValueParser #
ValueParser 是 Arg::value_parser()
的参数类型,用于解析和校验参数:
pub struct ValueParser(/* private fields */);
两种创建方式:
value_parser!
:自动根据类型选择对应的实现- value_parser!() 参数是
实现 ValueParserFactory traint 的类型名称
, 如 std::path::PathBuf 或其他自定义类型等;
- value_parser!() 参数是
ValueParser::new()
:使用自定义的TypedValueParser
类型来创建 ValueParser- pub fn new<P>(other: P) -> ValueParser where P: TypedValueParser
- clap::builder 提供了一些实现 TypedValueParser trait 的类型, 如 BoolValueParser 等;
1.1 value_parser!() #
通过 clap::builder::ValueParserFactory 来注册支持的类型,如各种 Rust 内置类型:
ValueParserFactory
类型包括:- Native types: bool, String, OsString, PathBuf
- Ranged numeric types: u8, i8, u16, i16, u32, i32, u64, i64
- ValueEnum types
- From<OsString> types and From<&OsStr> types
- From<String> types and From<&str> types
- FromStr types, including
usize, isize
// Register a type with value_parser!
pub trait ValueParserFactory {
type Parser;
// Required method
fn value_parser() -> Self::Parser;
}
// clap 内置注册的类型
impl ValueParserFactory for bool
impl ValueParserFactory for i8
impl ValueParserFactory for i16
impl ValueParserFactory for i32
impl ValueParserFactory for i64
impl ValueParserFactory for u8
impl ValueParserFactory for u16
impl ValueParserFactory for u32
impl ValueParserFactory for u64
impl ValueParserFactory for Box<str>
impl ValueParserFactory for Box<OsStr>
impl ValueParserFactory for Box<Path>
impl ValueParserFactory for String
impl ValueParserFactory for OsString
impl ValueParserFactory for PathBuf
impl<T> ValueParserFactory for Box<T>
where
T: ValueParserFactory + Send + Sync + Clone,
<T as ValueParserFactory>::Parser: TypedValueParser<Value = T>
impl<T> ValueParserFactory for Arc<T>
where
T: ValueParserFactory + Send + Sync + Clone,
<T as ValueParserFactory>::Parser: TypedValueParser<Value = T>
impl<T> ValueParserFactory for Wrapping<T>
where
T: ValueParserFactory + Send + Sync + Clone,
<T as ValueParserFactory>::Parser: TypedValueParser<Value = T>,
示例:
// Built-in types
let parser = clap::value_parser!(String);
assert_eq!(format!("{parser:?}"), "ValueParser::string");
let parser = clap::value_parser!(std::ffi::OsString);
assert_eq!(format!("{parser:?}"), "ValueParser::os_string");
let parser = clap::value_parser!(std::path::PathBuf);
assert_eq!(format!("{parser:?}"), "ValueParser::path_buf");
clap::value_parser!(u16).range(3000..);
clap::value_parser!(u64).range(3000..);
// FromStr types
let parser = clap::value_parser!(usize);
assert_eq!(format!("{parser:?}"), "_AnonymousValueParser(ValueParser::other(usize))");
// ValueEnum types
pub enum ColorChoice {
Auto,
Always,
Never,
}
clap::value_parser!(ColorChoice);
1.2 TypedValuePrarser #
TypedValueParser 是一个 trait,可以为自定义类型实现该 trait 来实现自定义解析,它们可以 Into<ValueParser>,所以都可以作为 Arg::value_parser() 的参数:
- TypedValueParser 的 parse_ref() 从传入的 arg/value 来解析出 type Value 对应的类型值;
- 闭包 Fn(&str) -> Result<T,E> 也实现了 TypedValueParser,可以灵活的对参数进行解析;
clap::builder 提供了如下实现 TypedValuePrarser
trait 的 struct 类型:
- BoolValueParser Implementation for ValueParser::bool
- BoolishValueParser Parse
bool-like
string values, everything else is true - EnumValueParser Parse an ValueEnum value.
- FalseyValueParser Parse
false-like
string values, everything else is true - MapValueParser Adapt a TypedValueParser from one value to another
- NonEmptyStringValueParser Parse non-empty string values
- OsStringValueParser Implementation for ValueParser::os_string
- PathBufValueParser Implementation for ValueParser::path_buf
- PossibleValue A possible value of an argument.
- PossibleValuesParser Verify the value is from an enumerated set of PossibleValue.
- RangedI64ValueParser Parse number that fall within a range of values
- RangedU64ValueParser Parse number that fall within a range of values
- StringValueParser Implementation for ValueParser::string
- TryMapValueParser Adapt a TypedValueParser from one value to another
- UnknownArgumentValueParser When encountered, report ErrorKind::UnknownArgument
其中 RangedI64ValueParser 和 RangedU64ValueParser 用于定义一个 range 范围。
pub trait TypedValueParser: Clone + Send + Sync + 'static {
type Value: Send + Sync + Clone;
// Required method
fn parse_ref( &self, cmd: &Command, arg: Option<&Arg>, value: &OsStr ) -> Result<Self::Value, Error>;
//...
}
impl TypedValueParser for BoolValueParser // 返回 bool
impl TypedValueParser for BoolishValueParser
impl TypedValueParser for FalseyValueParser
impl TypedValueParser for NonEmptyStringValueParser // 返回非空 String
impl TypedValueParser for OsStringValueParser // 返回 OsString
impl TypedValueParser for PathBufValueParser // 返回 PathBuf
impl TypedValueParser for PossibleValuesParser // 只能从可选值列表中选择
impl TypedValueParser for StringValueParser // 返回 String
impl TypedValueParser for UnknownArgumentValueParser
impl<E> TypedValueParser for EnumValueParser<E> where E: ValueEnum + Clone + Send + Sync + 'static
// Fn(&str) -> Result<T,E> 闭包也实现了 TypedValueParser
impl<F, T, E> TypedValueParser for F
where
F: Fn(&str) -> Result<T, E> + Clone + Send + Sync + 'static,
E: Into<Box<dyn Error + Sync + Send>>,
T: Send + Sync + Clone
impl<P, F, T> TypedValueParser for MapValueParser<P, F>
where
P: TypedValueParser,
<P as TypedValueParser>::Value: Send + Sync + Clone,
F: Fn(<P as TypedValueParser>::Value) -> T + Clone + Send + Sync + 'static,
T: Send + Sync + Clone
impl<P, F, T, E> TypedValueParser for TryMapValueParser<P, F>
where
P: TypedValueParser,
<P as TypedValueParser>::Value: Send + Sync + Clone,
F: Fn(<P as TypedValueParser>::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
T: Send + Sync + Clone,
E: Into<Box<dyn Error + Sync + Send>>
impl<T> TypedValueParser for RangedI64ValueParser<T>
where
T: TryFrom<i64> + Clone + Send + Sync + 'static,
<T as TryFrom<i64>>::Error: Send + Sync + 'static + Error + ToString
impl<T> TypedValueParser for RangedU64ValueParser<T>
where
T: TryFrom<u64> + Clone + Send + Sync + 'static,
<T as TryFrom<u64>>::Error: Send + Sync + 'static + Error + ToString,
例子:
// 使用 Fn 闭包来实现 ValueParser 的例子:
fn bytes_from_str(src: &str) -> Result<Bytes, Infallible> {
Ok(Bytes::from(src.to_string()))
}
#[derive(Subcommand, Debug)]
enum Command {
Ping {
#[clap(value_parser = bytes_from_str)]
msg: Option<Bytes>,
}
}
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("port")
.long("port")
// 使用宏来创建 ValueParser
.value_parser(clap::value_parser!(u16).range(3000..))
.action(clap::ArgAction::Set)
.required(true)
);
let m = cmd.try_get_matches_from_mut(["cmd", "--port", "3001"]).unwrap();
let port: u16 = *m.get_one("port").expect("required");
assert_eq!(port, 3001);
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("append")
// 使用预定义的 ValueParser
.value_parser(clap::builder::NonEmptyStringValueParser::new())
.required(true)
);
let m = cmd.try_get_matches_from_mut(["cmd", "true"]).unwrap();
let port: &String = m.get_one("append").expect("required");
assert_eq!(port, "true");
通过实现 clap::builder::ValueParserFactory 和 clap::builder::TypedValueParser 可以添加自定义类型的解析:
#[derive(Copy, Clone, Debug)]
pub struct Custom(u32);
impl clap::builder::ValueParserFactory for Custom {
type Parser = CustomValueParser;
fn value_parser() -> Self::Parser {
CustomValueParser
}
}
#[derive(Clone, Debug)]
pub struct CustomValueParser;
impl clap::builder::TypedValueParser for CustomValueParser {
type Value = Custom;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let inner = clap::value_parser!(u32);
let val = inner.parse_ref(cmd, arg, value)?;
Ok(Custom(val))
}
}
// 自定义 ValueParser
let parser: CustomValueParser = clap::value_parser!(Custom);
1.3 Arg::value_parser() #
为 Arg 指定 value_parser 的方式,默认是按照 String 的方式来进行解析:
pub fn value_parser(self, parser: impl IntoResettable<ValueParser>) -> Arg
// clap 为所有 Into<ValueParser> 实现了 IntoResettable<ValueParser>
impl<I> IntoResettable<ValueParser> for I where I: Into<ValueParser>
impl IntoResettable<ValueParser> for Option<ValueParser>
impl<I> IntoResettable<ValueRange> for I where I: Into<ValueRange>
clap 为 Into<ValueParser>/Option<ValueParser> 实现了 IntoResettable<ValueParser>,所以任何实现了 From<T> 来转成 ValueParser 的类型都可以作为 Arg::value_parser() 的参数,如 [P; C], Vec<P> 和 TypedValueParser。
value_parser!(T)
:根据 T 的类型自动选择合适的实现。或者 0..=1 代表 RangedI64ValueParser;Fn(&str) -> Result<T, E>
: 该闭包实现了 TypedValueParser trait.[&str] and PossibleValuesParser
: for static enumerated valuesBoolishValueParser and FalseyValueParser
:for alternative bool implementationsNonEmptyStringValueParser
: for basic validation for strings- or any other
TypedValueParser
implementation
// 数组 [P; C] 和 Vec<P> 可以转换为 ValueParser
impl<P, const C: usize> From<[P; C]> for ValueParser where P: Into<PossibleValue>
impl<P> From<Vec<P>> for ValueParser where P: Into<PossibleValue>
// 为任何实现 TypedValueParser 的类型转换为 ValueParser
impl<P> From<P> for ValueParser where P: TypedValueParser + Send + Sync + 'static,
// 为 RangeXX 转换为 ValueParser
impl From<Range<i64>> for ValueParser
impl From<RangeFrom<i64>> for ValueParser
impl From<RangeFull> for ValueParser
impl From<RangeInclusive<i64>> for ValueParser
impl From<RangeTo<i64>> for ValueParser
impl From<RangeToInclusive<i64>> for ValueParser
// 从预定义的关联函数创建 ValueParser
pub const fn bool() -> ValueParser
pub const fn string() -> ValueParser
pub const fn os_string() -> ValueParser
pub const fn path_buf() -> ValueParser
// 更灵活的是为实现 TypedValueParser 的自定义类型创建 ValueParser
pub fn new<P>(other: P) -> ValueParser where P: TypedValueParser
PossibleValue 和 PossibleValuesParser:
- 任何可以 Into<Str> 的类型都可以转成 PossibleValue;
- 任何可以迭代生成 PossibleValue 的类型都可以转成 PossibleValuesParser;
pub struct PossibleValue { /* private fields */ }
impl PossibleValue
pub fn new(name: impl Into<Str>) -> PossibleValue
// 为可生成 str 的任意对象转换为 PossibleValue
impl<S> From<S> for PossibleValue where S: Into<Str>
impl PossibleValuesParser
pub fn new(values: impl Into<PossibleValuesParser>) -> PossibleValuesParser
// 为可迭代对象,如 &[&str], Vec<&str>, [&str; N] 等转换为 PossibleValuesParser
impl<I, T> From<I> for PossibleValuesParser where I: IntoIterator<Item = T>, T: Into<PossibleValue>
impl TypedValueParser for PossibleValuesParser
// 示例
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("color")
// 必须都是字符串列表
.value_parser(clap::builder::PossibleValuesParser::new(["always", "auto", "never"]))
.required(true)
);
let m = cmd.try_get_matches_from_mut(["cmd", "always"]).unwrap();
let port: &String = m.get_one("color").expect("required");
assert_eq!(port, "always");
2 builder #
核心 Struct 类型:
- Command:
- Arg:
- ArgGroup:
- ValueParser:作为
Arg.value_parser(valueParser)
的参数;
可以使用宏函数 command!(), arg!(), value_parser!() 宏可以快速创建这些对象。
- command
- Allows you to build the Command instance from your Cargo.toml at compile time.
- command!() 宏可以从 Cargo.toml 中提取 Command 的 name、about、author、version 等信息,不需要单独设定;
- arg
- Create an Arg from a usage string
- arg!() 宏可以更方便的创建 [BROKEN LINK: .pre-processed.org];
以下 carte_XX!() 宏是从 Cargo.toml 中提取信息: name、about、author、version、description 等;
- crate_authors
- Allows you to pull the authors for the command from your Cargo.toml at compile time in the form:“author1 lastname <[email protected]>:author2 lastname <[email protected]>”
- crate_description
- Allows you to pull the description from your Cargo.toml at compile time.
- crate_name
- Allows you to pull the name from your Cargo.toml at compile time.
- crate_version
- Allows you to pull the version from your Cargo.toml at compile time as MAJOR.MINOR.PATCH_PKGVERSION_PRE
- value_parser
- Select a
ValueParser
implementation from the intended type,- 默认是ValueParser::string;
- value_parser!() 的参数是类型名称, 如 std::path::PathBuf 或其他自定义类型等;
use std::path::PathBuf;
use clap::{arg, command, value_parser, ArgAction, Command};
let m = Command::new("My Program")
.author("Me, [email protected]")
.version("1.0.2")
.about("Explains in brief what the program does")
.arg(
Arg::new("in_file")
)
.after_help("Longer explanation to appear after the options when \
displaying the help information from --help or -h")
.get_matches();
let m = Command::new(crate_name!())
.author(crate_authors!("\n"))
.version(crate_version!())
.about(crate_description!())
.get_matches();
let matches = command!() // 从 Cargo.toml 中读取和设置 name/version/authors/description 信息
.arg(arg!([name] "Optional name to operate on") )
.arg(arg!(-c --config <FILE> "Sets a custom config file" )
.required(false) // 可选参数(默认都是必选的)
.value_parser(value_parser!(PathBuf))) // 自定义解析类型(默认为 String)
.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();
if let Some(name) = matches.get_one::<String>("name") {
println!("Value for name: {name}");
}
if let Some(config_path) = matches.get_one::<PathBuf>("config") {
println!("Value for config: {}", config_path.display());
}
match matches.get_one::<u8>("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"),
}
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...");
}
}
3 derive #
通过 derive macro 和 attr macro 来声明式定义命令和参数,需要启用 derive 和 cargo feature,文档。
derive macro: 使用 #[derive] 来定义命令和参数,示例;
- Parser : Parse command-line arguments into Self.
- Args : Parse a set of arguments into a user-defined container.
- Subcommand : Parse a sub-command into a user-defined enum.
- ValueEnum : Parse arguments into enums.
- CommandFactory : Create a Command relevant for a user-defined container.
- FromArgMatches : Converts an instance of ArgMatches to a user-defined container.
Parser trait: 使用 #[derive(Parser)] 来定义命令行解构的入口类型;
- 父 trait FromArgsMathes trait 实现从 struct ArgMatches 来生成自身对象;
- parse() 默认从 std::env::args_os 解析参数, 使用 parse_from() 函数来传入其他命令行字符串来源;
pub trait Parser: FromArgMatches + CommandFactory + Sized {
// Provided methods
fn parse() -> Self { ... }
fn try_parse() -> Result<Self, Error> { ... }
fn parse_from<I, T>(itr: I) -> Self where I: IntoIterator<Item = T>, T: Into<OsString> + Clone
fn try_parse_from<I, T>(itr: I) -> Result<Self, Error>
where I: IntoIterator<Item = T>, T: Into<OsString> + Clone { ... }
fn update_from<I, T>(&mut self, itr: I) where I: IntoIterator<Item = T>, T: Into<OsString> + Clone
fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error>
where I: IntoIterator<Item = T>, T: Into<OsString> + Clone { ... }
}
Parser 可以使用 command 和 arg 两个 attribue macro:
- #[command]: 指定命令参数, 可以使用任何 Command builder 方法, 如 Command::next_line_help:
- #[arg]: 指定 flag 参数, 可以使用任何 Args builder 方法,如 long:
示例:
// 例子
use clap::Parser;
#[derive(Parser)]
// auth/version/about 等均为 Command builder 方法
#[command(author, version, about, long_about = None)]
#[command(next_line_help = true)] // Command builder 方法
struct Cli {
// 对于 flag 参数必须指定 #[arg], 否则为位置参数.
#[arg(long)] // Arg builder 方法
two: String,
#[arg(long)]
one: String,
}
fn main() {
let cli = Cli::parse();
println!("two: {:?}", cli.two);
println!("one: {:?}", cli.one);
}
// 例子
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] // 1. 从 Cargo.toml 获取缺省值
#[command(name = "MyApp")] // 2. 或者指定缺省值
struct Args {
#[arg(short, long)]
name: String,
#[arg(short, long, default_value_t = 1)]
count: u8,
}
fn main() {
let args = Args::parse(); // std::env::args_os
for _ in 0..args.count {
println!("Hello {}!", args.name)
}
}
// 例子
use clap::Parser;
#[derive(Parser)]
#[command(name = "MyApp")]
#[command(author = "Kevin K. <[email protected]>")]
#[command(version = "1.0")]
#[command(about = "Does awesome things", long_about = None)]
struct Cli {
#[arg(long)]
two: String,
#[arg(long)]
one: String,
}
fn main() {
let cli = Cli::parse();
println!("two: {:?}", cli.two);
println!("one: {:?}", cli.one);
}
// $ ./02_apps_derive --help
// Does awesome things // about
// Usage: 02_apps_derive[EXE] --two <TWO> --one <ONE>
// Options:
// --two <TWO>
// --one <ONE>
// -h, --help Print help
// -V, --version Print version
// $ 02_apps_derive --version // name + version
// MyApp 1.0
位置参数:没有指定任何 clip 相关的 attr 的 field 为位置参数;
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
// 没有任何 attribute macro, 所以作为命令行位置参数
name: Option<String>,
// 同样是命令行位置参数,但是可以指定多个(空格分隔)
name2: Vec<String>,
}
fn main() {
let cli = Cli::parse();
println!("name: {:?}", cli.name.as_deref());
}
Arg:表示命令行参数 flag, 需要通过 #[arg] 来修饰 field:
- Arg.action() 的参数 ArgAction 默认为
Set/SetTrue
, 对于 Vec 等类型需要明确设置为Append
; - Arg field 的类型要求:
- Vec<XX> : 可以指定多次 flag, 各参数值被 Append 到 Vec 中;
- Option<XX>: 表示该 field 是可选的(默认是
必选
); - clap 根据各 field 的类型 XX, 来设置 value_parser!(XX), 所以 XX 必须是实现 ValueParser 的类型.
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about=None)]
struct Cli {
#[arg(short = 'n')]
#[arg(long = "name")] // 可以使用 clap::builder::Arg 的各种方法来设置 arg 的参数
#[arg(short, long)] // 根据 filed name 自动推断
name: Option<String>, // field 类型可以是任何 clap 支持的类型
#[arg(short, long)]
verbose: bool,
}
fn main() {
let cli = Cli::parse();
println!("name: {:?}", cli.name.as_deref());
}
对于 derive 风格的 subcommand,需要使用 enum 类型,每一个 variant 都是一个 subcommand,struct variant 的 field 为 subcommand 的 args:
- #[derive(Subcommand)] 只支持 enum 类型, 所以只能通过 enum 来定义 subcommnad;
- #[derive(Args)] 只支持 non-tuple struct 类型,所以只能通过 struct field 来定义 Args;
- #[command(flatten)] 有两种使用场景:
- 在 Parser/Args 中:field type 必须实现 Args;
- 在 Subcommand 中: field type 必须实现 Subcommand;
pub fn test_clap() {
use clap::{Args, Parser, Subcommand};
use std::fmt::Debug;
/// Cli command
#[derive(Parser, Debug)]
struct Cli {
#[command(flatten)]
// Parser/Args 场景下,flatten 的 field type 必须是 struct 类型
delegate: Struct, // Struct 必须实现 Args,它的 field 作为命令行 Arg
#[command(subcommand)] // 必须修饰 enum 类型
command: Command, // Command 必须是 enum struct 类型
}
/// struct args
#[derive(Args, Debug)] // Struct必须实现 Args
struct Struct {
/// field arg
field: u8,
}
/// sub command
#[derive(Subcommand, Debug)]
// Subcommand 必须是 enum 类型, 各 variant 可以是 struct 或 newtype 类型, struct 的各 field 为
// 命令的参数 Args. newtype 的 type 必须是 struct类型.
enum Command {
/// cmd1, 支持 tuple 类型, 但则只支持一个类型的 newtype, 不支持 2 个及以上的 tuple
Cmd1(Struct), /* 每一个 Variant 都是一个 subcommand, Struct
* 必须实现 Args */
/// cmd2, 支持 struct 类型, struct field 为命令行参数(不需要添加 #[arg])
Cmd2 {
// Variant struct 的 field 对应 subcommand 的 args
/// field
field: u8,
},
// https://rustdoc.swc.rs/clap/trait.Subcommand.html
#[command(flatten)]
// SubCommand 场景下也可以使用 flatten,但必须是实现 Subcommand 的 enum 类型.
Variant3(SubCmd), /* SubCmd 必须实现 Subcommand,而 Subcommand
* 必须是 enum 类型 */
}
/// sub command
#[derive(Subcommand, Debug)]
enum SubCmd {
/// Doc comment
Sub1 { field: u8 },
}
let cli = Cli::parse();
println!("{:?}", cli);
}
// cargo run:
// Usage: foo <FIELD> <COMMAND>
// Commands:
// cmd1 cmd1, 支持 tuple 类型, 但则只支持一个类型的 newtype, 不支持 2 个及以上的 tuple
// cmd2 cmd2, 支持 struct 类型, struct field 为命令行参数(不需要添加 #[arg])
// sub1 Doc comment
// help Print this message or the help of the given subcommand(s)
// Arguments:
// <FIELD> field arg
// Options:
// -h, --help Print help
// cargo run cmd1 -h
// cmd1, 支持 tuple 类型, 但则只支持一个类型的 newtype, 不支持 2 个及以上的 tuple
// Usage: foo <FIELD> cmd1 <FIELD>
// Arguments:
// <FIELD> field arg
// Options:
// -h, --help Print help
// cargo run cmd2 -h
// cmd2, 支持 struct 类型, struct field 为命令行参数(不需要添加 #[arg])
// Usage: foo <FIELD> cmd2 <FIELD>
// Arguments:
// <FIELD> field
// Options:
// -h, --help Print help
在使用 drive macro 定义 Arg 时,使用 #[arg(value_enum)] 来定义枚举 field,clap 自动调用 clap::EnumValueParser 。
pub enum ColorChoice {
Auto,
Always,
Never,
}
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("color")
.value_parser(clap::builder::EnumValueParser::<ColorChoice>::new())
.required(true)
);
let m = cmd.try_get_matches_from_mut(["cmd", "always"]).unwrap();
let port: ColorChoice = *m.get_one("color").expect("required");
assert_eq!(port, ColorChoice::Always);
// 例子
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");
}
}
}
使用 #[arg(default_value_t = expr)] 来为 field 指定缺省值 expr,如果未指定 expr,则类型需要实现 Default trait。指定该 attr 时,field 是可选的。
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);
}
参考:
- https://github.com/mrjackwills/havn/blob/main/src/parse_arg.rs
- https://github.com/franticxx/dn/blob/main/src/cli/cli.rs
4 help template #
use std::io::Write;
use clap::Command;
fn main() -> Result<(), String> {
loop {
let line = readline()?;
let line = line.trim();
if line.is_empty() {
continue;
}
match respond(line) {
Ok(quit) => {
if quit {
break;
}
}
Err(err) => {
write!(std::io::stdout(), "{err}").map_err(|e| e.to_string())?;
std::io::stdout().flush().map_err(|e| e.to_string())?;
}
}
}
Ok(())
}
fn respond(line: &str) -> Result<bool, String> {
let args = shlex::split(line).ok_or("error: Invalid quoting")?;
let matches = cli()
.try_get_matches_from(args)
.map_err(|e| e.to_string())?;
match matches.subcommand() {
Some(("ping", _matches)) => {
write!(std::io::stdout(), "Pong").map_err(|e| e.to_string())?;
std::io::stdout().flush().map_err(|e| e.to_string())?;
}
Some(("quit", _matches)) => {
write!(std::io::stdout(), "Exiting ...").map_err(|e| e.to_string())?;
std::io::stdout().flush().map_err(|e| e.to_string())?;
return Ok(true);
}
Some((name, _matches)) => unimplemented!("{name}"),
None => unreachable!("subcommand required"),
}
Ok(false)
}
fn cli() -> Command {
// strip out usage
const PARSER_TEMPLATE: &str = "\
{all-args}
";
// strip out name/version
const APPLET_TEMPLATE: &str = "\
{about-with-newline}\n\
{usage-heading}\n {usage}\n\
\n\
{all-args}{after-help}\
";
Command::new("repl")
.multicall(true)
.arg_required_else_help(true)
.subcommand_required(true)
.subcommand_value_name("APPLET")
.subcommand_help_heading("APPLETS")
.help_template(PARSER_TEMPLATE)
.subcommand(
Command::new("ping")
.about("Get a response")
.help_template(APPLET_TEMPLATE),
)
.subcommand(
Command::new("quit")
.alias("exit")
.about("Quit the REPL")
.help_template(APPLET_TEMPLATE),
)
}
fn readline() -> Result<String, String> {
write!(std::io::stdout(), "$ ").map_err(|e| e.to_string())?;
std::io::stdout().flush().map_err(|e| e.to_string())?;
let mut buffer = String::new();
std::io::stdin()
.read_line(&mut buffer)
.map_err(|e| e.to_string())?;
Ok(buffer)
}
5 示例 #
- 未调用 value_parser() 指定 ValueParser 时,默认为 StringValueParser,所以默认解析为 String;
- 未调用 action() 时默认为 ArgAction::Set,对于 Vec 需要指定为 action(ArgAction::Append));
- field 未加 #[attr] 时默认为位置参数;
- 参数默认是必须的;
let matches = command!()
.arg(
arg!([PORT])
.value_parser(value_parser!(u16)) // Rust 内置类型
.default_value("2020"),
)
.get_matches();
let cfg = Arg::new("config")
.action(ArgAction::Set)
.value_name("FILE")
// [PossibleValue; 3] 实现了 ValueParser
.value_parser([
PossibleValue::new("fast"),
PossibleValue::new("slow").help("slower than fast"),
PossibleValue::new("secret speed").hide(true)
]);
let cfg = Arg::new("config")
.action(ArgAction::Set)
.value_name("FILE")
// RangeFull 实现了 ValueParser
.value_parser(2..5);
let cfg = Arg::new("config")
.short('c')
.long("config")
.action(ArgAction::Set)
.value_name("FILE")
.help("Provides a config file to myprog");
let input = arg!(-i --input <FILE> "Provides an input file to the program");
use clap::{arg, command, ArgAction};
fn main() {
let matches = command!()
.next_line_help(true)
// 未指定 ValueParser 时默认为 StringValueParser
.arg(arg!(--two <VALUE>).required(true).action(ArgAction::Set))
.arg(arg!(--one <VALUE>).required(true).action(ArgAction::Set))
.get_matches();
println!(
"two: {:?}",
matches.get_one::<String>("two").expect("required")
);
println!(
"one: {:?}",
matches.get_one::<String>("one").expect("required")
);
}
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("color")
.long("color")
// [&str; 3] 实现了 ValueParser
.value_parser(["always", "auto", "never"])
.default_value("auto")
);
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("output")
.value_parser(clap::value_parser!(PathBuf))
.required(true)
);
Vec 类型 Arg 的实现:
- builder 风格: 使用 .action(clap::ArgAction::Append) 来指定 flag Arg 出现多次时追加到 Vec。
- .num_args(2) 是为 flag Arg 指定有多个位置参数值,也可以指定 Range,如 2.. 表示至少 2 个位置参数;
- derive 风格: Vec<T>
- clap 隐式调用 .action(ArgAction::Append).required(false);
- 默认为 T 自动添加
.value_parser(value_parser!(T))
, 如果不符合预期则需要为自定义类型实现 ValueParserFactory;
// builder 风格
let cmd = Command::new("mycmd")
.arg(
Arg::new("flag")
.long("flag")
.action(clap::ArgAction::Append)
);
let matches = cmd.try_get_matches_from(["mycmd", "--flag", "value", "--flag" "value2"]).unwrap();
assert!(matches.contains_id("flag"));
assert_eq!(
matches.get_many::<String>("flag").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
vec!["value", "value2"]
);
// derive 风格
#[derive(Parser, Debug)]
struct AddArgs {
name: Vec<String>,
}
#[derive(Parser, Debug)]
struct RemoveArgs {
#[arg(short, long)]
force: bool,
name: Vec<String>,
}
// num_args 指定参数值的个数
let cmd = Command::new("prog")
.arg(Arg::new("file")
.action(ArgAction::Set)
.num_args(2) // 2个位置参数,也可以指定 Range,如 2.. 表示至少 2 个位置参数
.short('F'));
let m = cmd.clone()
.get_matches_from(vec!["prog", "-F", "in-file", "out-file"]);
assert_eq!(
m.get_many::<String>("file").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
vec!["in-file", "out-file"]
);
let res = cmd.clone().try_get_matches_from(vec!["prog", "-F", "file1"]);
assert_eq!(res.unwrap_err().kind(), ErrorKind::WrongNumberOfValues);
Option 类型 Arg 的实现:
- builder 风格: 为 Arg 设置 .required(false);
- derive 风格: Option<Vec<T>>, clap 自动调用 .action(ArgAction::Append).required(false)
- 默认为 T 自动添加
.value_parser(value_parser!(T))
, 如果不符合预期, 如 T 不是 Rust 原始类型, 则需要为自定义类型实现 ValueParserFactory;
- 默认为 T 自动添加
let mut cmd = clap::Command::new("raw")
.arg(
clap::Arg::new("append")
.value_parser(clap::builder::FalseyValueParser::new())
.required(false)
);
Args Group:在 help 显示时按照 Group 显示 Args:
use std::collections::BTreeMap;
use clap::{command, value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
fn main() {
let matches = cli().get_matches();
let values = Value::from_matches(&matches);
println!("{values:#?}");
}
fn cli() -> Command {
command!()
.group(ArgGroup::new("tests").multiple(true))
.next_help_heading("TESTS")
.args([
position_sensitive_flag(Arg::new("empty"))
.long("empty")
.action(ArgAction::Append)
.help("File is empty and is either a regular file or a directory")
.group("tests"),
Arg::new("name")
.long("name")
.action(ArgAction::Append)
.help("Base of file name (the path with the leading directories removed) matches shell pattern pattern")
.group("tests")
])
.group(ArgGroup::new("operators").multiple(true))
.next_help_heading("OPERATORS")
.args([
position_sensitive_flag(Arg::new("or"))
.short('o')
.long("or")
.action(ArgAction::Append)
.help("expr2 is not evaluate if exp1 is true")
.group("operators"),
position_sensitive_flag(Arg::new("and"))
.short('a')
.long("and")
.action(ArgAction::Append)
.help("Same as `expr1 expr1`")
.group("operators"),
])
}
fn position_sensitive_flag(arg: Arg) -> Arg {
// Flags don't track the position of each occurrence, so we need to emulate flags with
// value-less options to get the same result
arg.num_args(0)
.value_parser(value_parser!(bool))
.default_missing_value("true")
.default_value("false")
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Value {
Bool(bool),
String(String),
}
impl Value {
pub fn from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)> {
let mut values = BTreeMap::new();
for id in matches.ids() {
if matches.try_get_many::<clap::Id>(id.as_str()).is_ok() {
// ignore groups
continue;
}
let value_source = matches
.value_source(id.as_str())
.expect("id came from matches");
if value_source != clap::parser::ValueSource::CommandLine {
// Any other source just gets tacked on at the end (like default values)
continue;
}
if Self::extract::<String>(matches, id, &mut values) {
continue;
}
if Self::extract::<bool>(matches, id, &mut values) {
continue;
}
unimplemented!("unknown type for {id}: {matches:?}");
}
values.into_values().collect::<Vec<_>>()
}
fn extract<T: Clone + Into<Value> + Send + Sync + 'static>(
matches: &ArgMatches,
id: &clap::Id,
output: &mut BTreeMap<usize, (clap::Id, Self)>,
) -> bool {
match matches.try_get_many::<T>(id.as_str()) {
Ok(Some(values)) => {
for (value, index) in values.zip(
matches
.indices_of(id.as_str())
.expect("id came from matches"),
) {
output.insert(index, (id.clone(), value.clone().into()));
}
true
}
Ok(None) => {
unreachable!("`ids` only reports what is present")
}
Err(clap::parser::MatchesError::UnknownArgument { .. }) => {
unreachable!("id came from matches")
}
Err(clap::parser::MatchesError::Downcast { .. }) => false,
Err(_) => {
unreachable!("id came from matches")
}
}
}
}
impl From<String> for Value {
fn from(other: String) -> Self {
Self::String(other)
}
}
impl From<bool> for Value {
fn from(other: bool) -> Self {
Self::Bool(other)
}
}
// $ find --help
// A simple to use, efficient, and full-featured Command Line Argument Parser
// Usage: find[EXE] [OPTIONS]
// Options:
// -h, --help Print help
// -V, --version Print version
// TESTS:
// --empty File is empty and is either a regular file or a directory
// --name <name> Base of file name (the path with the leading directories removed) matches shell
// pattern pattern
// OPERATORS:
// -o, --or expr2 is not evaluate if exp1 is true
// -a, --and Same as `expr1 expr1`