Skip to content

Commit 960c152

Browse files
committed
feat(parser): TypedValueParseer::map for adapting value parsers
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.
1 parent c361d01 commit 960c152

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77
<!-- next-header -->
88
## [Unreleased] - ReleaseDate
99

10+
### Features
11+
12+
- `TypedValueParser::map` to allow reusing existing value parsers for other purposes
13+
1014
## [3.2.20] - 2022-09-02
1115

1216
### Features

src/builder/value_parser.rs

+97
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,50 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static {
637637
) -> Option<Box<dyn Iterator<Item = crate::PossibleValue<'static>> + '_>> {
638638
None
639639
}
640+
641+
/// Adapt a `TypedValueParser` from one value to another
642+
///
643+
/// # Example
644+
///
645+
/// ```rust
646+
/// # use clap::Command;
647+
/// # use clap::Arg;
648+
/// # use clap::builder::TypedValueParser as _;
649+
/// # use clap::builder::BoolishValueParser;
650+
/// let cmd = Command::new("mycmd")
651+
/// .arg(
652+
/// Arg::new("flag")
653+
/// .long("flag")
654+
/// .action(clap::ArgAction::Set)
655+
/// .value_parser(
656+
/// BoolishValueParser::new()
657+
/// .map(|b| -> usize {
658+
/// if b { 10 } else { 5 }
659+
/// })
660+
/// )
661+
/// );
662+
///
663+
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag=true", "--flag=true"]).unwrap();
664+
/// assert!(matches.contains_id("flag"));
665+
/// assert_eq!(
666+
/// matches.get_one::<usize>("flag").copied(),
667+
/// Some(10)
668+
/// );
669+
///
670+
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag=false"]).unwrap();
671+
/// assert!(matches.contains_id("flag"));
672+
/// assert_eq!(
673+
/// matches.get_one::<usize>("flag").copied(),
674+
/// Some(5)
675+
/// );
676+
/// ```
677+
fn map<T, F>(self, func: F) -> MapValueParser<Self, F>
678+
where
679+
T: Send + Sync + Clone,
680+
F: Fn(Self::Value) -> T + Clone,
681+
{
682+
MapValueParser::new(self, func)
683+
}
640684
}
641685

642686
impl<F, T, E> TypedValueParser for F
@@ -1777,6 +1821,59 @@ impl Default for NonEmptyStringValueParser {
17771821
}
17781822
}
17791823

1824+
/// Adapt a `TypedValueParser` from one value to another
1825+
///
1826+
/// See [`TypedValueParser::map`]
1827+
#[derive(Clone, Debug)]
1828+
pub struct MapValueParser<P, F> {
1829+
parser: P,
1830+
func: F,
1831+
}
1832+
1833+
impl<P, F> MapValueParser<P, F> {
1834+
fn new(parser: P, func: F) -> Self {
1835+
Self { parser, func }
1836+
}
1837+
}
1838+
1839+
impl<P, F, T> TypedValueParser for MapValueParser<P, F>
1840+
where
1841+
P: TypedValueParser,
1842+
P::Value: Send + Sync + Clone,
1843+
F: Fn(P::Value) -> T + Clone + Send + Sync + 'static,
1844+
T: Send + Sync + Clone,
1845+
{
1846+
type Value = T;
1847+
1848+
fn parse_ref(
1849+
&self,
1850+
cmd: &crate::Command,
1851+
arg: Option<&crate::Arg>,
1852+
value: &std::ffi::OsStr,
1853+
) -> Result<Self::Value, crate::Error> {
1854+
let value = self.parser.parse_ref(cmd, arg, value)?;
1855+
let value = (self.func)(value);
1856+
Ok(value)
1857+
}
1858+
1859+
fn parse(
1860+
&self,
1861+
cmd: &crate::Command,
1862+
arg: Option<&crate::Arg>,
1863+
value: std::ffi::OsString,
1864+
) -> Result<Self::Value, crate::Error> {
1865+
let value = self.parser.parse(cmd, arg, value)?;
1866+
let value = (self.func)(value);
1867+
Ok(value)
1868+
}
1869+
1870+
fn possible_values(
1871+
&self,
1872+
) -> Option<Box<dyn Iterator<Item = crate::builder::PossibleValue<'static>> + '_>> {
1873+
self.parser.possible_values()
1874+
}
1875+
}
1876+
17801877
/// Register a type with [value_parser!][crate::value_parser!]
17811878
///
17821879
/// # Example

0 commit comments

Comments
 (0)