Skip to content

Commit

Permalink
Merge pull request clap-rs#4095 from epage/flag
Browse files Browse the repository at this point in the history
feat(parser): Make customizeing flags easier
  • Loading branch information
epage committed Aug 19, 2022
2 parents cf7d786 + 9074b60 commit e81b1aa
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -58,6 +58,7 @@ MSRV is now 1.60.0
### Features

- `Arg::num_args` now accepts ranges, allowing setting both the minimum and maximum number of values per occurrence
- Added `TypedValueParser::map` to make it easier to reuse existing value parsers
- *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312)

### Fixes
Expand Down
35 changes: 35 additions & 0 deletions src/builder/action.rs
Expand Up @@ -101,6 +101,41 @@ pub enum ArgAction {
/// Some(false)
/// );
/// ```
///
/// You can use [`TypedValueParser::map`][crate::builder::TypedValueParser::map] to have the
/// flag control an application-specific type:
/// ```rust
/// # use clap::Command;
/// # use clap::Arg;
/// # use clap::builder::TypedValueParser as _;
/// # use clap::builder::BoolishValueParser;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::SetTrue)
/// .value_parser(
/// BoolishValueParser::new()
/// .map(|b| -> usize {
/// if b { 10 } else { 5 }
/// })
/// )
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(10)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(5)
/// );
/// ```
SetTrue,
/// When encountered, act as if `"false"` was encountered on the command-line
///
Expand Down
1 change: 1 addition & 0 deletions src/builder/mod.rs
Expand Up @@ -32,6 +32,7 @@ pub use value_parser::BoolValueParser;
pub use value_parser::BoolishValueParser;
pub use value_parser::EnumValueParser;
pub use value_parser::FalseyValueParser;
pub use value_parser::MapValueParser;
pub use value_parser::NonEmptyStringValueParser;
pub use value_parser::OsStringValueParser;
pub use value_parser::PathBufValueParser;
Expand Down
108 changes: 105 additions & 3 deletions src/builder/value_parser.rs
Expand Up @@ -108,7 +108,6 @@ impl ValueParser {
pub fn new<P>(other: P) -> Self
where
P: TypedValueParser,
P::Value: Send + Sync + Clone,
{
Self(ValueParserInner::Other(Box::new(other)))
}
Expand Down Expand Up @@ -284,7 +283,6 @@ impl ValueParser {
impl<P> From<P> for ValueParser
where
P: TypedValueParser + Send + Sync + 'static,
P::Value: Send + Sync + Clone,
{
fn from(p: P) -> Self {
Self::new(p)
Expand Down Expand Up @@ -604,7 +602,7 @@ where
/// Parse/validate argument values
pub trait TypedValueParser: Clone + Send + Sync + 'static {
/// Argument's value type
type Value;
type Value: Send + Sync + Clone;

/// Parse the argument value
///
Expand Down Expand Up @@ -637,12 +635,57 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static {
) -> Option<Box<dyn Iterator<Item = crate::builder::PossibleValue<'static>> + '_>> {
None
}

/// Adapt a `TypedValueParser` from one value to another
///
/// # Example
///
/// ```rust
/// # use clap::Command;
/// # use clap::Arg;
/// # use clap::builder::TypedValueParser as _;
/// # use clap::builder::BoolishValueParser;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::SetTrue)
/// .value_parser(
/// BoolishValueParser::new()
/// .map(|b| -> usize {
/// if b { 10 } else { 5 }
/// })
/// )
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(10)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(5)
/// );
/// ```
fn map<T, F>(self, func: F) -> MapValueParser<Self, F>
where
T: Send + Sync + Clone,
F: Fn(Self::Value) -> T + Clone,
{
MapValueParser::new(self, func)
}
}

impl<F, T, E> TypedValueParser for F
where
F: Fn(&str) -> Result<T, E> + Clone + Send + Sync + 'static,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
T: Send + Sync + Clone,
{
type Value = T;

Expand Down Expand Up @@ -1777,6 +1820,65 @@ impl Default for NonEmptyStringValueParser {
}
}

/// Adapt a `TypedValueParser` from one value to another
///
/// See [`TypedValueParser::map`]
#[derive(Clone, Debug)]
pub struct MapValueParser<P, F> {
parser: P,
func: F,
}

impl<P, F, T> MapValueParser<P, F>
where
P: TypedValueParser,
P::Value: Send + Sync + Clone,
F: Fn(P::Value) -> T + Clone,
T: Send + Sync + Clone,
{
fn new(parser: P, func: F) -> Self {
Self { parser, func }
}
}

impl<P, F, T> TypedValueParser for MapValueParser<P, F>
where
P: TypedValueParser,
P::Value: Send + Sync + Clone,
F: Fn(P::Value) -> T + Clone + Send + Sync + 'static,
T: Send + Sync + Clone,
{
type Value = T;

fn parse_ref(
&self,
cmd: &crate::Command,
arg: Option<&crate::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, crate::Error> {
let value = self.parser.parse_ref(cmd, arg, value)?;
let value = (self.func)(value);
Ok(value)
}

fn parse(
&self,
cmd: &crate::Command,
arg: Option<&crate::Arg>,
value: std::ffi::OsString,
) -> Result<Self::Value, crate::Error> {
let value = self.parser.parse(cmd, arg, value)?;
let value = (self.func)(value);
Ok(value)
}

fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = crate::builder::PossibleValue<'static>> + '_>> {
self.parser.possible_values()
}
}

/// Register a type with [value_parser!][crate::value_parser!]
///
/// # Example
Expand Down
16 changes: 10 additions & 6 deletions tests/derive/flags.rs
Expand Up @@ -12,6 +12,8 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.

use clap::builder::BoolishValueParser;
use clap::builder::TypedValueParser as _;
use clap::ArgAction;
use clap::CommandFactory;
use clap::Parser;
Expand Down Expand Up @@ -46,17 +48,19 @@ fn bool_type_is_flag() {

#[test]
fn non_bool_type_flag() {
fn parse_from_flag(b: &str) -> Result<usize, String> {
b.parse::<bool>()
.map(|b| if b { 10 } else { 5 })
.map_err(|e| e.to_string())
fn parse_from_flag(b: bool) -> usize {
if b {
10
} else {
5
}
}

#[derive(Parser, Debug)]
struct Opt {
#[clap(short, long, action = ArgAction::SetTrue, value_parser = parse_from_flag)]
#[clap(short, long, action = ArgAction::SetTrue, value_parser = BoolishValueParser::new().map(parse_from_flag))]
alice: usize,
#[clap(short, long, action = ArgAction::SetTrue, value_parser = parse_from_flag)]
#[clap(short, long, action = ArgAction::SetTrue, value_parser = BoolishValueParser::new().map(parse_from_flag))]
bob: usize,
}

Expand Down

0 comments on commit e81b1aa

Please sign in to comment.