From 9c9cc9fcff1ac8b233597eb1a7d1079d66530352 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Aug 2022 10:40:20 -0500 Subject: [PATCH] docs(cookbook): Add position-sensitive example --- Cargo.toml | 4 ++ examples/find.md | 47 ++++++++++++++++++++ examples/find.rs | 99 +++++++++++++++++++++++++++++++++++++++++++ src/_cookbook/find.rs | 7 +++ src/_cookbook/mod.rs | 5 +++ 5 files changed, 162 insertions(+) create mode 100644 examples/find.md create mode 100644 examples/find.rs create mode 100644 src/_cookbook/find.rs diff --git a/Cargo.toml b/Cargo.toml index 404f85f2..214c230e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,10 @@ required-features = ["cargo"] name = "escaped-positional-derive" required-features = ["derive"] +[[example]] +name = "find" +required-features = ["cargo"] + [[example]] name = "git-derive" required-features = ["derive"] diff --git a/examples/find.md b/examples/find.md new file mode 100644 index 00000000..decfa2ab --- /dev/null +++ b/examples/find.md @@ -0,0 +1,47 @@ +`find` is an example of position-sensitive flags + +```console +$ find --help +clap 4.0.0-alpha.0 +A simple to use, efficient, and full-featured Command Line Argument Parser + +USAGE: + find[EXE] [OPTIONS] --name + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +TESTS: + --empty File is empty and is either a regular file or a directory + --name Base of file name (the path with the leading directories removed) matches + shell pattern pattern + +OPERATORS: + -o, --or expr2 is not evaluate if exp1 is true + -a, --and Same as `expr1 expr1` + +$ find --empty -o --name .keep +[ + ( + "empty", + Bool( + true, + ), + ), + ( + "or", + Bool( + true, + ), + ), + ( + "name", + String( + ".keep", + ), + ), +] + +``` + diff --git a/examples/find.rs b/examples/find.rs new file mode 100644 index 00000000..c16ff991 --- /dev/null +++ b/examples/find.rs @@ -0,0 +1,99 @@ +use std::collections::BTreeMap; + +use clap::{arg, command, ArgGroup, ArgMatches, Command}; + +fn main() { + let matches = cli().get_matches(); + let values = Value::from_matches(&matches); + println!("{:#?}", values); +} + +fn cli() -> Command<'static> { + command!() + .group(ArgGroup::new("tests").multiple(true)) + .next_help_heading("TESTS") + .args([ + arg!(--empty "File is empty and is either a regular file or a directory").group("tests"), + arg!(--name "Base of file name (the path with the leading directories removed) matches shell pattern pattern").group("tests"), + ]) + .group(ArgGroup::new("operators").multiple(true)) + .next_help_heading("OPERATORS") + .args([ + arg!(-o - -or "expr2 is not evaluate if exp1 is true").group("operators"), + arg!(-a - -and "Same as `expr1 expr1`").group("operators"), + ]) +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum Value { + Bool(bool), + String(String), +} + +impl Value { + pub fn from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)> { + let mut values = BTreeMap::new(); + for id in matches.ids() { + if matches.try_get_many::(id.as_str()).is_ok() { + // ignore groups + continue; + } + let value_source = matches + .value_source(id.as_str()) + .expect("id came from matches"); + if value_source != clap::parser::ValueSource::CommandLine { + // Any other source just gets tacked on at the end (like default values) + continue; + } + if Self::extract::(matches, id, &mut values) { + continue; + } + if Self::extract::(matches, id, &mut values) { + continue; + } + unimplemented!("unknown type for {}: {:?}", id, matches); + } + values.into_values().collect::>() + } + + fn extract + Send + Sync + 'static>( + matches: &ArgMatches, + id: &clap::Id, + output: &mut BTreeMap, + ) -> bool { + match matches.try_get_many::(id.as_str()) { + Ok(Some(values)) => { + for (value, index) in values.zip( + matches + .indices_of(id.as_str()) + .expect("id came from matches"), + ) { + output.insert(index, (id.clone(), value.clone().into())); + } + true + } + Ok(None) => { + unreachable!("`ids` only reports what is present") + } + Err(clap::parser::MatchesError::UnknownArgument { .. }) => { + unreachable!("id came from matches") + } + Err(clap::parser::MatchesError::Downcast { .. }) => false, + Err(_) => { + unreachable!("id came from matches") + } + } + } +} + +impl From for Value { + fn from(other: String) -> Self { + Self::String(other) + } +} + +impl From for Value { + fn from(other: bool) -> Self { + Self::Bool(other) + } +} diff --git a/src/_cookbook/find.rs b/src/_cookbook/find.rs new file mode 100644 index 00000000..2ca11c66 --- /dev/null +++ b/src/_cookbook/find.rs @@ -0,0 +1,7 @@ +//! # Example: find-like CLI (Builder API) +//! +//! ```rust +#![doc = include_str!("../../examples/find.rs")] +//! ``` +//! +#![doc = include_str!("../../examples/find.md")] diff --git a/src/_cookbook/mod.rs b/src/_cookbook/mod.rs index 1a78ed11..8c45d2cf 100644 --- a/src/_cookbook/mod.rs +++ b/src/_cookbook/mod.rs @@ -16,6 +16,10 @@ //! - Subcommands //! - Cargo plugins //! +//! find-like interface: [builder][find] +//! - Topics: +//! - Position-sensitive flags +//! //! git-like interface: [builder][git], [derive][git_derive] //! - Topics: //! - Subcommands @@ -46,6 +50,7 @@ pub mod cargo_example; pub mod cargo_example_derive; pub mod escaped_positional; pub mod escaped_positional_derive; +pub mod find; pub mod git; pub mod git_derive; pub mod multicall_busybox;