Skip to content

Commit

Permalink
feat(builder): Allow injecting known unknowns
Browse files Browse the repository at this point in the history
Fixes #4706
  • Loading branch information
epage committed Aug 16, 2023
1 parent 063b153 commit 8413c15
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 1 deletion.
1 change: 1 addition & 0 deletions clap_builder/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub use value_parser::RangedU64ValueParser;
pub use value_parser::StringValueParser;
pub use value_parser::TryMapValueParser;
pub use value_parser::TypedValueParser;
pub use value_parser::UnknownArgumentValueParser;
pub use value_parser::ValueParser;
pub use value_parser::ValueParserFactory;
pub use value_parser::_AnonymousValueParser;
Expand Down
69 changes: 69 additions & 0 deletions clap_builder/src/builder/value_parser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::convert::TryInto;
use std::ops::RangeBounds;

use crate::builder::Str;
use crate::util::AnyValue;
use crate::util::AnyValueId;

Expand Down Expand Up @@ -2086,6 +2087,74 @@ where
}
}

/// When encountered, report [ErrorKind::UnknownArgument][crate::error::ErrorKind::UnknownArgument]
///
/// Useful to help users migrate, either from old versions or similar tools.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .args([
/// Arg::new("current-dir")
/// .short('C'),
/// Arg::new("current-dir-unknown")
/// .long("cwd")
/// .aliases(["current-dir", "directory", "working-directory", "root"])
/// .value_parser(clap::builder::UnknownArgumentValueParser::suggest("-C"))
/// .hide(true),
/// ]);
///
/// // Use a supported version of the argument
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "-C", ".."]).unwrap();
/// assert!(matches.contains_id("current-dir"));
/// assert_eq!(
/// matches.get_many::<String>("current-dir").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
/// vec![".."]
/// );
///
/// // Use one of the invalid versions
/// let err = cmd.try_get_matches_from(["mycmd", "--cwd", ".."]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
/// ```
#[derive(Clone, Debug)]
pub struct UnknownArgumentValueParser {
arg: Str,
}

impl UnknownArgumentValueParser {
/// Suggest an alternative argument
pub fn suggest(arg: impl Into<Str>) -> Self {
Self { arg: arg.into() }
}
}

impl TypedValueParser for UnknownArgumentValueParser {
type Value = String;

fn parse_ref(
&self,
cmd: &crate::Command,
arg: Option<&crate::Arg>,
_value: &std::ffi::OsStr,
) -> Result<Self::Value, crate::Error> {
let arg = match arg {
Some(arg) => arg.to_string(),
None => "..".to_owned(),
};
Err(crate::Error::unknown_argument(
cmd,
arg,
Some((self.arg.as_str().to_owned(), None)),
false,
crate::output::Usage::new(cmd).create_usage_with_title(&[]),
))
}
}

/// Register a type with [value_parser!][crate::value_parser!]
///
/// # Example
Expand Down
60 changes: 59 additions & 1 deletion tests/builder/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::utils;

use clap::{arg, error::Error, error::ErrorKind, value_parser, Arg, Command};
use clap::{arg, builder::ArgAction, error::Error, error::ErrorKind, value_parser, Arg, Command};

#[track_caller]
fn assert_error<F: clap::error::ErrorFormatter>(
Expand Down Expand Up @@ -209,3 +209,61 @@ For more information, try '--help'.
";
assert_error(err, expected_kind, MESSAGE, true);
}

#[test]
#[cfg(feature = "error-context")]
#[cfg(feature = "suggestions")]
fn unknown_argument_option() {
let cmd = Command::new("test").args([
Arg::new("current-dir").short('C'),
Arg::new("current-dir-unknown")
.long("cwd")
.aliases(["current-dir", "directory", "working-directory", "root"])
.value_parser(clap::builder::UnknownArgumentValueParser::suggest("-C"))
.hide(true),
]);
let res = cmd.try_get_matches_from(["test", "--cwd", ".."]);
assert!(res.is_err());
let err = res.unwrap_err();
let expected_kind = ErrorKind::UnknownArgument;
static MESSAGE: &str = "\
error: unexpected argument '--cwd <current-dir-unknown>' found
tip: a similar argument exists: '---C'
Usage: test [OPTIONS]
For more information, try '--help'.
";
assert_error(err, expected_kind, MESSAGE, true);
}

#[test]
#[cfg(feature = "error-context")]
#[cfg(feature = "suggestions")]
fn unknown_argument_flag() {
let cmd = Command::new("test").args([
Arg::new("ignore-rust-version").long("ignore-rust-version"),
Arg::new("libtest-ignore")
.long("ignored")
.action(ArgAction::SetTrue)
.value_parser(clap::builder::UnknownArgumentValueParser::suggest(
"-- --ignored",
))
.hide(true),
]);
let res = cmd.try_get_matches_from(["test", "--ignored"]);
assert!(res.is_err());
let err = res.unwrap_err();
let expected_kind = ErrorKind::UnknownArgument;
static MESSAGE: &str = "\
error: unexpected argument '--ignored' found
tip: a similar argument exists: '---- --ignored'
Usage: test [OPTIONS]
For more information, try '--help'.
";
assert_error(err, expected_kind, MESSAGE, true);
}

0 comments on commit 8413c15

Please sign in to comment.