From 567c3806adaa2f39953c1a9ac32df340eb93fc3a Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Thu, 7 Mar 2024 15:06:29 +0900 Subject: Import clap 3.2.22 --- examples/cargo-example-derive.md | 43 ++++ examples/cargo-example-derive.rs | 20 ++ examples/cargo-example.md | 43 ++++ examples/cargo-example.rs | 19 ++ examples/demo.md | 19 ++ examples/demo.rs | 22 ++ examples/derive_ref/augment_args.rs | 27 +++ examples/derive_ref/augment_subcommands.rs | 21 ++ examples/derive_ref/custom-bool.md | 47 ++++ examples/derive_ref/custom-bool.rs | 32 +++ examples/derive_ref/flatten_hand_args.rs | 81 +++++++ examples/derive_ref/hand_subcommand.rs | 81 +++++++ examples/derive_ref/interop_tests.md | 256 ++++++++++++++++++++++ examples/escaped-positional-derive.md | 63 ++++++ examples/escaped-positional-derive.rs | 25 +++ examples/escaped-positional.md | 63 ++++++ examples/escaped-positional.rs | 36 +++ examples/git-derive.md | 138 ++++++++++++ examples/git-derive.rs | 105 +++++++++ examples/git.md | 136 ++++++++++++ examples/git.rs | 101 +++++++++ examples/multicall-busybox.md | 42 ++++ examples/multicall-busybox.rs | 48 ++++ examples/multicall-hostname.md | 10 + examples/multicall-hostname.rs | 17 ++ examples/pacman.md | 85 +++++++ examples/pacman.rs | 111 ++++++++++ examples/repl.rs | 92 ++++++++ examples/tutorial_builder/01_quick.md | 37 ++++ examples/tutorial_builder/01_quick.rs | 63 ++++++ examples/tutorial_builder/02_app_settings.md | 19 ++ examples/tutorial_builder/02_app_settings.rs | 19 ++ examples/tutorial_builder/02_apps.md | 19 ++ examples/tutorial_builder/02_apps.rs | 20 ++ examples/tutorial_builder/02_crate.md | 18 ++ examples/tutorial_builder/02_crate.rs | 18 ++ examples/tutorial_builder/03_01_flag_bool.md | 23 ++ examples/tutorial_builder/03_01_flag_bool.rs | 14 ++ examples/tutorial_builder/03_01_flag_count.md | 23 ++ examples/tutorial_builder/03_01_flag_count.rs | 9 + examples/tutorial_builder/03_02_option.md | 32 +++ examples/tutorial_builder/03_02_option.rs | 9 + examples/tutorial_builder/03_03_positional.md | 22 ++ examples/tutorial_builder/03_03_positional.rs | 9 + examples/tutorial_builder/03_04_subcommands.md | 64 ++++++ examples/tutorial_builder/03_04_subcommands.rs | 22 ++ examples/tutorial_builder/03_05_default_values.md | 22 ++ examples/tutorial_builder/03_05_default_values.rs | 14 ++ examples/tutorial_builder/04_01_enum.md | 29 +++ examples/tutorial_builder/04_01_enum.rs | 66 ++++++ examples/tutorial_builder/04_01_possible.md | 29 +++ examples/tutorial_builder/04_01_possible.rs | 26 +++ examples/tutorial_builder/04_02_parse.md | 31 +++ examples/tutorial_builder/04_02_parse.rs | 17 ++ examples/tutorial_builder/04_02_validate.md | 31 +++ examples/tutorial_builder/04_02_validate.rs | 36 +++ examples/tutorial_builder/04_03_relations.md | 58 +++++ examples/tutorial_builder/04_03_relations.rs | 80 +++++++ examples/tutorial_builder/04_04_custom.md | 57 +++++ examples/tutorial_builder/04_04_custom.rs | 88 ++++++++ examples/tutorial_builder/05_01_assert.rs | 25 +++ examples/tutorial_derive/01_quick.md | 37 ++++ examples/tutorial_derive/01_quick.rs | 69 ++++++ examples/tutorial_derive/02_app_settings.md | 19 ++ examples/tutorial_derive/02_app_settings.rs | 19 ++ examples/tutorial_derive/02_apps.md | 19 ++ examples/tutorial_derive/02_apps.rs | 20 ++ examples/tutorial_derive/02_crate.md | 18 ++ examples/tutorial_derive/02_crate.rs | 17 ++ examples/tutorial_derive/03_01_flag_bool.md | 23 ++ examples/tutorial_derive/03_01_flag_bool.rs | 14 ++ examples/tutorial_derive/03_01_flag_count.md | 23 ++ examples/tutorial_derive/03_01_flag_count.rs | 14 ++ examples/tutorial_derive/03_02_option.md | 32 +++ examples/tutorial_derive/03_02_option.rs | 14 ++ examples/tutorial_derive/03_03_positional.md | 22 ++ examples/tutorial_derive/03_03_positional.rs | 14 ++ examples/tutorial_derive/03_04_subcommands.md | 64 ++++++ examples/tutorial_derive/03_04_subcommands.rs | 30 +++ examples/tutorial_derive/03_04_subcommands_alt.rs | 33 +++ examples/tutorial_derive/03_05_default_values.md | 22 ++ examples/tutorial_derive/03_05_default_values.rs | 14 ++ examples/tutorial_derive/04_01_enum.md | 29 +++ examples/tutorial_derive/04_01_enum.rs | 28 +++ examples/tutorial_derive/04_02_parse.md | 31 +++ examples/tutorial_derive/04_02_parse.rs | 15 ++ examples/tutorial_derive/04_02_validate.md | 31 +++ examples/tutorial_derive/04_02_validate.rs | 34 +++ examples/tutorial_derive/04_03_relations.md | 58 +++++ examples/tutorial_derive/04_03_relations.rs | 72 ++++++ examples/tutorial_derive/04_04_custom.md | 57 +++++ examples/tutorial_derive/04_04_custom.rs | 93 ++++++++ examples/tutorial_derive/05_01_assert.rs | 21 ++ examples/typed-derive.md | 84 +++++++ examples/typed-derive.rs | 44 ++++ 95 files changed, 3966 insertions(+) create mode 100644 examples/cargo-example-derive.md create mode 100644 examples/cargo-example-derive.rs create mode 100644 examples/cargo-example.md create mode 100644 examples/cargo-example.rs create mode 100644 examples/demo.md create mode 100644 examples/demo.rs create mode 100644 examples/derive_ref/augment_args.rs create mode 100644 examples/derive_ref/augment_subcommands.rs create mode 100644 examples/derive_ref/custom-bool.md create mode 100644 examples/derive_ref/custom-bool.rs create mode 100644 examples/derive_ref/flatten_hand_args.rs create mode 100644 examples/derive_ref/hand_subcommand.rs create mode 100644 examples/derive_ref/interop_tests.md create mode 100644 examples/escaped-positional-derive.md create mode 100644 examples/escaped-positional-derive.rs create mode 100644 examples/escaped-positional.md create mode 100644 examples/escaped-positional.rs create mode 100644 examples/git-derive.md create mode 100644 examples/git-derive.rs create mode 100644 examples/git.md create mode 100644 examples/git.rs create mode 100644 examples/multicall-busybox.md create mode 100644 examples/multicall-busybox.rs create mode 100644 examples/multicall-hostname.md create mode 100644 examples/multicall-hostname.rs create mode 100644 examples/pacman.md create mode 100644 examples/pacman.rs create mode 100644 examples/repl.rs create mode 100644 examples/tutorial_builder/01_quick.md create mode 100644 examples/tutorial_builder/01_quick.rs create mode 100644 examples/tutorial_builder/02_app_settings.md create mode 100644 examples/tutorial_builder/02_app_settings.rs create mode 100644 examples/tutorial_builder/02_apps.md create mode 100644 examples/tutorial_builder/02_apps.rs create mode 100644 examples/tutorial_builder/02_crate.md create mode 100644 examples/tutorial_builder/02_crate.rs create mode 100644 examples/tutorial_builder/03_01_flag_bool.md create mode 100644 examples/tutorial_builder/03_01_flag_bool.rs create mode 100644 examples/tutorial_builder/03_01_flag_count.md create mode 100644 examples/tutorial_builder/03_01_flag_count.rs create mode 100644 examples/tutorial_builder/03_02_option.md create mode 100644 examples/tutorial_builder/03_02_option.rs create mode 100644 examples/tutorial_builder/03_03_positional.md create mode 100644 examples/tutorial_builder/03_03_positional.rs create mode 100644 examples/tutorial_builder/03_04_subcommands.md create mode 100644 examples/tutorial_builder/03_04_subcommands.rs create mode 100644 examples/tutorial_builder/03_05_default_values.md create mode 100644 examples/tutorial_builder/03_05_default_values.rs create mode 100644 examples/tutorial_builder/04_01_enum.md create mode 100644 examples/tutorial_builder/04_01_enum.rs create mode 100644 examples/tutorial_builder/04_01_possible.md create mode 100644 examples/tutorial_builder/04_01_possible.rs create mode 100644 examples/tutorial_builder/04_02_parse.md create mode 100644 examples/tutorial_builder/04_02_parse.rs create mode 100644 examples/tutorial_builder/04_02_validate.md create mode 100644 examples/tutorial_builder/04_02_validate.rs create mode 100644 examples/tutorial_builder/04_03_relations.md create mode 100644 examples/tutorial_builder/04_03_relations.rs create mode 100644 examples/tutorial_builder/04_04_custom.md create mode 100644 examples/tutorial_builder/04_04_custom.rs create mode 100644 examples/tutorial_builder/05_01_assert.rs create mode 100644 examples/tutorial_derive/01_quick.md create mode 100644 examples/tutorial_derive/01_quick.rs create mode 100644 examples/tutorial_derive/02_app_settings.md create mode 100644 examples/tutorial_derive/02_app_settings.rs create mode 100644 examples/tutorial_derive/02_apps.md create mode 100644 examples/tutorial_derive/02_apps.rs create mode 100644 examples/tutorial_derive/02_crate.md create mode 100644 examples/tutorial_derive/02_crate.rs create mode 100644 examples/tutorial_derive/03_01_flag_bool.md create mode 100644 examples/tutorial_derive/03_01_flag_bool.rs create mode 100644 examples/tutorial_derive/03_01_flag_count.md create mode 100644 examples/tutorial_derive/03_01_flag_count.rs create mode 100644 examples/tutorial_derive/03_02_option.md create mode 100644 examples/tutorial_derive/03_02_option.rs create mode 100644 examples/tutorial_derive/03_03_positional.md create mode 100644 examples/tutorial_derive/03_03_positional.rs create mode 100644 examples/tutorial_derive/03_04_subcommands.md create mode 100644 examples/tutorial_derive/03_04_subcommands.rs create mode 100644 examples/tutorial_derive/03_04_subcommands_alt.rs create mode 100644 examples/tutorial_derive/03_05_default_values.md create mode 100644 examples/tutorial_derive/03_05_default_values.rs create mode 100644 examples/tutorial_derive/04_01_enum.md create mode 100644 examples/tutorial_derive/04_01_enum.rs create mode 100644 examples/tutorial_derive/04_02_parse.md create mode 100644 examples/tutorial_derive/04_02_parse.rs create mode 100644 examples/tutorial_derive/04_02_validate.md create mode 100644 examples/tutorial_derive/04_02_validate.rs create mode 100644 examples/tutorial_derive/04_03_relations.md create mode 100644 examples/tutorial_derive/04_03_relations.rs create mode 100644 examples/tutorial_derive/04_04_custom.md create mode 100644 examples/tutorial_derive/04_04_custom.rs create mode 100644 examples/tutorial_derive/05_01_assert.rs create mode 100644 examples/typed-derive.md create mode 100644 examples/typed-derive.rs (limited to 'examples') diff --git a/examples/cargo-example-derive.md b/examples/cargo-example-derive.md new file mode 100644 index 0000000..2c7a11b --- /dev/null +++ b/examples/cargo-example-derive.md @@ -0,0 +1,43 @@ +For more on creating a custom subcommand, see [the cargo +book](https://doc.rust-lang.org/cargo/reference/external-tools.html#custom-subcommands). +The crate [`clap-cargo`](https://github.com/crate-ci/clap-cargo) can help in +mimicking cargo's interface. + +The help looks like: +```console +$ cargo-example-derive --help +cargo + +USAGE: + cargo + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + example-derive A simple to use, efficient, and full-featured Command Line Argument Parser + help Print this message or the help of the given subcommand(s) + +$ cargo-example-derive example-derive --help +cargo-example-derive [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + cargo example-derive [OPTIONS] + +OPTIONS: + -h, --help Print help information + --manifest-path + -V, --version Print version information + +``` + +Then to directly invoke the command, run: +```console +$ cargo-example-derive example-derive +None + +$ cargo-example-derive example-derive --manifest-path Cargo.toml +Some("Cargo.toml") + +``` diff --git a/examples/cargo-example-derive.rs b/examples/cargo-example-derive.rs new file mode 100644 index 0000000..f0aad29 --- /dev/null +++ b/examples/cargo-example-derive.rs @@ -0,0 +1,20 @@ +use clap::Parser; + +#[derive(Parser)] // requires `derive` feature +#[clap(name = "cargo")] +#[clap(bin_name = "cargo")] +enum Cargo { + ExampleDerive(ExampleDerive), +} + +#[derive(clap::Args)] +#[clap(author, version, about, long_about = None)] +struct ExampleDerive { + #[clap(long, value_parser)] + manifest_path: Option, +} + +fn main() { + let Cargo::ExampleDerive(args) = Cargo::parse(); + println!("{:?}", args.manifest_path); +} diff --git a/examples/cargo-example.md b/examples/cargo-example.md new file mode 100644 index 0000000..6fb6a3c --- /dev/null +++ b/examples/cargo-example.md @@ -0,0 +1,43 @@ +For more on creating a custom subcommand, see [the cargo +book](https://doc.rust-lang.org/cargo/reference/external-tools.html#custom-subcommands). +The crate [`clap-cargo`](https://github.com/crate-ci/clap-cargo) can help in +mimicking cargo's interface. + +The help looks like: +```console +$ cargo-example --help +cargo + +USAGE: + cargo + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + example A simple to use, efficient, and full-featured Command Line Argument Parser + help Print this message or the help of the given subcommand(s) + +$ cargo-example example --help +cargo-example [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + cargo example [OPTIONS] + +OPTIONS: + -h, --help Print help information + --manifest-path + -V, --version Print version information + +``` + +Then to directly invoke the command, run: +```console +$ cargo-example example +None + +$ cargo-example example --manifest-path Cargo.toml +Some("Cargo.toml") + +``` diff --git a/examples/cargo-example.rs b/examples/cargo-example.rs new file mode 100644 index 0000000..d9a909f --- /dev/null +++ b/examples/cargo-example.rs @@ -0,0 +1,19 @@ +fn main() { + let cmd = clap::Command::new("cargo") + .bin_name("cargo") + .subcommand_required(true) + .subcommand( + clap::command!("example").arg( + clap::arg!(--"manifest-path" ) + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)), + ), + ); + 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::("manifest-path"); + println!("{:?}", manifest_path); +} diff --git a/examples/demo.md b/examples/demo.md new file mode 100644 index 0000000..93ee49c --- /dev/null +++ b/examples/demo.md @@ -0,0 +1,19 @@ +```console +$ demo --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + demo[EXE] [OPTIONS] --name + +OPTIONS: + -c, --count Number of times to greet [default: 1] + -h, --help Print help information + -n, --name Name of the person to greet + -V, --version Print version information + +$ demo --name Me +Hello Me! + +``` +*(version number and `.exe` extension on windows replaced by placeholders)* diff --git a/examples/demo.rs b/examples/demo.rs new file mode 100644 index 0000000..a7cecfb --- /dev/null +++ b/examples/demo.rs @@ -0,0 +1,22 @@ +use clap::Parser; + +/// Simple program to greet a person +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Name of the person to greet + #[clap(short, long, value_parser)] + name: String, + + /// Number of times to greet + #[clap(short, long, value_parser, default_value_t = 1)] + count: u8, +} + +fn main() { + let args = Args::parse(); + + for _ in 0..args.count { + println!("Hello {}!", args.name) + } +} diff --git a/examples/derive_ref/augment_args.rs b/examples/derive_ref/augment_args.rs new file mode 100644 index 0000000..3105569 --- /dev/null +++ b/examples/derive_ref/augment_args.rs @@ -0,0 +1,27 @@ +use clap::{arg, Args, Command, FromArgMatches as _}; + +#[derive(Args, Debug)] +struct DerivedArgs { + #[clap(short, long, action)] + derived: bool, +} + +fn main() { + let cli = Command::new("CLI").arg(arg!(-b - -built).action(clap::ArgAction::SetTrue)); + // Augment built args with derived args + let cli = DerivedArgs::augment_args(cli); + + let matches = cli.get_matches(); + println!("Value of built: {:?}", matches.get_flag("built")); + println!( + "Value of derived via ArgMatches: {:?}", + matches.get_flag("derived") + ); + + // Since DerivedArgs implements FromArgMatches, we can extract it from the unstructured ArgMatches. + // This is the main benefit of using derived arguments. + let derived_matches = DerivedArgs::from_arg_matches(&matches) + .map_err(|err| err.exit()) + .unwrap(); + println!("Value of derived: {:#?}", derived_matches); +} diff --git a/examples/derive_ref/augment_subcommands.rs b/examples/derive_ref/augment_subcommands.rs new file mode 100644 index 0000000..199da98 --- /dev/null +++ b/examples/derive_ref/augment_subcommands.rs @@ -0,0 +1,21 @@ +use clap::{Command, FromArgMatches as _, Parser, Subcommand as _}; + +#[derive(Parser, Debug)] +enum Subcommands { + Derived { + #[clap(short, long, action)] + derived_flag: bool, + }, +} + +fn main() { + let cli = Command::new("Built CLI"); + // Augment with derived subcommands + let cli = Subcommands::augment_subcommands(cli); + + let matches = cli.get_matches(); + let derived_subcommands = Subcommands::from_arg_matches(&matches) + .map_err(|err| err.exit()) + .unwrap(); + println!("Derived subcommands: {:#?}", derived_subcommands); +} diff --git a/examples/derive_ref/custom-bool.md b/examples/derive_ref/custom-bool.md new file mode 100644 index 0000000..619f9ba --- /dev/null +++ b/examples/derive_ref/custom-bool.md @@ -0,0 +1,47 @@ +*Jump to [source](custom-bool.rs)* + +Example of overriding the magic `bool` behavior + +```console +$ custom-bool --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + custom-bool[EXE] [OPTIONS] --foo + +ARGS: + [possible values: true, false] + +OPTIONS: + --bar [default: false] + --foo [possible values: true, false] + -h, --help Print help information + -V, --version Print version information + +$ custom-bool +? failed +error: The following required arguments were not provided: + --foo + + +USAGE: + custom-bool[EXE] [OPTIONS] --foo + +For more information try --help + +$ custom-bool --foo true false +[examples/derive_ref/custom-bool.rs:31] opt = Opt { + foo: true, + bar: false, + boom: false, +} + +$ custom-bool --foo true --bar true false +[examples/derive_ref/custom-bool.rs:31] opt = Opt { + foo: true, + bar: true, + boom: false, +} + +``` diff --git a/examples/derive_ref/custom-bool.rs b/examples/derive_ref/custom-bool.rs new file mode 100644 index 0000000..d3c321e --- /dev/null +++ b/examples/derive_ref/custom-bool.rs @@ -0,0 +1,32 @@ +use clap::Parser; + +#[derive(Parser, Debug, PartialEq)] +#[clap(author, version, about, long_about = None)] +struct Opt { + // Default parser for `Set` is FromStr::from_str. + // `impl FromStr for bool` parses `true` or `false` so this + // works as expected. + #[clap(long, action = clap::ArgAction::Set)] + foo: bool, + + // Of course, this could be done with an explicit parser function. + #[clap(long, action = clap::ArgAction::Set, value_parser = true_or_false, default_value_t)] + bar: bool, + + // `bool` can be positional only with explicit `action` annotation + #[clap(action = clap::ArgAction::Set)] + boom: bool, +} + +fn true_or_false(s: &str) -> Result { + match s { + "true" => Ok(true), + "false" => Ok(false), + _ => Err("expected `true` or `false`"), + } +} + +fn main() { + let opt = Opt::parse(); + dbg!(opt); +} diff --git a/examples/derive_ref/flatten_hand_args.rs b/examples/derive_ref/flatten_hand_args.rs new file mode 100644 index 0000000..c10e0b2 --- /dev/null +++ b/examples/derive_ref/flatten_hand_args.rs @@ -0,0 +1,81 @@ +use clap::error::Error; +use clap::{Arg, ArgAction, ArgMatches, Args, Command, FromArgMatches, Parser}; + +#[derive(Debug)] +struct CliArgs { + foo: bool, + bar: bool, + quuz: Option, +} + +impl FromArgMatches for CliArgs { + fn from_arg_matches(matches: &ArgMatches) -> Result { + let mut matches = matches.clone(); + Self::from_arg_matches_mut(&mut matches) + } + fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result { + Ok(Self { + foo: matches.get_flag("foo"), + bar: matches.get_flag("bar"), + quuz: matches.remove_one::("quuz"), + }) + } + fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> { + let mut matches = matches.clone(); + self.update_from_arg_matches_mut(&mut matches) + } + fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), Error> { + self.foo |= matches.get_flag("foo"); + self.bar |= matches.get_flag("bar"); + if let Some(quuz) = matches.remove_one::("quuz") { + self.quuz = Some(quuz); + } + Ok(()) + } +} + +impl Args for CliArgs { + fn augment_args(cmd: Command<'_>) -> Command<'_> { + cmd.arg( + Arg::new("foo") + .short('f') + .long("foo") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("bar") + .short('b') + .long("bar") + .action(ArgAction::SetTrue), + ) + .arg(Arg::new("quuz").short('q').long("quuz").takes_value(true)) + } + fn augment_args_for_update(cmd: Command<'_>) -> Command<'_> { + cmd.arg( + Arg::new("foo") + .short('f') + .long("foo") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("bar") + .short('b') + .long("bar") + .action(ArgAction::SetTrue), + ) + .arg(Arg::new("quuz").short('q').long("quuz").takes_value(true)) + } +} + +#[derive(Parser, Debug)] +struct Cli { + #[clap(short, long, action)] + top_level: bool, + #[clap(flatten)] + more_args: CliArgs, +} + +fn main() { + let args = Cli::parse(); + println!("{:#?}", args); +} diff --git a/examples/derive_ref/hand_subcommand.rs b/examples/derive_ref/hand_subcommand.rs new file mode 100644 index 0000000..e9423bd --- /dev/null +++ b/examples/derive_ref/hand_subcommand.rs @@ -0,0 +1,81 @@ +use clap::error::{Error, ErrorKind}; +use clap::{ArgMatches, Args as _, Command, FromArgMatches, Parser, Subcommand}; + +#[derive(Parser, Debug)] +struct AddArgs { + #[clap(value_parser)] + name: Vec, +} +#[derive(Parser, Debug)] +struct RemoveArgs { + #[clap(short, long, action)] + force: bool, + #[clap(value_parser)] + name: Vec, +} + +#[derive(Debug)] +enum CliSub { + Add(AddArgs), + Remove(RemoveArgs), +} + +impl FromArgMatches for CliSub { + fn from_arg_matches(matches: &ArgMatches) -> Result { + match matches.subcommand() { + Some(("add", args)) => Ok(Self::Add(AddArgs::from_arg_matches(args)?)), + Some(("remove", args)) => Ok(Self::Remove(RemoveArgs::from_arg_matches(args)?)), + Some((_, _)) => Err(Error::raw( + ErrorKind::UnrecognizedSubcommand, + "Valid subcommands are `add` and `remove`", + )), + None => Err(Error::raw( + ErrorKind::MissingSubcommand, + "Valid subcommands are `add` and `remove`", + )), + } + } + fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> { + match matches.subcommand() { + Some(("add", args)) => *self = Self::Add(AddArgs::from_arg_matches(args)?), + Some(("remove", args)) => *self = Self::Remove(RemoveArgs::from_arg_matches(args)?), + Some((_, _)) => { + return Err(Error::raw( + ErrorKind::UnrecognizedSubcommand, + "Valid subcommands are `add` and `remove`", + )) + } + None => (), + }; + Ok(()) + } +} + +impl Subcommand for CliSub { + fn augment_subcommands(cmd: Command<'_>) -> Command<'_> { + cmd.subcommand(AddArgs::augment_args(Command::new("add"))) + .subcommand(RemoveArgs::augment_args(Command::new("remove"))) + .subcommand_required(true) + } + fn augment_subcommands_for_update(cmd: Command<'_>) -> Command<'_> { + cmd.subcommand(AddArgs::augment_args(Command::new("add"))) + .subcommand(RemoveArgs::augment_args(Command::new("remove"))) + .subcommand_required(true) + } + fn has_subcommand(name: &str) -> bool { + matches!(name, "add" | "remove") + } +} + +#[derive(Parser, Debug)] +struct Cli { + #[clap(short, long, action)] + top_level: bool, + #[clap(subcommand)] + subcommand: CliSub, +} + +fn main() { + let args = Cli::parse(); + println!("{:#?}", args); +} diff --git a/examples/derive_ref/interop_tests.md b/examples/derive_ref/interop_tests.md new file mode 100644 index 0000000..746fe18 --- /dev/null +++ b/examples/derive_ref/interop_tests.md @@ -0,0 +1,256 @@ +Following are tests for the interop examples in this directory. + +## Augment Args + +```console +$ interop_augment_args +Value of built: false +Value of derived via ArgMatches: false +Value of derived: DerivedArgs { + derived: false, +} + +``` + +```console +$ interop_augment_args -b --derived +Value of built: true +Value of derived via ArgMatches: true +Value of derived: DerivedArgs { + derived: true, +} + +``` + +```console +$ interop_augment_args -d --built +Value of built: true +Value of derived via ArgMatches: true +Value of derived: DerivedArgs { + derived: true, +} + +``` + +```console +$ interop_augment_args --unknown +? failed +error: Found argument '--unknown' which wasn't expected, or isn't valid in this context + + If you tried to supply `--unknown` as a value rather than a flag, use `-- --unknown` + +USAGE: + interop_augment_args[EXE] [OPTIONS] + +For more information try --help + +``` + +## Augment Subcommands + +```console +$ interop_augment_subcommands +? failed +error: A subcommand is required but one was not provided. +``` + +```console +$ interop_augment_subcommands derived +Derived subcommands: Derived { + derived_flag: false, +} + +``` + +```console +$ interop_augment_subcommands derived --derived-flag +Derived subcommands: Derived { + derived_flag: true, +} + +``` + +```console +$ interop_augment_subcommands derived --unknown +? failed +error: Found argument '--unknown' which wasn't expected, or isn't valid in this context + + If you tried to supply `--unknown` as a value rather than a flag, use `-- --unknown` + +USAGE: + interop_augment_subcommands[EXE] derived [OPTIONS] + +For more information try --help + +``` + +```console +$ interop_augment_subcommands unknown +? failed +error: Found argument 'unknown' which wasn't expected, or isn't valid in this context + +USAGE: + interop_augment_subcommands[EXE] [SUBCOMMAND] + +For more information try --help + +``` + +## Hand-Implemented Subcommand + +```console +$ interop_hand_subcommand +? failed +error: 'interop_hand_subcommand[EXE]' requires a subcommand but one was not provided + +USAGE: + interop_hand_subcommand[EXE] [OPTIONS] + +For more information try --help + +``` + +```console +$ interop_hand_subcommand add +Cli { + top_level: false, + subcommand: Add( + AddArgs { + name: [], + }, + ), +} + +``` + +```console +$ interop_hand_subcommand add a b c +Cli { + top_level: false, + subcommand: Add( + AddArgs { + name: [ + "a", + "b", + "c", + ], + }, + ), +} + +``` + +```console +$ interop_hand_subcommand add --unknown +? failed +error: Found argument '--unknown' which wasn't expected, or isn't valid in this context + + If you tried to supply `--unknown` as a value rather than a flag, use `-- --unknown` + +USAGE: + interop_hand_subcommand[EXE] add [NAME]... + +For more information try --help + +``` + +```console +$ interop_hand_subcommand remove +Cli { + top_level: false, + subcommand: Remove( + RemoveArgs { + force: false, + name: [], + }, + ), +} + +``` + +```console +$ interop_hand_subcommand remove --force a b c +Cli { + top_level: false, + subcommand: Remove( + RemoveArgs { + force: true, + name: [ + "a", + "b", + "c", + ], + }, + ), +} + +``` + +```console +$ interop_hand_subcommand unknown +? failed +error: Found argument 'unknown' which wasn't expected, or isn't valid in this context + +USAGE: + interop_hand_subcommand[EXE] [OPTIONS] + +For more information try --help + +``` + +## Flatten Hand-Implemented Args + +```console +$ interop_flatten_hand_args +Cli { + top_level: false, + more_args: CliArgs { + foo: false, + bar: false, + quuz: None, + }, +} + +``` + +```console +$ interop_flatten_hand_args -f --bar +Cli { + top_level: false, + more_args: CliArgs { + foo: true, + bar: true, + quuz: None, + }, +} + +``` + +```console +$ interop_flatten_hand_args --quuz abc +Cli { + top_level: false, + more_args: CliArgs { + foo: false, + bar: false, + quuz: Some( + "abc", + ), + }, +} + +``` + +```console +$ interop_flatten_hand_args --unknown +? failed +error: Found argument '--unknown' which wasn't expected, or isn't valid in this context + + If you tried to supply `--unknown` as a value rather than a flag, use `-- --unknown` + +USAGE: + interop_flatten_hand_args[EXE] [OPTIONS] + +For more information try --help + +``` diff --git a/examples/escaped-positional-derive.md b/examples/escaped-positional-derive.md new file mode 100644 index 0000000..fa39a2c --- /dev/null +++ b/examples/escaped-positional-derive.md @@ -0,0 +1,63 @@ +**This requires enabling the [`derive` feature flag][crate::_features].** + +You can use `--` to escape further arguments. + +Let's see what this looks like in the help: +```console +$ escaped-positional-derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + escaped-positional-derive[EXE] [OPTIONS] [-- ...] + +ARGS: + ... + +OPTIONS: + -f + -h, --help Print help information + -p + -V, --version Print version information + +``` + +Here is a baseline without any arguments: +```console +$ escaped-positional-derive +-f used: false +-p's value: None +'slops' values: [] + +``` + +Notice that we can't pass positional arguments before `--`: +```console +$ escaped-positional-derive foo bar +? failed +error: Found argument 'foo' which wasn't expected, or isn't valid in this context + +USAGE: + escaped-positional-derive[EXE] [OPTIONS] [-- ...] + +For more information try --help + +``` + +But you can after: +```console +$ escaped-positional-derive -f -p=bob -- sloppy slop slop +-f used: true +-p's value: Some("bob") +'slops' values: ["sloppy", "slop", "slop"] + +``` + +As mentioned, the parser will directly pass everything through: +```console +$ escaped-positional-derive -- -f -p=bob sloppy slop slop +-f used: false +-p's value: None +'slops' values: ["-f", "-p=bob", "sloppy", "slop", "slop"] + +``` diff --git a/examples/escaped-positional-derive.rs b/examples/escaped-positional-derive.rs new file mode 100644 index 0000000..fd8fde4 --- /dev/null +++ b/examples/escaped-positional-derive.rs @@ -0,0 +1,25 @@ +use clap::Parser; + +#[derive(Parser)] // requires `derive` feature +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(short = 'f', action)] + eff: bool, + + #[clap(short = 'p', value_name = "PEAR", value_parser)] + pea: Option, + + #[clap(last = true, value_parser)] + slop: Vec, +} + +fn main() { + let args = Cli::parse(); + + // This is what will happen with `myprog -f -p=bob -- sloppy slop slop`... + println!("-f used: {:?}", args.eff); // -f used: true + println!("-p's value: {:?}", args.pea); // -p's value: Some("bob") + println!("'slops' values: {:?}", args.slop); // 'slops' values: Some(["sloppy", "slop", "slop"]) + + // Continued program logic goes here... +} diff --git a/examples/escaped-positional.md b/examples/escaped-positional.md new file mode 100644 index 0000000..987b261 --- /dev/null +++ b/examples/escaped-positional.md @@ -0,0 +1,63 @@ +**This requires enabling the [`cargo` feature flag][crate::_features].** + +You can use `--` to escape further arguments. + +Let's see what this looks like in the help: +```console +$ escaped-positional --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + escaped-positional[EXE] [OPTIONS] [-- ...] + +ARGS: + ... + +OPTIONS: + -f + -h, --help Print help information + -p + -V, --version Print version information + +``` + +Here is a baseline without any arguments: +```console +$ escaped-positional +-f used: false +-p's value: None +'slops' values: [] + +``` + +Notice that we can't pass positional arguments before `--`: +```console +$ escaped-positional foo bar +? failed +error: Found argument 'foo' which wasn't expected, or isn't valid in this context + +USAGE: + escaped-positional[EXE] [OPTIONS] [-- ...] + +For more information try --help + +``` + +But you can after: +```console +$ escaped-positional -f -p=bob -- sloppy slop slop +-f used: true +-p's value: Some("bob") +'slops' values: ["sloppy", "slop", "slop"] + +``` + +As mentioned, the parser will directly pass everything through: +```console +$ escaped-positional -- -f -p=bob sloppy slop slop +-f used: false +-p's value: None +'slops' values: ["-f", "-p=bob", "sloppy", "slop", "slop"] + +``` diff --git a/examples/escaped-positional.rs b/examples/escaped-positional.rs new file mode 100644 index 0000000..fdcb930 --- /dev/null +++ b/examples/escaped-positional.rs @@ -0,0 +1,36 @@ +use clap::{arg, command, value_parser, ArgAction}; + +fn main() { + let matches = command!() // requires `cargo` feature + .arg(arg!(eff: -f).action(ArgAction::SetTrue)) + .arg( + arg!(pea: -p ) + .required(false) + .value_parser(value_parser!(String)), + ) + .arg( + // Indicates that `slop` is only accessible after `--`. + arg!(slop: [SLOP]) + .multiple_values(true) + .last(true) + .value_parser(value_parser!(String)), + ) + .get_matches(); + + // This is what will happen with `myprog -f -p=bob -- sloppy slop slop`... + + // -f used: true + println!("-f used: {:?}", matches.get_flag("eff")); + // -p's value: Some("bob") + println!("-p's value: {:?}", matches.get_one::("pea")); + // 'slops' values: Some(["sloppy", "slop", "slop"]) + println!( + "'slops' values: {:?}", + matches + .get_many::("slop") + .map(|vals| vals.collect::>()) + .unwrap_or_default() + ); + + // Continued program logic goes here... +} diff --git a/examples/git-derive.md b/examples/git-derive.md new file mode 100644 index 0000000..aa1ca6d --- /dev/null +++ b/examples/git-derive.md @@ -0,0 +1,138 @@ +**This requires enabling the [`derive` feature flag][crate::_features].** + +Git is an example of several common subcommand patterns. + +Help: +```console +$ git-derive +? failed +git +A fictional versioning CLI + +USAGE: + git-derive[EXE] + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + add adds things + clone Clones repos + help Print this message or the help of the given subcommand(s) + push pushes things + stash + +$ git-derive help +git +A fictional versioning CLI + +USAGE: + git-derive[EXE] + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + add adds things + clone Clones repos + help Print this message or the help of the given subcommand(s) + push pushes things + stash + +$ git-derive help add +git-derive[EXE]-add +adds things + +USAGE: + git-derive[EXE] add ... + +ARGS: + ... Stuff to add + +OPTIONS: + -h, --help Print help information + +``` + +A basic argument: +```console +$ git-derive add +? failed +git-derive[EXE]-add +adds things + +USAGE: + git-derive[EXE] add ... + +ARGS: + ... Stuff to add + +OPTIONS: + -h, --help Print help information + +$ git-derive add Cargo.toml Cargo.lock +Adding ["Cargo.toml", "Cargo.lock"] + +``` + +Default subcommand: +```console +$ git-derive stash -h +git-derive[EXE]-stash + +USAGE: + git-derive[EXE] stash [OPTIONS] + git-derive[EXE] stash + +OPTIONS: + -h, --help Print help information + -m, --message + +SUBCOMMANDS: + apply + help Print this message or the help of the given subcommand(s) + pop + push + +$ git-derive stash push -h +git-derive[EXE]-stash-push + +USAGE: + git-derive[EXE] stash push [OPTIONS] + +OPTIONS: + -h, --help Print help information + -m, --message + +$ git-derive stash pop -h +git-derive[EXE]-stash-pop + +USAGE: + git-derive[EXE] stash pop [STASH] + +ARGS: + + +OPTIONS: + -h, --help Print help information + +$ git-derive stash -m "Prototype" +Pushing StashPush { message: Some("Prototype") } + +$ git-derive stash pop +Popping None + +$ git-derive stash push -m "Prototype" +Pushing StashPush { message: Some("Prototype") } + +$ git-derive stash pop +Popping None + +``` + +External subcommands: +```console +$ git-derive custom-tool arg1 --foo bar +Calling out to "custom-tool" with ["arg1", "--foo", "bar"] + +``` diff --git a/examples/git-derive.rs b/examples/git-derive.rs new file mode 100644 index 0000000..ac500dd --- /dev/null +++ b/examples/git-derive.rs @@ -0,0 +1,105 @@ +use std::ffi::OsString; +use std::path::PathBuf; + +use clap::{Args, Parser, Subcommand}; + +/// A fictional versioning CLI +#[derive(Debug, Parser)] // requires `derive` feature +#[clap(name = "git")] +#[clap(about = "A fictional versioning CLI", long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Clones repos + #[clap(arg_required_else_help = true)] + Clone { + /// The remote to clone + #[clap(value_parser)] + remote: String, + }, + /// pushes things + #[clap(arg_required_else_help = true)] + Push { + /// The remote to target + #[clap(value_parser)] + remote: String, + }, + /// adds things + #[clap(arg_required_else_help = true)] + Add { + /// Stuff to add + #[clap(required = true, value_parser)] + path: Vec, + }, + Stash(Stash), + #[clap(external_subcommand)] + External(Vec), +} + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true)] +struct Stash { + #[clap(subcommand)] + command: Option, + + #[clap(flatten)] + push: StashPush, +} + +#[derive(Debug, Subcommand)] +enum StashCommands { + Push(StashPush), + Pop { + #[clap(value_parser)] + stash: Option, + }, + Apply { + #[clap(value_parser)] + stash: Option, + }, +} + +#[derive(Debug, Args)] +struct StashPush { + #[clap(short, long, value_parser)] + message: Option, +} + +fn main() { + let args = Cli::parse(); + + match args.command { + Commands::Clone { remote } => { + println!("Cloning {}", remote); + } + Commands::Push { remote } => { + println!("Pushing to {}", remote); + } + Commands::Add { path } => { + println!("Adding {:?}", path); + } + Commands::Stash(stash) => { + let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push)); + match stash_cmd { + StashCommands::Push(push) => { + println!("Pushing {:?}", push); + } + StashCommands::Pop { stash } => { + println!("Popping {:?}", stash); + } + StashCommands::Apply { stash } => { + println!("Applying {:?}", stash); + } + } + } + Commands::External(args) => { + println!("Calling out to {:?} with {:?}", &args[0], &args[1..]); + } + } + + // Continued program logic goes here... +} diff --git a/examples/git.md b/examples/git.md new file mode 100644 index 0000000..9e41341 --- /dev/null +++ b/examples/git.md @@ -0,0 +1,136 @@ +Git is an example of several common subcommand patterns. + +Help: +```console +$ git +? failed +git +A fictional versioning CLI + +USAGE: + git[EXE] + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + add adds things + clone Clones repos + help Print this message or the help of the given subcommand(s) + push pushes things + stash + +$ git help +git +A fictional versioning CLI + +USAGE: + git[EXE] + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + add adds things + clone Clones repos + help Print this message or the help of the given subcommand(s) + push pushes things + stash + +$ git help add +git[EXE]-add +adds things + +USAGE: + git[EXE] add ... + +ARGS: + ... Stuff to add + +OPTIONS: + -h, --help Print help information + +``` + +A basic argument: +```console +$ git add +? failed +git[EXE]-add +adds things + +USAGE: + git[EXE] add ... + +ARGS: + ... Stuff to add + +OPTIONS: + -h, --help Print help information + +$ git add Cargo.toml Cargo.lock +Adding ["Cargo.toml", "Cargo.lock"] + +``` + +Default subcommand: +```console +$ git stash -h +git[EXE]-stash + +USAGE: + git[EXE] stash [OPTIONS] + git[EXE] stash + +OPTIONS: + -h, --help Print help information + -m, --message + +SUBCOMMANDS: + apply + help Print this message or the help of the given subcommand(s) + pop + push + +$ git stash push -h +git[EXE]-stash-push + +USAGE: + git[EXE] stash push [OPTIONS] + +OPTIONS: + -h, --help Print help information + -m, --message + +$ git stash pop -h +git[EXE]-stash-pop + +USAGE: + git[EXE] stash pop [STASH] + +ARGS: + + +OPTIONS: + -h, --help Print help information + +$ git stash -m "Prototype" +Pushing Some("Prototype") + +$ git stash pop +Popping None + +$ git stash push -m "Prototype" +Pushing Some("Prototype") + +$ git stash pop +Popping None + +``` + +External subcommands: +```console +$ git custom-tool arg1 --foo bar +Calling out to "custom-tool" with ["arg1", "--foo", "bar"] + +``` diff --git a/examples/git.rs b/examples/git.rs new file mode 100644 index 0000000..e56f427 --- /dev/null +++ b/examples/git.rs @@ -0,0 +1,101 @@ +use std::ffi::OsString; +use std::path::PathBuf; + +use clap::{arg, Command}; + +fn cli() -> Command<'static> { + Command::new("git") + .about("A fictional versioning CLI") + .subcommand_required(true) + .arg_required_else_help(true) + .allow_external_subcommands(true) + .allow_invalid_utf8_for_external_subcommands(true) + .subcommand( + Command::new("clone") + .about("Clones repos") + .arg(arg!( "The remote to clone")) + .arg_required_else_help(true), + ) + .subcommand( + Command::new("push") + .about("pushes things") + .arg(arg!( "The remote to target")) + .arg_required_else_help(true), + ) + .subcommand( + Command::new("add") + .about("adds things") + .arg_required_else_help(true) + .arg(arg!( ... "Stuff to add").value_parser(clap::value_parser!(PathBuf))), + ) + .subcommand( + Command::new("stash") + .args_conflicts_with_subcommands(true) + .args(push_args()) + .subcommand(Command::new("push").args(push_args())) + .subcommand(Command::new("pop").arg(arg!([STASH]))) + .subcommand(Command::new("apply").arg(arg!([STASH]))), + ) +} + +fn push_args() -> Vec> { + vec![arg!(-m --message ).required(false)] +} + +fn main() { + let matches = cli().get_matches(); + + match matches.subcommand() { + Some(("clone", sub_matches)) => { + println!( + "Cloning {}", + sub_matches.get_one::("REMOTE").expect("required") + ); + } + Some(("push", sub_matches)) => { + println!( + "Pushing to {}", + sub_matches.get_one::("REMOTE").expect("required") + ); + } + Some(("add", sub_matches)) => { + let paths = sub_matches + .get_many::("PATH") + .into_iter() + .flatten() + .collect::>(); + println!("Adding {:?}", paths); + } + Some(("stash", sub_matches)) => { + let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches)); + match stash_command { + ("apply", sub_matches) => { + let stash = sub_matches.get_one::("STASH"); + println!("Applying {:?}", stash); + } + ("pop", sub_matches) => { + let stash = sub_matches.get_one::("STASH"); + println!("Popping {:?}", stash); + } + ("push", sub_matches) => { + let message = sub_matches.get_one::("message"); + println!("Pushing {:?}", message); + } + (name, _) => { + unreachable!("Unsupported subcommand `{}`", name) + } + } + } + Some((ext, sub_matches)) => { + let args = sub_matches + .get_many::("") + .into_iter() + .flatten() + .collect::>(); + println!("Calling out to {:?} with {:?}", ext, args); + } + _ => unreachable!(), // If all subcommands are defined above, anything else is unreachabe!() + } + + // Continued program logic goes here... +} diff --git a/examples/multicall-busybox.md b/examples/multicall-busybox.md new file mode 100644 index 0000000..5ca2cad --- /dev/null +++ b/examples/multicall-busybox.md @@ -0,0 +1,42 @@ +See the documentation for [`Command::multicall`][crate::App::multicall] for rationale. + +This example omits every command except true and false, +which are the most trivial to implement, +```console +$ busybox true +? 0 + +$ busybox false +? 1 + +``` +*Note: without the links setup, we can't demonstrate the multicall behavior* + +But includes the `--install` option as an example of why it can be useful +for the main program to take arguments that aren't applet subcommands. +```console +$ busybox --install +? failed +... + +``` + +Though users must pass something: +```console +$ busybox +? failed +busybox + +USAGE: + busybox [OPTIONS] [APPLET] + +OPTIONS: + -h, --help Print help information + --install Install hardlinks for all subcommands in path + +APPLETS: + false does nothing unsuccessfully + help Print this message or the help of the given subcommand(s) + true does nothing successfully + +``` diff --git a/examples/multicall-busybox.rs b/examples/multicall-busybox.rs new file mode 100644 index 0000000..2e7f976 --- /dev/null +++ b/examples/multicall-busybox.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; +use std::process::exit; + +use clap::{value_parser, Arg, Command}; + +fn applet_commands() -> [Command<'static>; 2] { + [ + Command::new("true").about("does nothing successfully"), + Command::new("false").about("does nothing unsuccessfully"), + ] +} + +fn main() { + let cmd = Command::new(env!("CARGO_CRATE_NAME")) + .multicall(true) + .subcommand( + Command::new("busybox") + .arg_required_else_help(true) + .subcommand_value_name("APPLET") + .subcommand_help_heading("APPLETS") + .arg( + Arg::new("install") + .long("install") + .help("Install hardlinks for all subcommands in path") + .exclusive(true) + .takes_value(true) + .default_missing_value("/usr/local/bin") + .value_parser(value_parser!(PathBuf)) + .use_value_delimiter(false), + ) + .subcommands(applet_commands()), + ) + .subcommands(applet_commands()); + + let matches = cmd.get_matches(); + let mut subcommand = matches.subcommand(); + if let Some(("busybox", cmd)) = subcommand { + if cmd.contains_id("install") { + unimplemented!("Make hardlinks to the executable here"); + } + subcommand = cmd.subcommand(); + } + match subcommand { + Some(("false", _)) => exit(1), + Some(("true", _)) => exit(0), + _ => unreachable!("parser should ensure only valid subcommand names are used"), + } +} diff --git a/examples/multicall-hostname.md b/examples/multicall-hostname.md new file mode 100644 index 0000000..d22b85f --- /dev/null +++ b/examples/multicall-hostname.md @@ -0,0 +1,10 @@ +See the documentation for [`Command::multicall`][crate::App::multicall] for rationale. + +This example omits the implementation of displaying address config + +```console +$ hostname +www + +``` +*Note: without the links setup, we can't demonstrate the multicall behavior* diff --git a/examples/multicall-hostname.rs b/examples/multicall-hostname.rs new file mode 100644 index 0000000..b57680a --- /dev/null +++ b/examples/multicall-hostname.rs @@ -0,0 +1,17 @@ +use clap::Command; + +fn main() { + let cmd = Command::new(env!("CARGO_CRATE_NAME")) + .multicall(true) + .arg_required_else_help(true) + .subcommand_value_name("APPLET") + .subcommand_help_heading("APPLETS") + .subcommand(Command::new("hostname").about("show hostname part of FQDN")) + .subcommand(Command::new("dnsdomainname").about("show domain name part of FQDN")); + + match cmd.get_matches().subcommand_name() { + Some("hostname") => println!("www"), + Some("dnsdomainname") => println!("example.com"), + _ => unreachable!("parser should ensure only valid subcommand names are used"), + } +} diff --git a/examples/pacman.md b/examples/pacman.md new file mode 100644 index 0000000..b8ddd09 --- /dev/null +++ b/examples/pacman.md @@ -0,0 +1,85 @@ +[`pacman`](https://wiki.archlinux.org/index.php/pacman) defines subcommands via flags. + +Here, `-S` is a short flag subcommand: +```console +$ pacman -S package +Installing package... + +``` + +Here `--sync` is a long flag subcommand: +```console +$ pacman --sync package +Installing package... + +``` + +Now the short flag subcommand (`-S`) with a long flag: +```console +$ pacman -S --search name +Searching for name... + +``` + +And the various forms of short flags that work: +```console +$ pacman -S -s name +Searching for name... + +$ pacman -Ss name +Searching for name... + +``` +*(users can "stack" short subcommands with short flags or with other short flag subcommands)* + +In the help, this looks like: +```console +$ pacman -h +pacman 5.2.1 +Pacman Development Team +package manager utility + +USAGE: + pacman[EXE] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + help Print this message or the help of the given subcommand(s) + query -Q --query Query the package database. + sync -S --sync Synchronize packages. + +$ pacman -S -h +pacman[EXE]-sync +Synchronize packages. + +USAGE: + pacman[EXE] {sync|--sync|-S} [OPTIONS] [--] [package]... + +ARGS: + ... packages + +OPTIONS: + -h, --help Print help information + -i, --info view package information + -s, --search ... search remote repositories for matching strings + +``` + +And errors: +```console +$ pacman -S -s foo -i bar +? failed +error: The argument '--search ...' cannot be used with '--info' + +USAGE: + pacman[EXE] {sync|--sync|-S} --search ... ... + +For more information try --help + +``` + +**NOTE:** Keep in mind that subcommands, flags, and long flags are *case sensitive*: `-Q` and `-q` are different flags/subcommands. For example, you can have both `-Q` subcommand and `-q` flag, and they will be properly disambiguated. +Let's make a quick program to illustrate. diff --git a/examples/pacman.rs b/examples/pacman.rs new file mode 100644 index 0000000..aee7cd8 --- /dev/null +++ b/examples/pacman.rs @@ -0,0 +1,111 @@ +use clap::{Arg, ArgAction, Command}; + +fn main() { + let matches = Command::new("pacman") + .about("package manager utility") + .version("5.2.1") + .subcommand_required(true) + .arg_required_else_help(true) + .author("Pacman Development Team") + // Query subcommand + // + // Only a few of its arguments are implemented below. + .subcommand( + Command::new("query") + .short_flag('Q') + .long_flag("query") + .about("Query the package database.") + .arg( + Arg::new("search") + .short('s') + .long("search") + .help("search locally installed packages for matching strings") + .conflicts_with("info") + .takes_value(true) + .multiple_values(true), + ) + .arg( + Arg::new("info") + .long("info") + .short('i') + .conflicts_with("search") + .help("view package information") + .takes_value(true) + .multiple_values(true), + ), + ) + // Sync subcommand + // + // Only a few of its arguments are implemented below. + .subcommand( + Command::new("sync") + .short_flag('S') + .long_flag("sync") + .about("Synchronize packages.") + .arg( + Arg::new("search") + .short('s') + .long("search") + .conflicts_with("info") + .takes_value(true) + .multiple_values(true) + .help("search remote repositories for matching strings"), + ) + .arg( + Arg::new("info") + .long("info") + .conflicts_with("search") + .short('i') + .action(ArgAction::SetTrue) + .help("view package information"), + ) + .arg( + Arg::new("package") + .help("packages") + .required_unless_present("search") + .takes_value(true) + .multiple_values(true), + ), + ) + .get_matches(); + + match matches.subcommand() { + Some(("sync", sync_matches)) => { + if sync_matches.contains_id("search") { + let packages: Vec<_> = sync_matches + .get_many::("search") + .expect("contains_id") + .map(|s| s.as_str()) + .collect(); + let values = packages.join(", "); + println!("Searching for {}...", values); + return; + } + + let packages: Vec<_> = sync_matches + .get_many::("package") + .expect("is present") + .map(|s| s.as_str()) + .collect(); + let values = packages.join(", "); + + if sync_matches.get_flag("info") { + println!("Retrieving info for {}...", values); + } else { + println!("Installing {}...", values); + } + } + Some(("query", query_matches)) => { + if let Some(packages) = query_matches.get_many::("info") { + let comma_sep = packages.map(|s| s.as_str()).collect::>().join(", "); + println!("Retrieving info for {}...", comma_sep); + } else if let Some(queries) = query_matches.get_many::("search") { + let comma_sep = queries.map(|s| s.as_str()).collect::>().join(", "); + println!("Searching Locally for {}...", comma_sep); + } else { + println!("Displaying all locally installed packages..."); + } + } + _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable + } +} diff --git a/examples/repl.rs b/examples/repl.rs new file mode 100644 index 0000000..c509ade --- /dev/null +++ b/examples/repl.rs @@ -0,0 +1,92 @@ +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 { + 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<'static> { + // 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 { + 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) +} diff --git a/examples/tutorial_builder/01_quick.md b/examples/tutorial_builder/01_quick.md new file mode 100644 index 0000000..4a9e3fc --- /dev/null +++ b/examples/tutorial_builder/01_quick.md @@ -0,0 +1,37 @@ +```console +$ 01_quick --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 01_quick[EXE] [OPTIONS] [name] [SUBCOMMAND] + +ARGS: + Optional name to operate on + +OPTIONS: + -c, --config Sets a custom config file + -d, --debug Turn debugging information on + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + help Print this message or the help of the given subcommand(s) + test does testing things + +``` + +By default, the program does nothing: +```console +$ 01_quick +Debug mode is off + +``` + +But you can mix and match the various features +```console +$ 01_quick -dd test +Debug mode is on +Not printing testing lists... + +``` diff --git a/examples/tutorial_builder/01_quick.rs b/examples/tutorial_builder/01_quick.rs new file mode 100644 index 0000000..393d6ae --- /dev/null +++ b/examples/tutorial_builder/01_quick.rs @@ -0,0 +1,63 @@ +use std::path::PathBuf; + +use clap::{arg, command, value_parser, ArgAction, Command}; + +fn main() { + 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" + ) + .action(ArgAction::Count), + ) + .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_one::("list").expect("defaulted by clap") { + // "$ myapp test -l" was run + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + + // Continued program logic goes here... +} diff --git a/examples/tutorial_builder/02_app_settings.md b/examples/tutorial_builder/02_app_settings.md new file mode 100644 index 0000000..247843b --- /dev/null +++ b/examples/tutorial_builder/02_app_settings.md @@ -0,0 +1,19 @@ +```console +$ 02_app_settings --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 02_app_settings[EXE] --two --one + +OPTIONS: + --two + --one + -h, --help Print help information + -V, --version Print version information + +$ 02_app_settings --one -1 --one -3 --two 10 +two: "10" +one: "-3" + +``` diff --git a/examples/tutorial_builder/02_app_settings.rs b/examples/tutorial_builder/02_app_settings.rs new file mode 100644 index 0000000..5f374d7 --- /dev/null +++ b/examples/tutorial_builder/02_app_settings.rs @@ -0,0 +1,19 @@ +use clap::{arg, command, AppSettings, ArgAction}; + +fn main() { + let matches = command!() // requires `cargo` feature + .global_setting(AppSettings::DeriveDisplayOrder) + .allow_negative_numbers(true) + .arg(arg!(--two ).action(ArgAction::Set)) + .arg(arg!(--one ).action(ArgAction::Set)) + .get_matches(); + + println!( + "two: {:?}", + matches.get_one::("two").expect("required") + ); + println!( + "one: {:?}", + matches.get_one::("one").expect("required") + ); +} diff --git a/examples/tutorial_builder/02_apps.md b/examples/tutorial_builder/02_apps.md new file mode 100644 index 0000000..8504b5f --- /dev/null +++ b/examples/tutorial_builder/02_apps.md @@ -0,0 +1,19 @@ +```console +$ 02_apps --help +MyApp 1.0 +Kevin K. +Does awesome things + +USAGE: + 02_apps[EXE] --two --one + +OPTIONS: + -h, --help Print help information + --one + --two + -V, --version Print version information + +$ 02_apps --version +MyApp 1.0 + +``` diff --git a/examples/tutorial_builder/02_apps.rs b/examples/tutorial_builder/02_apps.rs new file mode 100644 index 0000000..db9da18 --- /dev/null +++ b/examples/tutorial_builder/02_apps.rs @@ -0,0 +1,20 @@ +use clap::{arg, Command}; + +fn main() { + let matches = Command::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .arg(arg!(--two )) + .arg(arg!(--one )) + .get_matches(); + + println!( + "two: {:?}", + matches.get_one::("two").expect("required") + ); + println!( + "one: {:?}", + matches.get_one::("one").expect("required") + ); +} diff --git a/examples/tutorial_builder/02_crate.md b/examples/tutorial_builder/02_crate.md new file mode 100644 index 0000000..f76e0d6 --- /dev/null +++ b/examples/tutorial_builder/02_crate.md @@ -0,0 +1,18 @@ +```console +$ 02_crate --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 02_crate[EXE] --two --one + +OPTIONS: + -h, --help Print help information + --one + --two + -V, --version Print version information + +$ 02_crate --version +clap [..] + +``` diff --git a/examples/tutorial_builder/02_crate.rs b/examples/tutorial_builder/02_crate.rs new file mode 100644 index 0000000..8a1722f --- /dev/null +++ b/examples/tutorial_builder/02_crate.rs @@ -0,0 +1,18 @@ +use clap::{arg, command}; + +fn main() { + // requires `cargo` feature, reading name, version, author, and description from `Cargo.toml` + let matches = command!() + .arg(arg!(--two )) + .arg(arg!(--one )) + .get_matches(); + + println!( + "two: {:?}", + matches.get_one::("two").expect("required") + ); + println!( + "one: {:?}", + matches.get_one::("one").expect("required") + ); +} diff --git a/examples/tutorial_builder/03_01_flag_bool.md b/examples/tutorial_builder/03_01_flag_bool.md new file mode 100644 index 0000000..b0ee2a1 --- /dev/null +++ b/examples/tutorial_builder/03_01_flag_bool.md @@ -0,0 +1,23 @@ +```console +$ 03_01_flag_bool --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_01_flag_bool[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -v, --verbose + -V, --version Print version information + +$ 03_01_flag_bool +verbose: false + +$ 03_01_flag_bool --verbose +verbose: true + +$ 03_01_flag_bool --verbose --verbose +verbose: true + +``` diff --git a/examples/tutorial_builder/03_01_flag_bool.rs b/examples/tutorial_builder/03_01_flag_bool.rs new file mode 100644 index 0000000..03f2f17 --- /dev/null +++ b/examples/tutorial_builder/03_01_flag_bool.rs @@ -0,0 +1,14 @@ +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")); +} diff --git a/examples/tutorial_builder/03_01_flag_count.md b/examples/tutorial_builder/03_01_flag_count.md new file mode 100644 index 0000000..ca4bcd6 --- /dev/null +++ b/examples/tutorial_builder/03_01_flag_count.md @@ -0,0 +1,23 @@ +```console +$ 03_01_flag_count --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_01_flag_count[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -v, --verbose + -V, --version Print version information + +$ 03_01_flag_count +verbose: 0 + +$ 03_01_flag_count --verbose +verbose: 1 + +$ 03_01_flag_count --verbose --verbose +verbose: 2 + +``` diff --git a/examples/tutorial_builder/03_01_flag_count.rs b/examples/tutorial_builder/03_01_flag_count.rs new file mode 100644 index 0000000..7f23649 --- /dev/null +++ b/examples/tutorial_builder/03_01_flag_count.rs @@ -0,0 +1,9 @@ +use clap::{arg, command, ArgAction}; + +fn main() { + let matches = command!() // requires `cargo` feature + .arg(arg!(-v - -verbose).action(ArgAction::Count)) + .get_matches(); + + println!("verbose: {:?}", matches.get_count("verbose")); +} diff --git a/examples/tutorial_builder/03_02_option.md b/examples/tutorial_builder/03_02_option.md new file mode 100644 index 0000000..6198bcc --- /dev/null +++ b/examples/tutorial_builder/03_02_option.md @@ -0,0 +1,32 @@ +```console +$ 03_02_option --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_02_option[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -n, --name + -V, --version Print version information + +$ 03_02_option +name: None + +$ 03_02_option --name bob +name: Some("bob") + +$ 03_02_option --name=bob +name: Some("bob") + +$ 03_02_option -n bob +name: Some("bob") + +$ 03_02_option -n=bob +name: Some("bob") + +$ 03_02_option -nbob +name: Some("bob") + +``` diff --git a/examples/tutorial_builder/03_02_option.rs b/examples/tutorial_builder/03_02_option.rs new file mode 100644 index 0000000..1882445 --- /dev/null +++ b/examples/tutorial_builder/03_02_option.rs @@ -0,0 +1,9 @@ +use clap::{arg, command}; + +fn main() { + let matches = command!() // requires `cargo` feature + .arg(arg!(-n --name ).required(false)) + .get_matches(); + + println!("name: {:?}", matches.get_one::("name")); +} diff --git a/examples/tutorial_builder/03_03_positional.md b/examples/tutorial_builder/03_03_positional.md new file mode 100644 index 0000000..a3d7b8c --- /dev/null +++ b/examples/tutorial_builder/03_03_positional.md @@ -0,0 +1,22 @@ +```console +$ 03_03_positional --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_03_positional[EXE] [NAME] + +ARGS: + + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_03_positional +NAME: None + +$ 03_03_positional bob +NAME: Some("bob") + +``` diff --git a/examples/tutorial_builder/03_03_positional.rs b/examples/tutorial_builder/03_03_positional.rs new file mode 100644 index 0000000..0c3a5f0 --- /dev/null +++ b/examples/tutorial_builder/03_03_positional.rs @@ -0,0 +1,9 @@ +use clap::{arg, command}; + +fn main() { + let matches = command!() // requires `cargo` feature + .arg(arg!([NAME])) + .get_matches(); + + println!("NAME: {:?}", matches.get_one::("NAME")); +} diff --git a/examples/tutorial_builder/03_04_subcommands.md b/examples/tutorial_builder/03_04_subcommands.md new file mode 100644 index 0000000..8f9efa9 --- /dev/null +++ b/examples/tutorial_builder/03_04_subcommands.md @@ -0,0 +1,64 @@ +```console +$ 03_04_subcommands help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_04_subcommands[EXE] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + add Adds files to myapp + help Print this message or the help of the given subcommand(s) + +$ 03_04_subcommands help add +03_04_subcommands[EXE]-add [..] +Adds files to myapp + +USAGE: + 03_04_subcommands[EXE] add [NAME] + +ARGS: + + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_04_subcommands add bob +'myapp add' was used, name is: Some("bob") + +``` + +Because we set [`Command::arg_required_else_help`][crate::Command::arg_required_else_help]: +```console +$ 03_04_subcommands +? failed +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_04_subcommands[EXE] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + add Adds files to myapp + help Print this message or the help of the given subcommand(s) + +``` + +Because we set [`Command::propagate_version`][crate::Command::propagate_version]: +```console +$ 03_04_subcommands --version +clap [..] + +$ 03_04_subcommands add --version +03_04_subcommands[EXE]-add [..] + +``` diff --git a/examples/tutorial_builder/03_04_subcommands.rs b/examples/tutorial_builder/03_04_subcommands.rs new file mode 100644 index 0000000..fbe2380 --- /dev/null +++ b/examples/tutorial_builder/03_04_subcommands.rs @@ -0,0 +1,22 @@ +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`"), + } +} diff --git a/examples/tutorial_builder/03_05_default_values.md b/examples/tutorial_builder/03_05_default_values.md new file mode 100644 index 0000000..7b6c030 --- /dev/null +++ b/examples/tutorial_builder/03_05_default_values.md @@ -0,0 +1,22 @@ +```console +$ 03_05_default_values --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_05_default_values[EXE] [NAME] + +ARGS: + [default: alice] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_05_default_values +NAME: "alice" + +$ 03_05_default_values bob +NAME: "bob" + +``` diff --git a/examples/tutorial_builder/03_05_default_values.rs b/examples/tutorial_builder/03_05_default_values.rs new file mode 100644 index 0000000..cb3e383 --- /dev/null +++ b/examples/tutorial_builder/03_05_default_values.rs @@ -0,0 +1,14 @@ +use clap::{arg, command}; + +fn main() { + let matches = command!() // requires `cargo` feature + .arg(arg!([NAME]).default_value("alice")) + .get_matches(); + + println!( + "NAME: {:?}", + matches + .get_one::("NAME") + .expect("default ensures there is always a value") + ); +} diff --git a/examples/tutorial_builder/04_01_enum.md b/examples/tutorial_builder/04_01_enum.md new file mode 100644 index 0000000..282b1e6 --- /dev/null +++ b/examples/tutorial_builder/04_01_enum.md @@ -0,0 +1,29 @@ +```console +$ 04_01_enum --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_01_enum[EXE] + +ARGS: + What mode to run the program in [possible values: fast, slow] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_01_enum fast +Hare + +$ 04_01_enum slow +Tortoise + +$ 04_01_enum medium +? failed +error: "medium" isn't a valid value for '' + [possible values: fast, slow] + +For more information try --help + +``` diff --git a/examples/tutorial_builder/04_01_enum.rs b/examples/tutorial_builder/04_01_enum.rs new file mode 100644 index 0000000..e8cf70f --- /dev/null +++ b/examples/tutorial_builder/04_01_enum.rs @@ -0,0 +1,66 @@ +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"), + Mode::Slow => PossibleValue::new("slow"), + }) + } +} + +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"); + } + } +} diff --git a/examples/tutorial_builder/04_01_possible.md b/examples/tutorial_builder/04_01_possible.md new file mode 100644 index 0000000..2909bfb --- /dev/null +++ b/examples/tutorial_builder/04_01_possible.md @@ -0,0 +1,29 @@ +```console +$ 04_01_possible --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_01_possible[EXE] + +ARGS: + What mode to run the program in [possible values: fast, slow] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_01_possible fast +Hare + +$ 04_01_possible slow +Tortoise + +$ 04_01_possible medium +? failed +error: "medium" isn't a valid value for '' + [possible values: fast, slow] + +For more information try --help + +``` diff --git a/examples/tutorial_builder/04_01_possible.rs b/examples/tutorial_builder/04_01_possible.rs new file mode 100644 index 0000000..3da7aca --- /dev/null +++ b/examples/tutorial_builder/04_01_possible.rs @@ -0,0 +1,26 @@ +use clap::{arg, command}; + +fn main() { + 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!(), + } +} diff --git a/examples/tutorial_builder/04_02_parse.md b/examples/tutorial_builder/04_02_parse.md new file mode 100644 index 0000000..605bf4b --- /dev/null +++ b/examples/tutorial_builder/04_02_parse.md @@ -0,0 +1,31 @@ +```console +$ 04_02_parse --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_02_parse[EXE] + +ARGS: + Network port to use + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_02_parse 22 +PORT = 22 + +$ 04_02_parse foobar +? failed +error: Invalid value "foobar" for '': invalid digit found in string + +For more information try --help + +$ 04_02_parse_derive 0 +? failed +error: Invalid value "0" for '': 0 is not in 1..=65535 + +For more information try --help + +``` diff --git a/examples/tutorial_builder/04_02_parse.rs b/examples/tutorial_builder/04_02_parse.rs new file mode 100644 index 0000000..13f41a1 --- /dev/null +++ b/examples/tutorial_builder/04_02_parse.rs @@ -0,0 +1,17 @@ +use clap::{arg, command, value_parser}; + +fn main() { + 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); +} diff --git a/examples/tutorial_builder/04_02_validate.md b/examples/tutorial_builder/04_02_validate.md new file mode 100644 index 0000000..a317a8e --- /dev/null +++ b/examples/tutorial_builder/04_02_validate.md @@ -0,0 +1,31 @@ +```console +$ 04_02_validate --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_02_validate[EXE] + +ARGS: + Network port to use + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_02_validate 22 +PORT = 22 + +$ 04_02_validate foobar +? failed +error: Invalid value "foobar" for '': `foobar` isn't a port number + +For more information try --help + +$ 04_02_validate 0 +? failed +error: Invalid value "0" for '': Port not in range 1-65535 + +For more information try --help + +``` diff --git a/examples/tutorial_builder/04_02_validate.rs b/examples/tutorial_builder/04_02_validate.rs new file mode 100644 index 0000000..ea2f32e --- /dev/null +++ b/examples/tutorial_builder/04_02_validate.rs @@ -0,0 +1,36 @@ +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!("`{}` isn't a port number", s))?; + if PORT_RANGE.contains(&port) { + Ok(port as u16) + } else { + Err(format!( + "Port not in range {}-{}", + PORT_RANGE.start(), + PORT_RANGE.end() + )) + } +} diff --git a/examples/tutorial_builder/04_03_relations.md b/examples/tutorial_builder/04_03_relations.md new file mode 100644 index 0000000..3ea3363 --- /dev/null +++ b/examples/tutorial_builder/04_03_relations.md @@ -0,0 +1,58 @@ +```console +$ 04_03_relations --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_03_relations[EXE] [OPTIONS] <--set-ver |--major|--minor|--patch> [INPUT_FILE] + +ARGS: + some regular input + +OPTIONS: + -c + -h, --help Print help information + --major auto inc major + --minor auto inc minor + --patch auto inc patch + --set-ver set version manually + --spec-in some special input argument + -V, --version Print version information + +$ 04_03_relations +? failed +error: The following required arguments were not provided: + <--set-ver |--major|--minor|--patch> + +USAGE: + 04_03_relations[EXE] [OPTIONS] <--set-ver |--major|--minor|--patch> [INPUT_FILE] + +For more information try --help + +$ 04_03_relations --major +Version: 2.2.3 + +$ 04_03_relations --major --minor +? failed +error: The argument '--major' cannot be used with '--minor' + +USAGE: + 04_03_relations[EXE] <--set-ver |--major|--minor|--patch> + +For more information try --help + +$ 04_03_relations --major -c config.toml +? failed +error: The following required arguments were not provided: + > + +USAGE: + 04_03_relations[EXE] -c <--set-ver |--major|--minor|--patch> > + +For more information try --help + +$ 04_03_relations --major -c config.toml --spec-in input.txt +Version: 2.2.3 +Doing work using input input.txt and config config.toml + +``` diff --git a/examples/tutorial_builder/04_03_relations.rs b/examples/tutorial_builder/04_03_relations.rs new file mode 100644 index 0000000..8e26d3e --- /dev/null +++ b/examples/tutorial_builder/04_03_relations.rs @@ -0,0 +1,80 @@ +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").required(false)) + .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") + .required(false) + .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 ) + .required(false) + .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() + ); + } +} diff --git a/examples/tutorial_builder/04_04_custom.md b/examples/tutorial_builder/04_04_custom.md new file mode 100644 index 0000000..26a3df1 --- /dev/null +++ b/examples/tutorial_builder/04_04_custom.md @@ -0,0 +1,57 @@ +```console +$ 04_04_custom --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_04_custom[EXE] [OPTIONS] [INPUT_FILE] + +ARGS: + some regular input + +OPTIONS: + -c + -h, --help Print help information + --major auto inc major + --minor auto inc minor + --patch auto inc patch + --set-ver set version manually + --spec-in some special input argument + -V, --version Print version information + +$ 04_04_custom +? failed +error: Can only modify one version field + +USAGE: + 04_04_custom[EXE] [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom --major +Version: 2.2.3 + +$ 04_04_custom --major --minor +? failed +error: Can only modify one version field + +USAGE: + 04_04_custom[EXE] [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom --major -c config.toml +? failed +Version: 2.2.3 +error: INPUT_FILE or --spec-in is required when using --config + +USAGE: + 04_04_custom[EXE] [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom --major -c config.toml --spec-in input.txt +Version: 2.2.3 +Doing work using input input.txt and config config.toml + +``` diff --git a/examples/tutorial_builder/04_04_custom.rs b/examples/tutorial_builder/04_04_custom.rs new file mode 100644 index 0000000..fb40b2f --- /dev/null +++ b/examples/tutorial_builder/04_04_custom.rs @@ -0,0 +1,88 @@ +use std::path::PathBuf; + +use clap::{arg, command, value_parser, ArgAction, ErrorKind}; + +fn main() { + // Create application like normal + let mut cmd = command!() // requires `cargo` feature + // Add the version arguments + .arg(arg!(--"set-ver" "set version manually").required(false)) + .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") + .required(false) + .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 ) + .required(false) + .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() + ); + } +} diff --git a/examples/tutorial_builder/05_01_assert.rs b/examples/tutorial_builder/05_01_assert.rs new file mode 100644 index 0000000..4b92542 --- /dev/null +++ b/examples/tutorial_builder/05_01_assert.rs @@ -0,0 +1,25 @@ +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<'static> { + command!() // requires `cargo` feature + .arg( + arg!() + .help("Network port to use") + .value_parser(value_parser!(usize)), + ) +} + +#[test] +fn verify_cmd() { + cmd().debug_assert(); +} diff --git a/examples/tutorial_derive/01_quick.md b/examples/tutorial_derive/01_quick.md new file mode 100644 index 0000000..6f70d2c --- /dev/null +++ b/examples/tutorial_derive/01_quick.md @@ -0,0 +1,37 @@ +```console +$ 01_quick_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 01_quick_derive[EXE] [OPTIONS] [NAME] [SUBCOMMAND] + +ARGS: + Optional name to operate on + +OPTIONS: + -c, --config Sets a custom config file + -d, --debug Turn debugging information on + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + help Print this message or the help of the given subcommand(s) + test does testing things + +``` + +By default, the program does nothing: +```console +$ 01_quick_derive +Debug mode is off + +``` + +But you can mix and match the various features +```console +$ 01_quick_derive -dd test +Debug mode is on +Not printing testing lists... + +``` diff --git a/examples/tutorial_derive/01_quick.rs b/examples/tutorial_derive/01_quick.rs new file mode 100644 index 0000000..2c20310 --- /dev/null +++ b/examples/tutorial_derive/01_quick.rs @@ -0,0 +1,69 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Optional name to operate on + #[clap(value_parser)] + name: Option, + + /// Sets a custom config file + #[clap(short, long, value_parser, value_name = "FILE")] + config: Option, + + /// Turn debugging information on + #[clap(short, long, action = clap::ArgAction::Count)] + debug: u8, + + #[clap(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands { + /// does testing things + Test { + /// lists test values + #[clap(short, long, action)] + list: bool, + }, +} + +fn main() { + let cli = Cli::parse(); + + // You can check the value provided by positional arguments, or option arguments + if let Some(name) = cli.name.as_deref() { + println!("Value for name: {}", name); + } + + if let Some(config_path) = cli.config.as_deref() { + 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 cli.debug { + 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 + match &cli.command { + Some(Commands::Test { list }) => { + if *list { + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + None => {} + } + + // Continued program logic goes here... +} diff --git a/examples/tutorial_derive/02_app_settings.md b/examples/tutorial_derive/02_app_settings.md new file mode 100644 index 0000000..ee16c66 --- /dev/null +++ b/examples/tutorial_derive/02_app_settings.md @@ -0,0 +1,19 @@ +```console +$ 02_app_settings_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 02_app_settings_derive[EXE] --two --one + +OPTIONS: + --two + --one + -h, --help Print help information + -V, --version Print version information + +$ 02_app_settings_derive --one -1 --one -3 --two 10 +two: "10" +one: "-3" + +``` diff --git a/examples/tutorial_derive/02_app_settings.rs b/examples/tutorial_derive/02_app_settings.rs new file mode 100644 index 0000000..6a06929 --- /dev/null +++ b/examples/tutorial_derive/02_app_settings.rs @@ -0,0 +1,19 @@ +use clap::{AppSettings, Parser}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(allow_negative_numbers = true)] +#[clap(global_setting(AppSettings::DeriveDisplayOrder))] +struct Cli { + #[clap(long, value_parser)] + two: String, + #[clap(long, value_parser)] + one: String, +} + +fn main() { + let cli = Cli::parse(); + + println!("two: {:?}", cli.two); + println!("one: {:?}", cli.one); +} diff --git a/examples/tutorial_derive/02_apps.md b/examples/tutorial_derive/02_apps.md new file mode 100644 index 0000000..0141581 --- /dev/null +++ b/examples/tutorial_derive/02_apps.md @@ -0,0 +1,19 @@ +```console +$ 02_apps_derive --help +MyApp 1.0 +Kevin K. +Does awesome things + +USAGE: + 02_apps_derive[EXE] --two --one + +OPTIONS: + -h, --help Print help information + --one + --two + -V, --version Print version information + +$ 02_apps_derive --version +MyApp 1.0 + +``` diff --git a/examples/tutorial_derive/02_apps.rs b/examples/tutorial_derive/02_apps.rs new file mode 100644 index 0000000..b97ce1e --- /dev/null +++ b/examples/tutorial_derive/02_apps.rs @@ -0,0 +1,20 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(name = "MyApp")] +#[clap(author = "Kevin K. ")] +#[clap(version = "1.0")] +#[clap(about = "Does awesome things", long_about = None)] +struct Cli { + #[clap(long, value_parser)] + two: String, + #[clap(long, value_parser)] + one: String, +} + +fn main() { + let cli = Cli::parse(); + + println!("two: {:?}", cli.two); + println!("one: {:?}", cli.one); +} diff --git a/examples/tutorial_derive/02_crate.md b/examples/tutorial_derive/02_crate.md new file mode 100644 index 0000000..92c8207 --- /dev/null +++ b/examples/tutorial_derive/02_crate.md @@ -0,0 +1,18 @@ +```console +$ 02_crate_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 02_crate_derive[EXE] --two --one + +OPTIONS: + -h, --help Print help information + --one + --two + -V, --version Print version information + +$ 02_crate_derive --version +clap [..] + +``` diff --git a/examples/tutorial_derive/02_crate.rs b/examples/tutorial_derive/02_crate.rs new file mode 100644 index 0000000..5576f99 --- /dev/null +++ b/examples/tutorial_derive/02_crate.rs @@ -0,0 +1,17 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] // Read from `Cargo.toml` +struct Cli { + #[clap(long, value_parser)] + two: String, + #[clap(long, value_parser)] + one: String, +} + +fn main() { + let cli = Cli::parse(); + + println!("two: {:?}", cli.two); + println!("one: {:?}", cli.one); +} diff --git a/examples/tutorial_derive/03_01_flag_bool.md b/examples/tutorial_derive/03_01_flag_bool.md new file mode 100644 index 0000000..0baaa10 --- /dev/null +++ b/examples/tutorial_derive/03_01_flag_bool.md @@ -0,0 +1,23 @@ +```console +$ 03_01_flag_bool_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_01_flag_bool_derive[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -v, --verbose + -V, --version Print version information + +$ 03_01_flag_bool_derive +verbose: false + +$ 03_01_flag_bool_derive --verbose +verbose: true + +$ 03_01_flag_bool_derive --verbose --verbose +verbose: true + +``` diff --git a/examples/tutorial_derive/03_01_flag_bool.rs b/examples/tutorial_derive/03_01_flag_bool.rs new file mode 100644 index 0000000..de677d8 --- /dev/null +++ b/examples/tutorial_derive/03_01_flag_bool.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(short, long, action)] + verbose: bool, +} + +fn main() { + let cli = Cli::parse(); + + println!("verbose: {:?}", cli.verbose); +} diff --git a/examples/tutorial_derive/03_01_flag_count.md b/examples/tutorial_derive/03_01_flag_count.md new file mode 100644 index 0000000..3d5a530 --- /dev/null +++ b/examples/tutorial_derive/03_01_flag_count.md @@ -0,0 +1,23 @@ +```console +$ 03_01_flag_count_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_01_flag_count_derive[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -v, --verbose + -V, --version Print version information + +$ 03_01_flag_count_derive +verbose: 0 + +$ 03_01_flag_count_derive --verbose +verbose: 1 + +$ 03_01_flag_count_derive --verbose --verbose +verbose: 2 + +``` diff --git a/examples/tutorial_derive/03_01_flag_count.rs b/examples/tutorial_derive/03_01_flag_count.rs new file mode 100644 index 0000000..680f7f5 --- /dev/null +++ b/examples/tutorial_derive/03_01_flag_count.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u8, +} + +fn main() { + let cli = Cli::parse(); + + println!("verbose: {:?}", cli.verbose); +} diff --git a/examples/tutorial_derive/03_02_option.md b/examples/tutorial_derive/03_02_option.md new file mode 100644 index 0000000..84ff7fa --- /dev/null +++ b/examples/tutorial_derive/03_02_option.md @@ -0,0 +1,32 @@ +```console +$ 03_02_option_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_02_option_derive[EXE] [OPTIONS] + +OPTIONS: + -h, --help Print help information + -n, --name + -V, --version Print version information + +$ 03_02_option_derive +name: None + +$ 03_02_option_derive --name bob +name: Some("bob") + +$ 03_02_option_derive --name=bob +name: Some("bob") + +$ 03_02_option_derive -n bob +name: Some("bob") + +$ 03_02_option_derive -n=bob +name: Some("bob") + +$ 03_02_option_derive -nbob +name: Some("bob") + +``` diff --git a/examples/tutorial_derive/03_02_option.rs b/examples/tutorial_derive/03_02_option.rs new file mode 100644 index 0000000..75b67af --- /dev/null +++ b/examples/tutorial_derive/03_02_option.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(short, long, value_parser)] + name: Option, +} + +fn main() { + let cli = Cli::parse(); + + println!("name: {:?}", cli.name.as_deref()); +} diff --git a/examples/tutorial_derive/03_03_positional.md b/examples/tutorial_derive/03_03_positional.md new file mode 100644 index 0000000..7281fe3 --- /dev/null +++ b/examples/tutorial_derive/03_03_positional.md @@ -0,0 +1,22 @@ +```console +$ 03_03_positional_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_03_positional_derive[EXE] [NAME] + +ARGS: + + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_03_positional_derive +name: None + +$ 03_03_positional_derive bob +name: Some("bob") + +``` diff --git a/examples/tutorial_derive/03_03_positional.rs b/examples/tutorial_derive/03_03_positional.rs new file mode 100644 index 0000000..7478951 --- /dev/null +++ b/examples/tutorial_derive/03_03_positional.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(value_parser)] + name: Option, +} + +fn main() { + let cli = Cli::parse(); + + println!("name: {:?}", cli.name.as_deref()); +} diff --git a/examples/tutorial_derive/03_04_subcommands.md b/examples/tutorial_derive/03_04_subcommands.md new file mode 100644 index 0000000..02f96e3 --- /dev/null +++ b/examples/tutorial_derive/03_04_subcommands.md @@ -0,0 +1,64 @@ +```console +$ 03_04_subcommands_derive help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_04_subcommands_derive[EXE] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + add Adds files to myapp + help Print this message or the help of the given subcommand(s) + +$ 03_04_subcommands_derive help add +03_04_subcommands_derive[EXE]-add [..] +Adds files to myapp + +USAGE: + 03_04_subcommands_derive[EXE] add [NAME] + +ARGS: + + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_04_subcommands_derive add bob +'myapp add' was used, name is: Some("bob") + +``` + +Because we used `command: Commands` instead of `command: Option`: +```console +$ 03_04_subcommands_derive +? failed +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_04_subcommands_derive[EXE] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + add Adds files to myapp + help Print this message or the help of the given subcommand(s) + +``` + +Because we added `#[clap(propagate_version = true)]`: +```console +$ 03_04_subcommands_derive --version +clap [..] + +$ 03_04_subcommands_derive add --version +03_04_subcommands_derive[EXE]-add [..] + +``` diff --git a/examples/tutorial_derive/03_04_subcommands.rs b/examples/tutorial_derive/03_04_subcommands.rs new file mode 100644 index 0000000..62a45a9 --- /dev/null +++ b/examples/tutorial_derive/03_04_subcommands.rs @@ -0,0 +1,30 @@ +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Adds files to myapp + Add { + #[clap(value_parser)] + name: Option, + }, +} + +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) + } + } +} diff --git a/examples/tutorial_derive/03_04_subcommands_alt.rs b/examples/tutorial_derive/03_04_subcommands_alt.rs new file mode 100644 index 0000000..b612493 --- /dev/null +++ b/examples/tutorial_derive/03_04_subcommands_alt.rs @@ -0,0 +1,33 @@ +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Adds files to myapp + Add(Add), +} + +#[derive(Args)] +struct Add { + #[clap(value_parser)] + name: Option, +} + +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.name) + } + } +} diff --git a/examples/tutorial_derive/03_05_default_values.md b/examples/tutorial_derive/03_05_default_values.md new file mode 100644 index 0000000..f7315e7 --- /dev/null +++ b/examples/tutorial_derive/03_05_default_values.md @@ -0,0 +1,22 @@ +```console +$ 03_05_default_values_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 03_05_default_values_derive[EXE] [NAME] + +ARGS: + [default: alice] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 03_05_default_values_derive +name: "alice" + +$ 03_05_default_values_derive bob +name: "bob" + +``` diff --git a/examples/tutorial_derive/03_05_default_values.rs b/examples/tutorial_derive/03_05_default_values.rs new file mode 100644 index 0000000..10a1ec8 --- /dev/null +++ b/examples/tutorial_derive/03_05_default_values.rs @@ -0,0 +1,14 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(default_value_t = String::from("alice"), value_parser)] + name: String, +} + +fn main() { + let cli = Cli::parse(); + + println!("name: {:?}", cli.name); +} diff --git a/examples/tutorial_derive/04_01_enum.md b/examples/tutorial_derive/04_01_enum.md new file mode 100644 index 0000000..2523af9 --- /dev/null +++ b/examples/tutorial_derive/04_01_enum.md @@ -0,0 +1,29 @@ +```console +$ 04_01_enum_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_01_enum_derive[EXE] + +ARGS: + What mode to run the program in [possible values: fast, slow] + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_01_enum_derive fast +Hare + +$ 04_01_enum_derive slow +Tortoise + +$ 04_01_enum_derive medium +? failed +error: "medium" isn't a valid value for '' + [possible values: fast, slow] + +For more information try --help + +``` diff --git a/examples/tutorial_derive/04_01_enum.rs b/examples/tutorial_derive/04_01_enum.rs new file mode 100644 index 0000000..1ac9842 --- /dev/null +++ b/examples/tutorial_derive/04_01_enum.rs @@ -0,0 +1,28 @@ +use clap::{Parser, ValueEnum}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// What mode to run the program in + #[clap(arg_enum, value_parser)] + mode: Mode, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum Mode { + Fast, + Slow, +} + +fn main() { + let cli = Cli::parse(); + + match cli.mode { + Mode::Fast => { + println!("Hare"); + } + Mode::Slow => { + println!("Tortoise"); + } + } +} diff --git a/examples/tutorial_derive/04_02_parse.md b/examples/tutorial_derive/04_02_parse.md new file mode 100644 index 0000000..6079ef1 --- /dev/null +++ b/examples/tutorial_derive/04_02_parse.md @@ -0,0 +1,31 @@ +```console +$ 04_02_parse_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_02_parse_derive[EXE] + +ARGS: + Network port to use + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_02_parse_derive 22 +PORT = 22 + +$ 04_02_parse_derive foobar +? failed +error: Invalid value "foobar" for '': invalid digit found in string + +For more information try --help + +$ 04_02_parse_derive 0 +? failed +error: Invalid value "0" for '': 0 is not in 1..=65535 + +For more information try --help + +``` diff --git a/examples/tutorial_derive/04_02_parse.rs b/examples/tutorial_derive/04_02_parse.rs new file mode 100644 index 0000000..6336a9c --- /dev/null +++ b/examples/tutorial_derive/04_02_parse.rs @@ -0,0 +1,15 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Network port to use + #[clap(value_parser = clap::value_parser!(u16).range(1..))] + port: u16, +} + +fn main() { + let cli = Cli::parse(); + + println!("PORT = {}", cli.port); +} diff --git a/examples/tutorial_derive/04_02_validate.md b/examples/tutorial_derive/04_02_validate.md new file mode 100644 index 0000000..cc43440 --- /dev/null +++ b/examples/tutorial_derive/04_02_validate.md @@ -0,0 +1,31 @@ +```console +$ 04_02_validate_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_02_validate_derive[EXE] + +ARGS: + Network port to use + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +$ 04_02_validate_derive 22 +PORT = 22 + +$ 04_02_validate_derive foobar +? failed +error: Invalid value "foobar" for '': `foobar` isn't a port number + +For more information try --help + +$ 04_02_validate_derive 0 +? failed +error: Invalid value "0" for '': Port not in range 1-65535 + +For more information try --help + +``` diff --git a/examples/tutorial_derive/04_02_validate.rs b/examples/tutorial_derive/04_02_validate.rs new file mode 100644 index 0000000..7dac79c --- /dev/null +++ b/examples/tutorial_derive/04_02_validate.rs @@ -0,0 +1,34 @@ +use std::ops::RangeInclusive; + +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Network port to use + #[clap(value_parser = port_in_range)] + port: u16, +} + +fn main() { + let cli = Cli::parse(); + + println!("PORT = {}", cli.port); +} + +const PORT_RANGE: RangeInclusive = 1..=65535; + +fn port_in_range(s: &str) -> Result { + let port: usize = s + .parse() + .map_err(|_| format!("`{}` isn't a port number", s))?; + if PORT_RANGE.contains(&port) { + Ok(port as u16) + } else { + Err(format!( + "Port not in range {}-{}", + PORT_RANGE.start(), + PORT_RANGE.end() + )) + } +} diff --git a/examples/tutorial_derive/04_03_relations.md b/examples/tutorial_derive/04_03_relations.md new file mode 100644 index 0000000..4f5703d --- /dev/null +++ b/examples/tutorial_derive/04_03_relations.md @@ -0,0 +1,58 @@ +```console +$ 04_03_relations_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_03_relations_derive[EXE] [OPTIONS] <--set-ver |--major|--minor|--patch> [INPUT_FILE] + +ARGS: + some regular input + +OPTIONS: + -c + -h, --help Print help information + --major auto inc major + --minor auto inc minor + --patch auto inc patch + --set-ver set version manually + --spec-in some special input argument + -V, --version Print version information + +$ 04_03_relations_derive +? failed +error: The following required arguments were not provided: + <--set-ver |--major|--minor|--patch> + +USAGE: + 04_03_relations_derive[EXE] [OPTIONS] <--set-ver |--major|--minor|--patch> [INPUT_FILE] + +For more information try --help + +$ 04_03_relations_derive --major +Version: 2.2.3 + +$ 04_03_relations_derive --major --minor +? failed +error: The argument '--major' cannot be used with '--minor' + +USAGE: + 04_03_relations_derive[EXE] <--set-ver |--major|--minor|--patch> + +For more information try --help + +$ 04_03_relations_derive --major -c config.toml +? failed +error: The following required arguments were not provided: + > + +USAGE: + 04_03_relations_derive[EXE] -c <--set-ver |--major|--minor|--patch> > + +For more information try --help + +$ 04_03_relations_derive --major -c config.toml --spec-in input.txt +Version: 2.2.3 +Doing work using input input.txt and config config.toml + +``` diff --git a/examples/tutorial_derive/04_03_relations.rs b/examples/tutorial_derive/04_03_relations.rs new file mode 100644 index 0000000..37efe95 --- /dev/null +++ b/examples/tutorial_derive/04_03_relations.rs @@ -0,0 +1,72 @@ +use clap::{ArgGroup, Parser}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(group( + ArgGroup::new("vers") + .required(true) + .args(&["set-ver", "major", "minor", "patch"]), + ))] +struct Cli { + /// set version manually + #[clap(long, value_name = "VER", value_parser)] + set_ver: Option, + + /// auto inc major + #[clap(long, action)] + major: bool, + + /// auto inc minor + #[clap(long, action)] + minor: bool, + + /// auto inc patch + #[clap(long, action)] + patch: bool, + + /// some regular input + #[clap(group = "input", value_parser)] + input_file: Option, + + /// some special input argument + #[clap(long, group = "input", value_parser)] + spec_in: Option, + + #[clap(short, requires = "input", value_parser)] + config: Option, +} + +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() { + 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, _, _) => 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 {} and config {}", input, config); + } +} diff --git a/examples/tutorial_derive/04_04_custom.md b/examples/tutorial_derive/04_04_custom.md new file mode 100644 index 0000000..0f19bfe --- /dev/null +++ b/examples/tutorial_derive/04_04_custom.md @@ -0,0 +1,57 @@ +```console +$ 04_04_custom_derive --help +clap [..] +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + 04_04_custom_derive[EXE] [OPTIONS] [INPUT_FILE] + +ARGS: + some regular input + +OPTIONS: + -c + -h, --help Print help information + --major auto inc major + --minor auto inc minor + --patch auto inc patch + --set-ver set version manually + --spec-in some special input argument + -V, --version Print version information + +$ 04_04_custom_derive +? failed +error: Can only modify one version field + +USAGE: + clap [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom_derive --major +Version: 2.2.3 + +$ 04_04_custom_derive --major --minor +? failed +error: Can only modify one version field + +USAGE: + clap [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom_derive --major -c config.toml +? failed +Version: 2.2.3 +error: INPUT_FILE or --spec-in is required when using --config + +USAGE: + clap [OPTIONS] [INPUT_FILE] + +For more information try --help + +$ 04_04_custom_derive --major -c config.toml --spec-in input.txt +Version: 2.2.3 +Doing work using input input.txt and config config.toml + +``` diff --git a/examples/tutorial_derive/04_04_custom.rs b/examples/tutorial_derive/04_04_custom.rs new file mode 100644 index 0000000..454d489 --- /dev/null +++ b/examples/tutorial_derive/04_04_custom.rs @@ -0,0 +1,93 @@ +use clap::{CommandFactory, ErrorKind, Parser}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// set version manually + #[clap(long, value_name = "VER", value_parser)] + set_ver: Option, + + /// auto inc major + #[clap(long, action)] + major: bool, + + /// auto inc minor + #[clap(long, action)] + minor: bool, + + /// auto inc patch + #[clap(long, action)] + patch: bool, + + /// some regular input + #[clap(value_parser)] + input_file: Option, + + /// some special input argument + #[clap(long, value_parser)] + spec_in: Option, + + #[clap(short, value_parser)] + config: Option, +} + +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() { + // todo: remove `#[allow(clippy::or_fun_call)]` lint when MSRV is bumped. + #[allow(clippy::or_fun_call)] + 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 {} and config {}", input, config); + } +} diff --git a/examples/tutorial_derive/05_01_assert.rs b/examples/tutorial_derive/05_01_assert.rs new file mode 100644 index 0000000..66dca72 --- /dev/null +++ b/examples/tutorial_derive/05_01_assert.rs @@ -0,0 +1,21 @@ +use clap::Parser; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Network port to use + #[clap(value_parser)] + port: u16, +} + +fn main() { + let cli = Cli::parse(); + + println!("PORT = {}", cli.port); +} + +#[test] +fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert() +} diff --git a/examples/typed-derive.md b/examples/typed-derive.md new file mode 100644 index 0000000..9c7615c --- /dev/null +++ b/examples/typed-derive.md @@ -0,0 +1,84 @@ +**This requires enabling the [`derive` feature flag][crate::_features].** + +Help: +```console +$ typed-derive --help +clap + +USAGE: + typed-derive[EXE] [OPTIONS] + +OPTIONS: + --bind Handle IP addresses + -D Hand-written parser for tuples + -h, --help Print help information + -I Allow invalid UTF-8 paths + -O Implicitly using `std::str::FromStr` + --sleep Allow human-readable durations + +``` + +Optimization-level (number) +```console +$ typed-derive -O 1 +Args { optimization: Some(1), include: None, bind: None, sleep: None, defines: [] } + +$ typed-derive -O plaid +? failed +error: Invalid value "plaid" for '-O ': invalid digit found in string + +For more information try --help + +``` + +Include (path) +```console +$ typed-derive -I../hello +Args { optimization: None, include: Some("../hello"), bind: None, sleep: None, defines: [] } + +``` + +IP Address +```console +$ typed-derive --bind 192.0.0.1 +Args { optimization: None, include: None, bind: Some(192.0.0.1), sleep: None, defines: [] } + +$ typed-derive --bind localhost +? failed +error: Invalid value "localhost" for '--bind ': invalid IP address syntax + +For more information try --help + +``` + +Time +```console +$ typed-derive --sleep 10s +Args { optimization: None, include: None, bind: None, sleep: Some(Duration(10s)), defines: [] } + +$ typed-derive --sleep forever +? failed +error: Invalid value "forever" for '--sleep ': expected number at 0 + +For more information try --help + +``` + +Defines (key-value pairs) +```console +$ typed-derive -D Foo=10 -D Alice=30 +Args { optimization: None, include: None, bind: None, sleep: None, defines: [("Foo", 10), ("Alice", 30)] } + +$ typed-derive -D Foo +? failed +error: Invalid value "Foo" for '-D ': invalid KEY=value: no `=` found in `Foo` + +For more information try --help + +$ typed-derive -D Foo=Bar +? failed +error: Invalid value "Foo=Bar" for '-D ': invalid digit found in string + +For more information try --help + +``` diff --git a/examples/typed-derive.rs b/examples/typed-derive.rs new file mode 100644 index 0000000..3108402 --- /dev/null +++ b/examples/typed-derive.rs @@ -0,0 +1,44 @@ +use clap::Parser; +use std::error::Error; + +#[derive(Parser, Debug)] // requires `derive` feature +struct Args { + /// Implicitly using `std::str::FromStr` + #[clap(short = 'O', value_parser)] + optimization: Option, + + /// Allow invalid UTF-8 paths + #[clap(short = 'I', value_parser, value_name = "DIR", value_hint = clap::ValueHint::DirPath)] + include: Option, + + /// Handle IP addresses + #[clap(long, value_parser)] + bind: Option, + + /// Allow human-readable durations + #[clap(long, value_parser)] + sleep: Option, + + /// Hand-written parser for tuples + #[clap(short = 'D', value_parser = parse_key_val::)] + defines: Vec<(String, i32)>, +} + +/// Parse a single key-value pair +fn parse_key_val(s: &str) -> Result<(T, U), Box> +where + T: std::str::FromStr, + T::Err: Error + Send + Sync + 'static, + U: std::str::FromStr, + U::Err: Error + Send + Sync + 'static, +{ + let pos = s + .find('=') + .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?; + Ok((s[..pos].parse()?, s[pos + 1..].parse()?)) +} + +fn main() { + let args = Args::parse(); + println!("{:?}", args); +} -- cgit v1.2.3