Skip to content

Commit 9f65eb0

Browse files
committed
refactor(error): Give caller control over suggestion
1 parent 8413c15 commit 9f65eb0

File tree

4 files changed

+54
-15
lines changed

4 files changed

+54
-15
lines changed

clap_builder/src/builder/value_parser.rs

+39-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::convert::TryInto;
22
use std::ops::RangeBounds;
33

44
use crate::builder::Str;
5+
use crate::builder::StyledStr;
56
use crate::util::AnyValue;
67
use crate::util::AnyValueId;
78

@@ -2104,7 +2105,7 @@ where
21042105
/// Arg::new("current-dir-unknown")
21052106
/// .long("cwd")
21062107
/// .aliases(["current-dir", "directory", "working-directory", "root"])
2107-
/// .value_parser(clap::builder::UnknownArgumentValueParser::suggest("-C"))
2108+
/// .value_parser(clap::builder::UnknownArgumentValueParser::suggest_arg("-C"))
21082109
/// .hide(true),
21092110
/// ]);
21102111
///
@@ -2122,13 +2123,31 @@ where
21222123
/// ```
21232124
#[derive(Clone, Debug)]
21242125
pub struct UnknownArgumentValueParser {
2125-
arg: Str,
2126+
arg: Option<Str>,
2127+
suggestions: Vec<StyledStr>,
21262128
}
21272129

21282130
impl UnknownArgumentValueParser {
21292131
/// Suggest an alternative argument
2130-
pub fn suggest(arg: impl Into<Str>) -> Self {
2131-
Self { arg: arg.into() }
2132+
pub fn suggest_arg(arg: impl Into<Str>) -> Self {
2133+
Self {
2134+
arg: Some(arg.into()),
2135+
suggestions: Default::default(),
2136+
}
2137+
}
2138+
2139+
/// Provide a general suggestion
2140+
pub fn suggest(text: impl Into<StyledStr>) -> Self {
2141+
Self {
2142+
arg: Default::default(),
2143+
suggestions: vec![text.into()],
2144+
}
2145+
}
2146+
2147+
/// Extend the suggestions
2148+
pub fn and_suggest(mut self, text: impl Into<StyledStr>) -> Self {
2149+
self.suggestions.push(text.into());
2150+
self
21322151
}
21332152
}
21342153

@@ -2145,13 +2164,26 @@ impl TypedValueParser for UnknownArgumentValueParser {
21452164
Some(arg) => arg.to_string(),
21462165
None => "..".to_owned(),
21472166
};
2148-
Err(crate::Error::unknown_argument(
2167+
let err = crate::Error::unknown_argument(
21492168
cmd,
21502169
arg,
2151-
Some((self.arg.as_str().to_owned(), None)),
2170+
self.arg.as_ref().map(|s| (s.as_str().to_owned(), None)),
21522171
false,
21532172
crate::output::Usage::new(cmd).create_usage_with_title(&[]),
2154-
))
2173+
);
2174+
#[cfg(feature = "error-context")]
2175+
let err = {
2176+
debug_assert_eq!(
2177+
err.get(crate::error::ContextKind::Suggested),
2178+
None,
2179+
"Assuming `Error::unknown_argument` doesn't apply any `Suggested` so we can without caution"
2180+
);
2181+
err.insert_context_unchecked(
2182+
crate::error::ContextKind::Suggested,
2183+
crate::error::ContextValue::StyledStrs(self.suggestions.clone()),
2184+
)
2185+
};
2186+
Err(err)
21552187
}
21562188
}
21572189

clap_builder/src/error/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ impl<F: ErrorFormatter> Error<F> {
718718
let mut styled_suggestion = StyledStr::new();
719719
let _ = write!(
720720
styled_suggestion,
721-
"'{}{sub} --{flag}{}' exists",
721+
"'{}{sub} {flag}{}' exists",
722722
valid.render(),
723723
valid.render_reset()
724724
);
@@ -727,7 +727,7 @@ impl<F: ErrorFormatter> Error<F> {
727727
Some((flag, None)) => {
728728
err = err.insert_context_unchecked(
729729
ContextKind::SuggestedArg,
730-
ContextValue::String(format!("--{flag}")),
730+
ContextValue::String(flag),
731731
);
732732
}
733733
None => {}

clap_builder/src/parser/parser.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,7 @@ impl<'cmd> Parser<'cmd> {
15211521
self.start_custom_arg(matcher, arg, ValueSource::CommandLine);
15221522
}
15231523
}
1524+
let did_you_mean = did_you_mean.map(|(arg, cmd)| (format!("--{arg}"), cmd));
15241525

15251526
let required = self.cmd.required_graph();
15261527
let used: Vec<Id> = matcher

tests/builder/error.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,10 @@ fn unknown_argument_option() {
219219
Arg::new("current-dir-unknown")
220220
.long("cwd")
221221
.aliases(["current-dir", "directory", "working-directory", "root"])
222-
.value_parser(clap::builder::UnknownArgumentValueParser::suggest("-C"))
222+
.value_parser(
223+
clap::builder::UnknownArgumentValueParser::suggest_arg("-C")
224+
.and_suggest("not much else to say"),
225+
)
223226
.hide(true),
224227
]);
225228
let res = cmd.try_get_matches_from(["test", "--cwd", ".."]);
@@ -229,7 +232,8 @@ fn unknown_argument_option() {
229232
static MESSAGE: &str = "\
230233
error: unexpected argument '--cwd <current-dir-unknown>' found
231234
232-
tip: a similar argument exists: '---C'
235+
tip: a similar argument exists: '-C'
236+
tip: not much else to say
233237
234238
Usage: test [OPTIONS]
235239
@@ -247,9 +251,10 @@ fn unknown_argument_flag() {
247251
Arg::new("libtest-ignore")
248252
.long("ignored")
249253
.action(ArgAction::SetTrue)
250-
.value_parser(clap::builder::UnknownArgumentValueParser::suggest(
251-
"-- --ignored",
252-
))
254+
.value_parser(
255+
clap::builder::UnknownArgumentValueParser::suggest_arg("-- --ignored")
256+
.and_suggest("not much else to say"),
257+
)
253258
.hide(true),
254259
]);
255260
let res = cmd.try_get_matches_from(["test", "--ignored"]);
@@ -259,7 +264,8 @@ fn unknown_argument_flag() {
259264
static MESSAGE: &str = "\
260265
error: unexpected argument '--ignored' found
261266
262-
tip: a similar argument exists: '---- --ignored'
267+
tip: a similar argument exists: '-- --ignored'
268+
tip: not much else to say
263269
264270
Usage: test [OPTIONS]
265271

0 commit comments

Comments
 (0)