diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a0ea32195..cd0d147807e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Features + +- `TypedValueParser::map` to allow reusing existing value parsers for other purposes + ## [3.2.20] - 2022-09-02 ### Features diff --git a/src/builder/value_parser.rs b/src/builder/value_parser.rs index 0492f278230..92cbbadf8d8 100644 --- a/src/builder/value_parser.rs +++ b/src/builder/value_parser.rs @@ -637,6 +637,50 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static { ) -> Option> + '_>> { 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::("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::("flag").copied(), + /// Some(5) + /// ); + /// ``` + fn map(self, func: F) -> MapValueParser + where + T: Send + Sync + Clone, + F: Fn(Self::Value) -> T + Clone, + { + MapValueParser::new(self, func) + } } impl TypedValueParser for F @@ -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 { + parser: P, + func: F, +} + +impl MapValueParser { + fn new(parser: P, func: F) -> Self { + Self { parser, func } + } +} + +impl TypedValueParser for MapValueParser +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 { + 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 { + let value = self.parser.parse(cmd, arg, value)?; + let value = (self.func)(value); + Ok(value) + } + + fn possible_values( + &self, + ) -> Option> + '_>> { + self.parser.possible_values() + } +} + /// Register a type with [value_parser!][crate::value_parser!] /// /// # Example