mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat(parser): Add TypedValueParser::try_map
This is a major building block to avoid needing to implement `TypedValueParser` Inspired by #4362
This commit is contained in:
parent
502bb93e5b
commit
2122e2b2dd
1 changed files with 108 additions and 2 deletions
|
@ -602,9 +602,9 @@ where
|
||||||
///
|
///
|
||||||
/// As alternatives to implementing `TypedValueParser`,
|
/// As alternatives to implementing `TypedValueParser`,
|
||||||
/// - Use `Fn(&str) -> Result<T, E>` which implements `TypedValueParser`
|
/// - Use `Fn(&str) -> Result<T, E>` which implements `TypedValueParser`
|
||||||
/// - [`TypedValueParser::map`] to adapt an existing `TypedValueParser`
|
/// - [`TypedValueParser::map`] or [`TypedValueParser::try_map`] to adapt an existing `TypedValueParser`
|
||||||
///
|
///
|
||||||
/// See `ValueParserFactory` for register `TypedValueParser::Value` with
|
/// See `ValueParserFactory` to register `TypedValueParser::Value` with
|
||||||
/// [`value_parser!`][crate::value_parser].
|
/// [`value_parser!`][crate::value_parser].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -726,6 +726,56 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static {
|
||||||
{
|
{
|
||||||
MapValueParser::new(self, func)
|
MapValueParser::new(self, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adapt a `TypedValueParser` from one value to another
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::ffi::OsString;
|
||||||
|
/// # use std::ffi::OsStr;
|
||||||
|
/// # use std::path::PathBuf;
|
||||||
|
/// # use std::path::Path;
|
||||||
|
/// # use clap::Command;
|
||||||
|
/// # use clap::Arg;
|
||||||
|
/// # use clap::builder::TypedValueParser as _;
|
||||||
|
/// # use clap::builder::OsStringValueParser;
|
||||||
|
/// let cmd = Command::new("mycmd")
|
||||||
|
/// .arg(
|
||||||
|
/// Arg::new("flag")
|
||||||
|
/// .long("flag")
|
||||||
|
/// .value_parser(
|
||||||
|
/// OsStringValueParser::new()
|
||||||
|
/// .try_map(verify_ext)
|
||||||
|
/// )
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// fn verify_ext(os: OsString) -> Result<PathBuf, &'static str> {
|
||||||
|
/// let path = PathBuf::from(os);
|
||||||
|
/// if path.extension() != Some(OsStr::new("rs")) {
|
||||||
|
/// return Err("only Rust files are supported");
|
||||||
|
/// }
|
||||||
|
/// Ok(path)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let error = cmd.clone().try_get_matches_from(["mycmd", "--flag", "foo.txt"]).unwrap_err();
|
||||||
|
/// error.print();
|
||||||
|
///
|
||||||
|
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "foo.rs"]).unwrap();
|
||||||
|
/// assert!(matches.contains_id("flag"));
|
||||||
|
/// assert_eq!(
|
||||||
|
/// matches.get_one::<PathBuf>("flag").map(|s| s.as_path()),
|
||||||
|
/// Some(Path::new("foo.rs"))
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
fn try_map<T, E, F>(self, func: F) -> TryMapValueParser<Self, F>
|
||||||
|
where
|
||||||
|
F: Fn(Self::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
|
||||||
|
T: Send + Sync + Clone,
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||||
|
{
|
||||||
|
TryMapValueParser::new(self, func)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T, E> TypedValueParser for F
|
impl<F, T, E> TypedValueParser for F
|
||||||
|
@ -1931,6 +1981,62 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adapt a `TypedValueParser` from one value to another
|
||||||
|
///
|
||||||
|
/// See [`TypedValueParser::try_map`]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TryMapValueParser<P, F> {
|
||||||
|
parser: P,
|
||||||
|
func: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P, F, T, E> TryMapValueParser<P, F>
|
||||||
|
where
|
||||||
|
P: TypedValueParser,
|
||||||
|
P::Value: Send + Sync + Clone,
|
||||||
|
F: Fn(P::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
|
||||||
|
T: Send + Sync + Clone,
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||||
|
{
|
||||||
|
fn new(parser: P, func: F) -> Self {
|
||||||
|
Self { parser, func }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P, F, T, E> TypedValueParser for TryMapValueParser<P, F>
|
||||||
|
where
|
||||||
|
P: TypedValueParser,
|
||||||
|
P::Value: Send + Sync + Clone,
|
||||||
|
F: Fn(P::Value) -> Result<T, E> + Clone + Send + Sync + 'static,
|
||||||
|
T: Send + Sync + Clone,
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||||
|
{
|
||||||
|
type Value = T;
|
||||||
|
|
||||||
|
fn parse_ref(
|
||||||
|
&self,
|
||||||
|
cmd: &crate::Command,
|
||||||
|
arg: Option<&crate::Arg>,
|
||||||
|
value: &std::ffi::OsStr,
|
||||||
|
) -> Result<Self::Value, crate::Error> {
|
||||||
|
let mid_value = ok!(self.parser.parse_ref(cmd, arg, value));
|
||||||
|
let value = ok!((self.func)(mid_value).map_err(|e| {
|
||||||
|
let arg = arg
|
||||||
|
.map(|a| a.to_string())
|
||||||
|
.unwrap_or_else(|| "...".to_owned());
|
||||||
|
crate::Error::value_validation(arg, value.to_string_lossy().into_owned(), e.into())
|
||||||
|
.with_cmd(cmd)
|
||||||
|
}));
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_values(
|
||||||
|
&self,
|
||||||
|
) -> Option<Box<dyn Iterator<Item = crate::builder::PossibleValue> + '_>> {
|
||||||
|
self.parser.possible_values()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a type with [value_parser!][crate::value_parser!]
|
/// Register a type with [value_parser!][crate::value_parser!]
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|
Loading…
Reference in a new issue