From 3312cc571c4f1abe857b79b87c0cf100b2287e87 Mon Sep 17 00:00:00 2001 From: Jort Date: Thu, 16 Jan 2025 16:26:35 -0800 Subject: [PATCH] feat: Add arg index to error context --- clap_builder/src/builder/value_parser.rs | 211 +++++++++++++++-------- clap_builder/src/error/context.rs | 3 + clap_builder/src/error/mod.rs | 71 ++++++-- clap_builder/src/parser/parser.rs | 122 +++++++++---- clap_builder/src/parser/validator.rs | 28 ++- clap_lex/src/lib.rs | 3 +- tests/builder/possible_values.rs | 1 + 7 files changed, 309 insertions(+), 130 deletions(-) diff --git a/clap_builder/src/builder/value_parser.rs b/clap_builder/src/builder/value_parser.rs index 87757ed96d9..071e6b215f4 100644 --- a/clap_builder/src/builder/value_parser.rs +++ b/clap_builder/src/builder/value_parser.rs @@ -238,8 +238,10 @@ impl ValueParser { arg: Option<&crate::Arg>, value: &std::ffi::OsStr, source: ValueSource, + index: isize, ) -> Result { - self.any_value_parser().parse_ref_(cmd, arg, value, source) + self.any_value_parser() + .parse_ref_(cmd, arg, value, source, index) } /// Describes the content of `AnyValue` @@ -594,6 +596,7 @@ trait AnyValueParser: Send + Sync + 'static { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result; fn parse_ref_( @@ -602,8 +605,9 @@ trait AnyValueParser: Send + Sync + 'static { arg: Option<&crate::Arg>, value: &std::ffi::OsStr, _source: ValueSource, + index: isize, ) -> Result { - self.parse_ref(cmd, arg, value) + self.parse_ref(cmd, arg, value, index) } /// Describes the content of `AnyValue` @@ -626,8 +630,9 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - let value = ok!(TypedValueParser::parse_ref(self, cmd, arg, value)); + let value = ok!(TypedValueParser::parse_ref(self, cmd, arg, value, index)); Ok(AnyValue::new(value)) } @@ -637,8 +642,11 @@ where arg: Option<&crate::Arg>, value: &std::ffi::OsStr, source: ValueSource, + index: isize, ) -> Result { - let value = ok!(TypedValueParser::parse_ref_(self, cmd, arg, value, source)); + let value = ok!(TypedValueParser::parse_ref_( + self, cmd, arg, value, source, index + )); Ok(AnyValue::new(value)) } @@ -688,9 +696,10 @@ where /// cmd: &clap::Command, /// arg: Option<&clap::Arg>, /// value: &std::ffi::OsStr, +/// index: isize, /// ) -> Result { /// let inner = clap::value_parser!(u32); -/// let val = inner.parse_ref(cmd, arg, value)?; +/// let val = inner.parse_ref(cmd, arg, value, index)?; /// /// const INVALID_VALUE: u32 = 10; /// if val == INVALID_VALUE { @@ -720,6 +729,7 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result; /// Parse the argument value @@ -731,8 +741,9 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static { arg: Option<&crate::Arg>, value: &std::ffi::OsStr, _source: ValueSource, + index: isize, ) -> Result { - self.parse_ref(cmd, arg, value) + self.parse_ref(cmd, arg, value, index) } /// Parse the argument value @@ -743,8 +754,9 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static { cmd: &crate::Command, arg: Option<&crate::Arg>, value: std::ffi::OsString, + index: isize, ) -> Result { - self.parse_ref(cmd, arg, &value) + self.parse_ref(cmd, arg, &value, index) } /// Parse the argument value @@ -756,8 +768,9 @@ pub trait TypedValueParser: Clone + Send + Sync + 'static { arg: Option<&crate::Arg>, value: std::ffi::OsString, _source: ValueSource, + index: isize, ) -> Result { - self.parse(cmd, arg, value) + self.parse(cmd, arg, value, index) } /// Reflect on enumerated value properties @@ -880,18 +893,20 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = ok!(value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); let value = ok!((self)(value).map_err(|e| { let arg = arg .map(|a| a.to_string()) .unwrap_or_else(|| "...".to_owned()); - crate::Error::value_validation(arg, value.to_owned(), e.into()).with_cmd(cmd) + crate::Error::value_validation(arg, value.to_owned(), e.into(), index).with_cmd(cmd) })); Ok(value) } @@ -919,8 +934,9 @@ impl TypedValueParser for StringValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - TypedValueParser::parse(self, cmd, arg, value.to_owned()) + TypedValueParser::parse(self, cmd, arg, value.to_owned(), index) } fn parse( @@ -928,11 +944,13 @@ impl TypedValueParser for StringValueParser { cmd: &crate::Command, _arg: Option<&crate::Arg>, value: std::ffi::OsString, + index: isize, ) -> Result { let value = ok!(value.into_string().map_err(|_| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); Ok(value) @@ -967,8 +985,9 @@ impl TypedValueParser for OsStringValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - TypedValueParser::parse(self, cmd, arg, value.to_owned()) + TypedValueParser::parse(self, cmd, arg, value.to_owned(), index) } fn parse( @@ -976,6 +995,7 @@ impl TypedValueParser for OsStringValueParser { _cmd: &crate::Command, _arg: Option<&crate::Arg>, value: std::ffi::OsString, + _index: isize, ) -> Result { Ok(value) } @@ -1009,8 +1029,9 @@ impl TypedValueParser for PathBufValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - TypedValueParser::parse(self, cmd, arg, value.to_owned()) + TypedValueParser::parse(self, cmd, arg, value.to_owned(), index) } fn parse( @@ -1018,6 +1039,7 @@ impl TypedValueParser for PathBufValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: std::ffi::OsString, + index: isize, ) -> Result { if value.is_empty() { return Err(crate::Error::empty_value( @@ -1025,6 +1047,7 @@ impl TypedValueParser for PathBufValueParser { &[], arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, )); } Ok(Self::Value::from(value)) @@ -1069,11 +1092,11 @@ impl Default for PathBufValueParser { /// let value_parser = clap::builder::EnumValueParser::::new(); /// // or /// let value_parser = clap::value_parser!(ColorChoice); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("always")).unwrap(), ColorChoice::Always); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("auto")).unwrap(), ColorChoice::Auto); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("never")).unwrap(), ColorChoice::Never); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("always"), 0).unwrap(), ColorChoice::Always); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("auto"), 0).unwrap(), ColorChoice::Auto); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("never"), 0).unwrap(), ColorChoice::Never); /// ``` #[derive(Clone, Debug)] pub struct EnumValueParser( @@ -1096,6 +1119,7 @@ impl TypedValueParser for E cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { let ignore_case = arg.map(|a| a.is_ignore_case_set()).unwrap_or(false); let possible_vals = || { @@ -1114,6 +1138,7 @@ impl TypedValueParser for E &possible_vals(), arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, ) })); let value = ok!(E::value_variants() @@ -1130,6 +1155,7 @@ impl TypedValueParser for E &possible_vals(), arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, ) })) .clone(); @@ -1186,11 +1212,11 @@ impl Default for EnumValueP /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::PossibleValuesParser::new(["always", "auto", "never"]); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("always")).unwrap(), "always"); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("auto")).unwrap(), "auto"); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("never")).unwrap(), "never"); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("always"), 0).unwrap(), "always"); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("auto"), 0).unwrap(), "auto"); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("never"), 0).unwrap(), "never"); /// ``` #[derive(Clone, Debug)] pub struct PossibleValuesParser(Vec); @@ -1210,8 +1236,9 @@ impl TypedValueParser for PossibleValuesParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - TypedValueParser::parse(self, cmd, arg, value.to_owned()) + TypedValueParser::parse(self, cmd, arg, value.to_owned(), index) } fn parse( @@ -1219,11 +1246,13 @@ impl TypedValueParser for PossibleValuesParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: std::ffi::OsString, + index: isize, ) -> Result { let value = ok!(value.into_string().map_err(|_| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); @@ -1244,6 +1273,7 @@ impl TypedValueParser for PossibleValuesParser { &possible_vals, arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, )) } } @@ -1303,13 +1333,13 @@ where /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::RangedI64ValueParser::::new().range(-1..200); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-200")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("300")).is_err()); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("-1")).unwrap(), -1); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0")).unwrap(), 0); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("50")).unwrap(), 50); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-200"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("300"), 0).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("-1"), 0).unwrap(), -1); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0"), 0).unwrap(), 0); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("50"), 0).unwrap(), 50); /// ``` #[derive(Copy, Clone, Debug)] pub struct RangedI64ValueParser + Clone + Send + Sync = i64> { @@ -1408,11 +1438,13 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, raw_value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = ok!(raw_value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); let value = ok!(value.parse::().map_err(|err| { @@ -1423,6 +1455,7 @@ where arg, raw_value.to_string_lossy().into_owned(), err.into(), + index, ) .with_cmd(cmd) })); @@ -1434,6 +1467,7 @@ where arg, raw_value.to_string_lossy().into_owned(), format!("{} is not in {}", value, self.format_bounds()).into(), + index, ) .with_cmd(cmd)); } @@ -1447,6 +1481,7 @@ where arg, raw_value.to_string_lossy().into_owned(), err.into(), + index, ) .with_cmd(cmd) })); @@ -1502,13 +1537,13 @@ impl + Clone + Send + Sync> Default for RangedI64ValueParser /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::RangedU64ValueParser::::new().range(0..200); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-200")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("300")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-1")).is_err()); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0")).unwrap(), 0); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("50")).unwrap(), 50); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-200"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("300"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("-1"), 0).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0"), 0).unwrap(), 0); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("50"), 0).unwrap(), 50); /// ``` #[derive(Copy, Clone, Debug)] pub struct RangedU64ValueParser = u64> { @@ -1607,11 +1642,13 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, raw_value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = ok!(raw_value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); let value = ok!(value.parse::().map_err(|err| { @@ -1622,6 +1659,7 @@ where arg, raw_value.to_string_lossy().into_owned(), err.into(), + index, ) .with_cmd(cmd) })); @@ -1633,6 +1671,7 @@ where arg, raw_value.to_string_lossy().into_owned(), format!("{} is not in {}", value, self.format_bounds()).into(), + index, ) .with_cmd(cmd)); } @@ -1646,6 +1685,7 @@ where arg, raw_value.to_string_lossy().into_owned(), err.into(), + index, ) .with_cmd(cmd) })); @@ -1698,6 +1738,7 @@ impl TypedValueParser for BoolValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = if value == std::ffi::OsStr::new("true") { true @@ -1715,6 +1756,7 @@ impl TypedValueParser for BoolValueParser { &possible_vals, arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, )); }; Ok(value) @@ -1765,13 +1807,13 @@ impl Default for BoolValueParser { /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::FalseyValueParser::new(); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("100")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("false")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("No")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oFF")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0")).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("100"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("false"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("No"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oFF"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0"), 0).unwrap(), false); /// ``` #[derive(Copy, Clone, Debug)] #[non_exhaustive] @@ -1800,11 +1842,13 @@ impl TypedValueParser for FalseyValueParser { cmd: &crate::Command, _arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = ok!(value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); let value = if value.is_empty() { @@ -1860,17 +1904,17 @@ impl Default for FalseyValueParser { /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::BoolishValueParser::new(); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("100")).is_err()); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("true")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("Yes")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oN")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("1")).unwrap(), true); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("false")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("No")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oFF")).unwrap(), false); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0")).unwrap(), false); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("100"), 0).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("true"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("Yes"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oN"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("1"), 0).unwrap(), true); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("false"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("No"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("oFF"), 0).unwrap(), false); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("0"), 0).unwrap(), false); /// ``` #[derive(Copy, Clone, Debug)] #[non_exhaustive] @@ -1899,19 +1943,26 @@ impl TypedValueParser for BoolishValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { let value = ok!(value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); let value = ok!(crate::util::str_to_bool(value).ok_or_else(|| { let arg = arg .map(|a| a.to_string()) .unwrap_or_else(|| "...".to_owned()); - crate::Error::value_validation(arg, value.to_owned(), "value was not a boolean".into()) - .with_cmd(cmd) + crate::Error::value_validation( + arg, + value.to_owned(), + "value was not a boolean".into(), + index, + ) + .with_cmd(cmd) })); Ok(value) } @@ -1960,8 +2011,8 @@ impl Default for BoolishValueParser { /// # let cmd = clap::Command::new("test"); /// # let arg = None; /// let value_parser = clap::builder::NonEmptyStringValueParser::new(); -/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("random")).unwrap(), "random"); -/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new("")).is_err()); +/// assert_eq!(value_parser.parse_ref(&cmd, arg, OsStr::new("random"), 0).unwrap(), "random"); +/// assert!(value_parser.parse_ref(&cmd, arg, OsStr::new(""), 0).is_err()); /// ``` #[derive(Copy, Clone, Debug)] #[non_exhaustive] @@ -1982,6 +2033,7 @@ impl TypedValueParser for NonEmptyStringValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { if value.is_empty() { return Err(crate::Error::empty_value( @@ -1989,12 +2041,14 @@ impl TypedValueParser for NonEmptyStringValueParser { &[], arg.map(ToString::to_string) .unwrap_or_else(|| "...".to_owned()), + index, )); } let value = ok!(value.to_str().ok_or_else(|| { crate::Error::invalid_utf8( cmd, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ) })); Ok(value.to_owned()) @@ -2042,8 +2096,9 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - let value = ok!(self.parser.parse_ref(cmd, arg, value)); + let value = ok!(self.parser.parse_ref(cmd, arg, value, index)); let value = (self.func)(value); Ok(value) } @@ -2053,8 +2108,9 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, value: std::ffi::OsString, + index: isize, ) -> Result { - let value = ok!(self.parser.parse(cmd, arg, value)); + let value = ok!(self.parser.parse(cmd, arg, value, index)); let value = (self.func)(value); Ok(value) } @@ -2103,14 +2159,20 @@ where cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - let mid_value = ok!(self.parser.parse_ref(cmd, arg, value)); + let mid_value = ok!(self.parser.parse_ref(cmd, arg, value, index)); 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) + crate::Error::value_validation( + arg, + value.to_string_lossy().into_owned(), + e.into(), + index, + ) + .with_cmd(cmd) })); Ok(value) } @@ -2193,8 +2255,9 @@ impl TypedValueParser for UnknownArgumentValueParser { cmd: &crate::Command, arg: Option<&crate::Arg>, value: &std::ffi::OsStr, + index: isize, ) -> Result { - TypedValueParser::parse_ref_(self, cmd, arg, value, ValueSource::CommandLine) + TypedValueParser::parse_ref_(self, cmd, arg, value, ValueSource::CommandLine, index) } fn parse_ref_( @@ -2203,11 +2266,17 @@ impl TypedValueParser for UnknownArgumentValueParser { arg: Option<&crate::Arg>, _value: &std::ffi::OsStr, source: ValueSource, + index: isize, ) -> Result { match source { - ValueSource::DefaultValue => { - TypedValueParser::parse_ref_(&StringValueParser::new(), cmd, arg, _value, source) - } + ValueSource::DefaultValue => TypedValueParser::parse_ref_( + &StringValueParser::new(), + cmd, + arg, + _value, + source, + index, + ), ValueSource::EnvVariable | ValueSource::CommandLine => { let arg = match arg { Some(arg) => arg.to_string(), @@ -2219,6 +2288,7 @@ impl TypedValueParser for UnknownArgumentValueParser { self.arg.as_ref().map(|s| (s.as_str().to_owned(), None)), false, crate::output::Usage::new(cmd).create_usage_with_title(&[]), + index, ); #[cfg(feature = "error-context")] let err = { @@ -2264,9 +2334,10 @@ impl TypedValueParser for UnknownArgumentValueParser { /// cmd: &clap::Command, /// arg: Option<&clap::Arg>, /// value: &std::ffi::OsStr, +/// index: isize, /// ) -> Result { /// let inner = clap::value_parser!(u32); -/// let val = inner.parse_ref(cmd, arg, value)?; +/// let val = inner.parse_ref(cmd, arg, value, index)?; /// Ok(Custom(val)) /// } /// } @@ -2680,7 +2751,7 @@ mod test { let cmd = crate::Command::new("cmd"); let arg = None; assert_eq!( - TypedValueParser::parse_ref(&parse, &cmd, arg, std::ffi::OsStr::new("foo")).unwrap(), + TypedValueParser::parse_ref(&parse, &cmd, arg, std::ffi::OsStr::new("foo"), 0).unwrap(), 10 ); } diff --git a/clap_builder/src/error/context.rs b/clap_builder/src/error/context.rs index 045923c4771..36341da7bfe 100644 --- a/clap_builder/src/error/context.rs +++ b/clap_builder/src/error/context.rs @@ -37,6 +37,8 @@ pub enum ContextKind { Usage, /// An opaque message to the user Custom, + /// Index of the argument + ArgIndex, } impl ContextKind { @@ -60,6 +62,7 @@ impl ContextKind { Self::Suggested => Some("Suggested"), Self::Usage => None, Self::Custom => None, + Self::ArgIndex => Some("Argument Index"), } } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index 7bd627375c1..2717929fa29 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -368,6 +368,7 @@ impl Error { arg: String, mut others: Vec, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd); @@ -381,6 +382,7 @@ impl Error { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::PriorArg, others), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -396,6 +398,7 @@ impl Error { sub: String, mut others: Vec, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd); @@ -409,6 +412,7 @@ impl Error { err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(sub)), (ContextKind::PriorArg, others), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -419,17 +423,29 @@ impl Error { err } - pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self { - Self::invalid_value(cmd, "".to_owned(), good_vals, arg) + pub(crate) fn empty_value( + cmd: &Command, + good_vals: &[String], + arg: String, + index: isize, + ) -> Self { + Self::invalid_value(cmd, "".to_owned(), good_vals, arg, index) } - pub(crate) fn no_equals(cmd: &Command, arg: String, usage: Option) -> Self { + pub(crate) fn no_equals( + cmd: &Command, + arg: String, + usage: Option, + index: isize, + ) -> Self { let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd); #[cfg(feature = "error-context")] { - err = err - .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]); + err = err.extend_context_unchecked([ + (ContextKind::InvalidArg, ContextValue::String(arg)), + (ContextKind::ArgIndex, ContextValue::Number(index)), + ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); @@ -444,6 +460,7 @@ impl Error { bad_val: String, good_vals: &[String], arg: String, + index: isize, ) -> Self { let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop(); let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd); @@ -457,6 +474,7 @@ impl Error { ContextKind::ValidValue, ContextValue::Strings(good_vals.iter().map(|s| (*s).clone()).collect()), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(suggestion) = suggestion { err = err.insert_context_unchecked( @@ -476,6 +494,7 @@ impl Error { name: String, suggested_trailing_arg: bool, usage: Option, + index: isize, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); @@ -505,6 +524,7 @@ impl Error { ContextKind::Suggested, ContextValue::StyledStrs(suggestions), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -519,15 +539,16 @@ impl Error { cmd: &Command, subcmd: String, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { - err = err.extend_context_unchecked([( - ContextKind::InvalidSubcommand, - ContextValue::String(subcmd), - )]); + err = err.extend_context_unchecked([ + (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), + (ContextKind::ArgIndex, ContextValue::Number(index)), + ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); @@ -541,15 +562,16 @@ impl Error { cmd: &Command, required: Vec, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd); #[cfg(feature = "error-context")] { - err = err.extend_context_unchecked([( - ContextKind::InvalidArg, - ContextValue::Strings(required), - )]); + err = err.extend_context_unchecked([ + (ContextKind::InvalidArg, ContextValue::Strings(required)), + (ContextKind::ArgIndex, ContextValue::Number(index)), + ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); @@ -564,6 +586,7 @@ impl Error { parent: String, available: Vec, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd); @@ -575,6 +598,7 @@ impl Error { ContextKind::ValidSubcommand, ContextValue::Strings(available), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -585,11 +609,13 @@ impl Error { err } - pub(crate) fn invalid_utf8(cmd: &Command, usage: Option) -> Self { + pub(crate) fn invalid_utf8(cmd: &Command, usage: Option, index: isize) -> Self { let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd); #[cfg(feature = "error-context")] { + err = err + .extend_context_unchecked([(ContextKind::ArgIndex, ContextValue::Number(index))]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); @@ -604,6 +630,7 @@ impl Error { val: String, arg: String, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd); @@ -612,6 +639,7 @@ impl Error { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -628,6 +656,7 @@ impl Error { min_vals: usize, curr_vals: usize, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd); @@ -643,6 +672,7 @@ impl Error { ContextKind::ActualNumValues, ContextValue::Number(curr_vals as isize), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -657,6 +687,7 @@ impl Error { arg: String, val: String, err: Box, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::ValueValidation).set_source(err); @@ -665,6 +696,7 @@ impl Error { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); } @@ -677,6 +709,7 @@ impl Error { num_vals: usize, curr_vals: usize, usage: Option, + index: isize, ) -> Self { let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd); @@ -692,6 +725,7 @@ impl Error { ContextKind::ActualNumValues, ContextValue::Number(curr_vals as isize), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err @@ -708,6 +742,7 @@ impl Error { did_you_mean: Option<(String, Option)>, suggested_trailing_arg: bool, usage: Option, + index: isize, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); @@ -727,8 +762,10 @@ impl Error { suggestions.push(styled_suggestion); } - err = err - .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]); + err = err.extend_context_unchecked([ + (ContextKind::InvalidArg, ContextValue::String(arg)), + (ContextKind::ArgIndex, ContextValue::Number(index)), + ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); @@ -762,6 +799,7 @@ impl Error { cmd: &Command, arg: String, usage: Option, + index: isize, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); @@ -783,6 +821,7 @@ impl Error { ContextKind::Suggested, ContextValue::StyledStrs(vec![styled_suggestion]), ), + (ContextKind::ArgIndex, ContextValue::Number(index)), ]); if let Some(usage) = usage { err = err diff --git a/clap_builder/src/parser/parser.rs b/clap_builder/src/parser/parser.rs index d787f89aa16..85318f9795c 100644 --- a/clap_builder/src/parser/parser.rs +++ b/clap_builder/src/parser/parser.rs @@ -53,22 +53,23 @@ impl<'cmd> Parser<'cmd> { args_cursor: clap_lex::ArgCursor, ) -> ClapResult<()> { debug!("Parser::get_matches_with"); + let index = args_cursor.cursor as isize; ok!(self.parse(matcher, raw_args, args_cursor).map_err(|err| { if self.cmd.is_ignore_errors_set() { #[cfg(feature = "env")] - let _ = self.add_env(matcher); - let _ = self.add_defaults(matcher); + let _ = self.add_env(matcher, index); + let _ = self.add_defaults(matcher, index); } err })); - ok!(self.resolve_pending(matcher)); + ok!(self.resolve_pending(matcher, index)); #[cfg(feature = "env")] - ok!(self.add_env(matcher)); - ok!(self.add_defaults(matcher)); + ok!(self.add_env(matcher, index)); + ok!(self.add_defaults(matcher, index)); - Validator::new(self.cmd).validate(matcher) + Validator::new(self.cmd).validate(matcher, index) } // The actual parsing function @@ -103,6 +104,7 @@ impl<'cmd> Parser<'cmd> { let contains_last = self.cmd.get_arguments().any(|x| x.is_last_set()); while let Some(arg_os) = raw_args.next(&mut args_cursor) { + let index = args_cursor.cursor as isize; debug!( "Parser::get_matches_with: Begin parsing '{:?}'", arg_os.to_value_os(), @@ -118,7 +120,10 @@ impl<'cmd> Parser<'cmd> { debug!("Parser::get_matches_with: sc={sc_name:?}"); if let Some(sc_name) = sc_name { if sc_name == "help" && !self.cmd.is_disable_help_subcommand_set() { - ok!(self.parse_help_subcommand(raw_args.remaining(&mut args_cursor))); + ok!(self.parse_help_subcommand( + raw_args.remaining(&mut args_cursor), + index + )); unreachable!("`parse_help_subcommand` always errors"); } else { subcmd_name = Some(sc_name.to_owned()); @@ -146,6 +151,7 @@ impl<'cmd> Parser<'cmd> { &parse_state, pos_counter, &mut valid_arg_found, + index, )); debug!("Parser::get_matches_with: After parse_long_arg {parse_result:?}"); match parse_result { @@ -169,15 +175,16 @@ impl<'cmd> Parser<'cmd> { break; } ParseResult::EqualsNotProvided { arg } => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); return Err(ClapError::no_equals( self.cmd, arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } ParseResult::NoMatchingArg { arg } => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); let remaining_args: Vec<_> = raw_args.remaining(&mut args_cursor).collect(); return Err(self.did_you_mean_error( @@ -185,15 +192,17 @@ impl<'cmd> Parser<'cmd> { matcher, &remaining_args, trailing_values, + index, )); } ParseResult::UnneededAttachedValue { rest, used, arg } => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); return Err(ClapError::too_many_values( self.cmd, rest, arg, Usage::new(self.cmd).create_usage_with_title(&used), + index, )); } ParseResult::MaybeHyphenValue => { @@ -215,6 +224,7 @@ impl<'cmd> Parser<'cmd> { &parse_state, pos_counter, &mut valid_arg_found, + index, )); // If it's None, we then check if one of those two AppSettings was set debug!("Parser::get_matches_with: After parse_short_arg {parse_result:?}"); @@ -255,15 +265,16 @@ impl<'cmd> Parser<'cmd> { break; } ParseResult::EqualsNotProvided { arg } => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); return Err(ClapError::no_equals( self.cmd, arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } ParseResult::NoMatchingArg { arg } => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); // We already know it looks like a flag let suggested_trailing_arg = !trailing_values && self.cmd.has_positionals(); @@ -273,6 +284,7 @@ impl<'cmd> Parser<'cmd> { None, suggested_trailing_arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } ParseResult::MaybeHyphenValue => { @@ -386,7 +398,7 @@ impl<'cmd> Parser<'cmd> { if let Some(arg) = self.cmd.get_keymap().get(&pos_counter) { if arg.is_last_set() && !trailing_values { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); // Its already considered a positional, we don't need to suggest turning it // into one let suggested_trailing_arg = false; @@ -396,6 +408,7 @@ impl<'cmd> Parser<'cmd> { None, suggested_trailing_arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } @@ -404,7 +417,7 @@ impl<'cmd> Parser<'cmd> { } if matcher.pending_arg_id() != Some(arg.get_id()) || !arg.is_multiple_values_set() { - ok!(self.resolve_pending(matcher)); + ok!(self.resolve_pending(matcher, index)); } parse_state = if let Some(parse_result) = self.check_terminator(arg, arg_os.to_value_os()) { @@ -435,10 +448,11 @@ impl<'cmd> Parser<'cmd> { let sc_name = match arg_os.to_value() { Ok(s) => s.to_owned(), Err(_) => { - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); return Err(ClapError::invalid_utf8( self.cmd, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } }; @@ -452,7 +466,8 @@ impl<'cmd> Parser<'cmd> { self.cmd, None, raw_val, - ValueSource::CommandLine + ValueSource::CommandLine, + index, )); let external_id = Id::from_static_ref(Id::EXTERNAL); sc_m.add_val_to(&external_id, val, raw_val.to_os_string()); @@ -466,12 +481,13 @@ impl<'cmd> Parser<'cmd> { return Ok(()); } else { // Start error processing - let _ = self.resolve_pending(matcher); + let _ = self.resolve_pending(matcher, index); return Err(self.match_arg_error( &arg_os, valid_arg_found, trailing_values, matcher, + index, )); } } @@ -486,6 +502,7 @@ impl<'cmd> Parser<'cmd> { .map(|id| self.cmd.find(id).unwrap().to_string()) .collect(), Usage::new(self.cmd).create_usage_with_title(&[]), + args_cursor.cursor as isize, )); } let sc_name = self @@ -506,6 +523,7 @@ impl<'cmd> Parser<'cmd> { valid_arg_found: bool, trailing_values: bool, matcher: &ArgMatcher, + index: isize, ) -> ClapError { // If argument follows a `--` if trailing_values { @@ -518,6 +536,7 @@ impl<'cmd> Parser<'cmd> { self.cmd, arg_os.display().to_string(), Usage::new(self.cmd).create_usage_with_title(&[]), + index, ); } } @@ -536,6 +555,7 @@ impl<'cmd> Parser<'cmd> { .filter_map(|id| self.cmd.find(id).map(|a| a.to_string())) .collect(), Usage::new(self.cmd).create_usage_with_title(&[]), + index, ); } @@ -552,6 +572,7 @@ impl<'cmd> Parser<'cmd> { self.cmd.get_bin_name_fallback().to_owned(), suggested_trailing_arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, ); } @@ -561,6 +582,7 @@ impl<'cmd> Parser<'cmd> { self.cmd, arg_os.display().to_string(), Usage::new(self.cmd).create_usage_with_title(&[]), + index, ); } } @@ -571,6 +593,7 @@ impl<'cmd> Parser<'cmd> { None, suggested_trailing_arg, Usage::new(self.cmd).create_usage_with_title(&[]), + index, ) } @@ -647,6 +670,7 @@ impl<'cmd> Parser<'cmd> { fn parse_help_subcommand( &self, cmds: impl Iterator, + index: isize, ) -> ClapResult { debug!("Parser::parse_help_subcommand"); @@ -664,6 +688,7 @@ impl<'cmd> Parser<'cmd> { sc, cmd.to_string_lossy().into_owned(), Usage::new(sc).create_usage_with_title(&[]), + index, )); }; } @@ -760,6 +785,7 @@ impl<'cmd> Parser<'cmd> { parse_state: &ParseState, pos_counter: usize, valid_arg_found: &mut bool, + index: isize, ) -> ClapResult { // maybe here lifetime should be 'a debug!("Parser::parse_long_arg"); @@ -817,7 +843,7 @@ impl<'cmd> Parser<'cmd> { long_arg, &long_value ); let has_eq = long_value.is_some(); - self.parse_opt_value(ident, long_value, arg, matcher, has_eq) + self.parse_opt_value(ident, long_value, arg, matcher, has_eq, index) } else if let Some(rest) = long_value { let required = self.cmd.required_graph(); debug!("Parser::parse_long_arg({long_arg:?}): Got invalid literal `{rest:?}`"); @@ -851,6 +877,7 @@ impl<'cmd> Parser<'cmd> { vec![], trailing_idx, matcher, + index, ) } } else if let Some(sc_name) = self.possible_long_flag_subcommand(long_arg) { @@ -879,6 +906,7 @@ impl<'cmd> Parser<'cmd> { // change this to possible pos_arg when removing the usage of &mut Parser. pos_counter: usize, valid_arg_found: &mut bool, + index: isize, ) -> ClapResult { debug!("Parser::parse_short_arg: short_arg={short_arg:?}"); @@ -951,6 +979,7 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); continue; } @@ -974,7 +1003,7 @@ impl<'cmd> Parser<'cmd> { } else { (val, false) }; - match ok!(self.parse_opt_value(ident, val, arg, matcher, has_eq)) { + match ok!(self.parse_opt_value(ident, val, arg, matcher, has_eq, index)) { ParseResult::AttachedValueNotConsumed => continue, x => return Ok(x), } @@ -983,7 +1012,7 @@ impl<'cmd> Parser<'cmd> { return if let Some(sc_name) = self.cmd.find_short_subcmd(c) { debug!("Parser::parse_short_arg:iter:{c}: subcommand={sc_name}"); // Make sure indices get updated before reading `self.cur_idx` - ok!(self.resolve_pending(matcher)); + ok!(self.resolve_pending(matcher, index)); self.cur_idx.set(self.cur_idx.get() + 1); debug!("Parser::parse_short_arg: cur_idx:={}", self.cur_idx.get()); @@ -1014,6 +1043,7 @@ impl<'cmd> Parser<'cmd> { arg: &Arg, matcher: &mut ArgMatcher, has_eq: bool, + index: isize, ) -> ClapResult { debug!( "Parser::parse_opt_value; arg={}, val={:?}, has_eq={:?}", @@ -1037,6 +1067,7 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); debug_assert_eq!(react_result, ParseResult::ValuesDone); if attached_value.is_some() { @@ -1060,13 +1091,14 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); debug_assert_eq!(react_result, ParseResult::ValuesDone); // Attached are always done Ok(ParseResult::ValuesDone) } else { debug!("Parser::parse_opt_value: More arg vals required..."); - ok!(self.resolve_pending(matcher)); + ok!(self.resolve_pending(matcher, index)); let trailing_values = false; matcher.pending_values_mut(arg.get_id(), Some(ident), trailing_values); Ok(ParseResult::Opt(arg.get_id().clone())) @@ -1088,6 +1120,7 @@ impl<'cmd> Parser<'cmd> { raw_vals: Vec, source: ValueSource, matcher: &mut ArgMatcher, + index: isize, ) -> ClapResult<()> { debug!("Parser::push_arg_values: {raw_vals:?}"); @@ -1099,7 +1132,7 @@ impl<'cmd> Parser<'cmd> { self.cur_idx.get() ); let value_parser = arg.get_value_parser(); - let val = ok!(value_parser.parse_ref(self.cmd, Some(arg), &raw_val, source)); + let val = ok!(value_parser.parse_ref(self.cmd, Some(arg), &raw_val, source, index)); matcher.add_val_to(arg.get_id(), val, raw_val); matcher.add_index_to(arg.get_id(), self.cur_idx.get()); @@ -1108,7 +1141,7 @@ impl<'cmd> Parser<'cmd> { Ok(()) } - fn resolve_pending(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + fn resolve_pending(&self, matcher: &mut ArgMatcher, index: isize) -> ClapResult<()> { let pending = match matcher.take_pending() { Some(pending) => pending, None => { @@ -1125,6 +1158,7 @@ impl<'cmd> Parser<'cmd> { pending.raw_vals, pending.trailing_idx, matcher, + index, )); Ok(()) @@ -1138,8 +1172,9 @@ impl<'cmd> Parser<'cmd> { mut raw_vals: Vec, mut trailing_idx: Option, matcher: &mut ArgMatcher, + index: isize, ) -> ClapResult { - ok!(self.resolve_pending(matcher)); + ok!(self.resolve_pending(matcher, index)); debug!( "Parser::react action={:?}, identifier={:?}, source={:?}", @@ -1151,7 +1186,7 @@ impl<'cmd> Parser<'cmd> { // Process before `default_missing_values` to avoid it counting as values from the command // line if source == ValueSource::CommandLine { - ok!(self.verify_num_args(arg, &raw_vals)); + ok!(self.verify_num_args(arg, &raw_vals, index)); } if raw_vals.is_empty() { @@ -1205,10 +1240,11 @@ impl<'cmd> Parser<'cmd> { arg.to_string(), vec![arg.to_string()], Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } self.start_custom_arg(matcher, arg, source); - ok!(self.push_arg_values(arg, raw_vals, source, matcher)); + ok!(self.push_arg_values(arg, raw_vals, source, matcher, index)); if cfg!(debug_assertions) && matcher.needs_more_vals(arg) { debug!( "Parser::react not enough values passed in, leaving it to the validator to complain", @@ -1225,7 +1261,7 @@ impl<'cmd> Parser<'cmd> { debug!("Parser::react: cur_idx:={}", self.cur_idx.get()); } self.start_custom_arg(matcher, arg, source); - ok!(self.push_arg_values(arg, raw_vals, source, matcher)); + ok!(self.push_arg_values(arg, raw_vals, source, matcher, index)); if cfg!(debug_assertions) && matcher.needs_more_vals(arg) { debug!( "Parser::react not enough values passed in, leaving it to the validator to complain", @@ -1248,10 +1284,11 @@ impl<'cmd> Parser<'cmd> { arg.to_string(), vec![arg.to_string()], Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } self.start_custom_arg(matcher, arg, source); - ok!(self.push_arg_values(arg, raw_vals, source, matcher)); + ok!(self.push_arg_values(arg, raw_vals, source, matcher, index)); Ok(ParseResult::ValuesDone) } ArgAction::SetFalse => { @@ -1269,10 +1306,11 @@ impl<'cmd> Parser<'cmd> { arg.to_string(), vec![arg.to_string()], Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } self.start_custom_arg(matcher, arg, source); - ok!(self.push_arg_values(arg, raw_vals, source, matcher)); + ok!(self.push_arg_values(arg, raw_vals, source, matcher, index)); Ok(ParseResult::ValuesDone) } ArgAction::Count => { @@ -1288,7 +1326,7 @@ impl<'cmd> Parser<'cmd> { matcher.remove(arg.get_id()); self.start_custom_arg(matcher, arg, source); - ok!(self.push_arg_values(arg, raw_vals, source, matcher)); + ok!(self.push_arg_values(arg, raw_vals, source, matcher, index)); Ok(ParseResult::ValuesDone) } ArgAction::Help => { @@ -1324,7 +1362,7 @@ impl<'cmd> Parser<'cmd> { } } - fn verify_num_args(&self, arg: &Arg, raw_vals: &[OsString]) -> ClapResult<()> { + fn verify_num_args(&self, arg: &Arg, raw_vals: &[OsString], index: isize) -> ClapResult<()> { if self.cmd.is_ignore_errors_set() { return Ok(()); } @@ -1343,6 +1381,7 @@ impl<'cmd> Parser<'cmd> { .map(|n| n.get_name().to_owned()) .collect::>(), arg.to_string(), + index, )); } else if let Some(expected) = expected.num_values() { if expected != actual { @@ -1353,6 +1392,7 @@ impl<'cmd> Parser<'cmd> { expected, actual, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } } else if actual < expected.min_values() { @@ -1362,6 +1402,7 @@ impl<'cmd> Parser<'cmd> { expected.min_values(), actual, Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } else if expected.max_values() < actual { debug!("Validator::validate_arg_num_vals: Sending error TooManyValues"); @@ -1374,6 +1415,7 @@ impl<'cmd> Parser<'cmd> { .into_owned(), arg.to_string(), Usage::new(self.cmd).create_usage_with_title(&[]), + index, )); } @@ -1403,7 +1445,7 @@ impl<'cmd> Parser<'cmd> { } #[cfg(feature = "env")] - fn add_env(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> { + fn add_env(&mut self, matcher: &mut ArgMatcher, index: isize) -> ClapResult<()> { debug!("Parser::add_env"); for arg in self.cmd.get_arguments() { @@ -1426,6 +1468,7 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); } } @@ -1433,18 +1476,23 @@ impl<'cmd> Parser<'cmd> { Ok(()) } - fn add_defaults(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + fn add_defaults(&self, matcher: &mut ArgMatcher, index: isize) -> ClapResult<()> { debug!("Parser::add_defaults"); for arg in self.cmd.get_arguments() { debug!("Parser::add_defaults:iter:{}:", arg.get_id()); - ok!(self.add_default_value(arg, matcher)); + ok!(self.add_default_value(arg, matcher, index)); } Ok(()) } - fn add_default_value(&self, arg: &Arg, matcher: &mut ArgMatcher) -> ClapResult<()> { + fn add_default_value( + &self, + arg: &Arg, + matcher: &mut ArgMatcher, + index: isize, + ) -> ClapResult<()> { if !arg.default_vals_ifs.is_empty() { debug!("Parser::add_default_value: has conditional defaults"); if !matcher.contains(arg.get_id()) { @@ -1471,6 +1519,7 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); } return Ok(()); @@ -1507,6 +1556,7 @@ impl<'cmd> Parser<'cmd> { arg_values, trailing_idx, matcher, + index, )); } } else { @@ -1549,6 +1599,7 @@ impl Parser<'_> { matcher: &mut ArgMatcher, remaining_args: &[&OsStr], trailing_values: bool, + index: isize, ) -> ClapError { debug!("Parser::did_you_mean_error: arg={arg}"); // Didn't match a flag or option @@ -1609,6 +1660,7 @@ impl Parser<'_> { Usage::new(self.cmd) .required(&required) .create_usage_with_title(&used), + index, ) } diff --git a/clap_builder/src/parser/validator.rs b/clap_builder/src/parser/validator.rs index 43552c926d0..4148e704acd 100644 --- a/clap_builder/src/parser/validator.rs +++ b/clap_builder/src/parser/validator.rs @@ -21,7 +21,7 @@ impl<'cmd> Validator<'cmd> { Validator { cmd, required } } - pub(crate) fn validate(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> { + pub(crate) fn validate(&mut self, matcher: &mut ArgMatcher, index: isize) -> ClapResult<()> { debug!("Validator::validate"); let conflicts = Conflicts::with_args(self.cmd, matcher); let has_subcmd = matcher.subcommand_name().is_some(); @@ -48,12 +48,13 @@ impl<'cmd> Validator<'cmd> { Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&[]), + index, )); } - ok!(self.validate_conflicts(matcher, &conflicts)); + ok!(self.validate_conflicts(matcher, &conflicts, index)); if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) { - ok!(self.validate_required(matcher, &conflicts)); + ok!(self.validate_required(matcher, &conflicts, index)); } Ok(()) @@ -63,10 +64,11 @@ impl<'cmd> Validator<'cmd> { &mut self, matcher: &ArgMatcher, conflicts: &Conflicts, + index: isize, ) -> ClapResult<()> { debug!("Validator::validate_conflicts"); - ok!(self.validate_exclusive(matcher)); + ok!(self.validate_exclusive(matcher, index)); for (arg_id, _) in matcher .args() @@ -75,13 +77,13 @@ impl<'cmd> Validator<'cmd> { { debug!("Validator::validate_conflicts::iter: id={arg_id:?}"); let conflicts = conflicts.gather_conflicts(self.cmd, arg_id); - ok!(self.build_conflict_err(arg_id, &conflicts, matcher)); + ok!(self.build_conflict_err(arg_id, &conflicts, matcher, index)); } Ok(()) } - fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> { + fn validate_exclusive(&self, matcher: &ArgMatcher, index: isize) -> ClapResult<()> { debug!("Validator::validate_exclusive"); let args_count = matcher .args() @@ -116,6 +118,7 @@ impl<'cmd> Validator<'cmd> { Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&[]), + index, )) }) .unwrap_or(Ok(())) @@ -126,6 +129,7 @@ impl<'cmd> Validator<'cmd> { name: &Id, conflict_ids: &[Id], matcher: &ArgMatcher, + index: isize, ) -> ClapResult<()> { if conflict_ids.is_empty() { return Ok(()); @@ -157,6 +161,7 @@ impl<'cmd> Validator<'cmd> { former_arg.to_string(), conflicts, usg, + index, )) } @@ -217,7 +222,12 @@ impl<'cmd> Validator<'cmd> { } } - fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> { + fn validate_required( + &mut self, + matcher: &ArgMatcher, + conflicts: &Conflicts, + index: isize, + ) -> ClapResult<()> { debug!("Validator::validate_required: required={:?}", self.required); self.gather_requires(matcher); @@ -335,7 +345,7 @@ impl<'cmd> Validator<'cmd> { } if !missing_required.is_empty() { - ok!(self.missing_required_error(matcher, missing_required)); + ok!(self.missing_required_error(matcher, missing_required, index)); } Ok(()) @@ -370,6 +380,7 @@ impl<'cmd> Validator<'cmd> { &self, matcher: &ArgMatcher, raw_req_args: Vec, + index: isize, ) -> ClapResult<()> { debug!("Validator::missing_required_error; incl={raw_req_args:?}"); debug!( @@ -429,6 +440,7 @@ impl<'cmd> Validator<'cmd> { self.cmd, req_args, usg.create_usage_with_title(&used), + index, )) } } diff --git a/clap_lex/src/lib.rs b/clap_lex/src/lib.rs index 4ff5871018e..c400e182571 100644 --- a/clap_lex/src/lib.rs +++ b/clap_lex/src/lib.rs @@ -274,7 +274,8 @@ where /// Position within [`RawArgs`] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ArgCursor { - cursor: usize, + /// Current position + pub cursor: usize, } impl ArgCursor { diff --git a/tests/builder/possible_values.rs b/tests/builder/possible_values.rs index 85487bf2185..76dcb1fe25f 100644 --- a/tests/builder/possible_values.rs +++ b/tests/builder/possible_values.rs @@ -539,6 +539,7 @@ mod expensive { _cmd: &Command, _arg: Option<&Arg>, _value: &std::ffi::OsStr, + _index: isize, ) -> Result { unimplemented!() }