Skip to content

Commit

Permalink
feat(parser): TypedValueParseer::map for adapting value parsers
Browse files Browse the repository at this point in the history
This is a partial backport of #4095.  This was written to allow mapping
bools to other types but this will be useful for people using
`PossibleValue` with non-string types to upgrade to value parsers.
  • Loading branch information
epage committed Sep 12, 2022
1 parent c361d01 commit 960c152
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
<!-- next-header -->
## [Unreleased] - ReleaseDate

### Features

- `TypedValueParser::map` to allow reusing existing value parsers for other purposes

## [3.2.20] - 2022-09-02

### Features
Expand Down
97 changes: 97 additions & 0 deletions src/builder/value_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,50 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static {
) -> Option<Box<dyn Iterator<Item = crate::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::Set)
/// .value_parser(
/// BoolishValueParser::new()
/// .map(|b| -> usize {
/// if b { 10 } else { 5 }
/// })
/// )
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag=true", "--flag=true"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(10)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag=false"]).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
Expand Down Expand Up @@ -1777,6 +1821,59 @@ 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> MapValueParser<P, F> {
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

0 comments on commit 960c152

Please sign in to comment.