Skip to content

Commit 2122e2b

Browse files
committed
feat(parser): Add TypedValueParser::try_map
This is a major building block to avoid needing to implement `TypedValueParser` Inspired by #4362
1 parent 502bb93 commit 2122e2b

File tree

1 file changed

+108
-2
lines changed

1 file changed

+108
-2
lines changed

src/builder/value_parser.rs

+108-2
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,9 @@ where
602602
///
603603
/// As alternatives to implementing `TypedValueParser`,
604604
/// - Use `Fn(&str) -> Result<T, E>` which implements `TypedValueParser`
605-
/// - [`TypedValueParser::map`] to adapt an existing `TypedValueParser`
605+
/// - [`TypedValueParser::map`] or [`TypedValueParser::try_map`] to adapt an existing `TypedValueParser`
606606
///
607-
/// See `ValueParserFactory` for register `TypedValueParser::Value` with
607+
/// See `ValueParserFactory` to register `TypedValueParser::Value` with
608608
/// [`value_parser!`][crate::value_parser].
609609
///
610610
/// # Example
@@ -726,6 +726,56 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static {
726726
{
727727
MapValueParser::new(self, func)
728728
}
729+
730+
/// Adapt a `TypedValueParser` from one value to another
731+
///
732+
/// # Example
733+
///
734+
/// ```rust
735+
/// # use std::ffi::OsString;
736+
/// # use std::ffi::OsStr;
737+
/// # use std::path::PathBuf;
738+
/// # use std::path::Path;
739+
/// # use clap::Command;
740+
/// # use clap::Arg;
741+
/// # use clap::builder::TypedValueParser as _;
742+
/// # use clap::builder::OsStringValueParser;
743+
/// let cmd = Command::new("mycmd")
744+
/// .arg(
745+
/// Arg::new("flag")
746+
/// .long("flag")
747+
/// .value_parser(
748+
/// OsStringValueParser::new()
749+
/// .try_map(verify_ext)
750+
/// )
751+
/// );
752+
///
753+
/// fn verify_ext(os: OsString) -> Result<PathBuf, &'static str> {
754+
/// let path = PathBuf::from(os);
755+
/// if path.extension() != Some(OsStr::new("rs")) {
756+
/// return Err("only Rust files are supported");
757+
/// }
758+
/// Ok(path)
759+
/// }
760+
///
761+
/// let error = cmd.clone().try_get_matches_from(["mycmd", "--flag", "foo.txt"]).unwrap_err();
762+
/// error.print();
763+
///
764+
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "foo.rs"]).unwrap();
765+
/// assert!(matches.contains_id("flag"));
766+
/// assert_eq!(
767+
/// matches.get_one::<PathBuf>("flag").map(|s| s.as_path()),
768+
/// Some(Path::new("foo.rs"))
769+
/// );
770+
/// ```
771+
fn try_map<T, E, F>(self, func: F) -> TryMapValueParser<Self, F>
772+
where
773+
F: Fn(Self::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
774+
T: Send + Sync + Clone,
775+
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
776+
{
777+
TryMapValueParser::new(self, func)
778+
}
729779
}
730780

731781
impl<F, T, E> TypedValueParser for F
@@ -1931,6 +1981,62 @@ where
19311981
}
19321982
}
19331983

1984+
/// Adapt a `TypedValueParser` from one value to another
1985+
///
1986+
/// See [`TypedValueParser::try_map`]
1987+
#[derive(Clone, Debug)]
1988+
pub struct TryMapValueParser<P, F> {
1989+
parser: P,
1990+
func: F,
1991+
}
1992+
1993+
impl<P, F, T, E> TryMapValueParser<P, F>
1994+
where
1995+
P: TypedValueParser,
1996+
P::Value: Send + Sync + Clone,
1997+
F: Fn(P::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
1998+
T: Send + Sync + Clone,
1999+
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
2000+
{
2001+
fn new(parser: P, func: F) -> Self {
2002+
Self { parser, func }
2003+
}
2004+
}
2005+
2006+
impl<P, F, T, E> TypedValueParser for TryMapValueParser<P, F>
2007+
where
2008+
P: TypedValueParser,
2009+
P::Value: Send + Sync + Clone,
2010+
F: Fn(P::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
2011+
T: Send + Sync + Clone,
2012+
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
2013+
{
2014+
type Value = T;
2015+
2016+
fn parse_ref(
2017+
&self,
2018+
cmd: &crate::Command,
2019+
arg: Option<&crate::Arg>,
2020+
value: &std::ffi::OsStr,
2021+
) -> Result<Self::Value, crate::Error> {
2022+
let mid_value = ok!(self.parser.parse_ref(cmd, arg, value));
2023+
let value = ok!((self.func)(mid_value).map_err(|e| {
2024+
let arg = arg
2025+
.map(|a| a.to_string())
2026+
.unwrap_or_else(|| "...".to_owned());
2027+
crate::Error::value_validation(arg, value.to_string_lossy().into_owned(), e.into())
2028+
.with_cmd(cmd)
2029+
}));
2030+
Ok(value)
2031+
}
2032+
2033+
fn possible_values(
2034+
&self,
2035+
) -> Option<Box<dyn Iterator<Item = crate::builder::PossibleValue> + '_>> {
2036+
self.parser.possible_values()
2037+
}
2038+
}
2039+
19342040
/// Register a type with [value_parser!][crate::value_parser!]
19352041
///
19362042
/// # Example

0 commit comments

Comments
 (0)