Skip to content

Commit

Permalink
feat(parser): Add method to disable escape arg
Browse files Browse the repository at this point in the history
  • Loading branch information
emersonford committed Aug 8, 2022
1 parent 179faa6 commit 8f6226a
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/builder/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) enum AppSettings {
DisableHelpFlag,
DisableHelpSubcommand,
DisableVersionFlag,
DisableEscapeArg,
PropagateVersion,
Hidden,
HidePossibleValues,
Expand Down Expand Up @@ -101,6 +102,7 @@ bitflags! {
const INFER_LONG_ARGS = 1 << 43;
const IGNORE_ERRORS = 1 << 44;
const MULTICALL = 1 << 45;
const DISABLE_ESCAPE_ARG = 1 << 46;
const NO_OP = 0;
}
}
Expand Down Expand Up @@ -138,6 +140,8 @@ impl_settings! { AppSettings, AppFlags,
=> Flags::DISABLE_HELP_FLAG,
DisableVersionFlag
=> Flags::DISABLE_VERSION_FLAG,
DisableEscapeArg
=> Flags::DISABLE_ESCAPE_ARG,
PropagateVersion
=> Flags::PROPAGATE_VERSION,
HidePossibleValues
Expand Down
63 changes: 63 additions & 0 deletions src/builder/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,64 @@ impl<'help> Command<'help> {
}
}

/// Disables the `--` escape argument, always parsing it as if it were an argument value.
///
/// This can be used to ensure that when `--` is passed on the CLI, it is captured as part of
/// the final positional (e.g. with [`Command::trailing_var_arg`]).
///
/// Otherwise, when enabled, the `--` escape argument can be used to skip to the final
/// positional argument (see [`Command::allow_missing_positional`] and [`Arg::last`]).
///
/// NOTE: this cannot be used if the [`Command`] has an [`Arg`] with [`last(true)`] set.
///
/// # Examples
/// Suppose you want to capture *every* argument for a subcommand, e.g. to pass it off to an
/// external subcommand. Without disabling the `--` escape argument, the following would
/// exclude it from the final position arg as it is used to escape to the final positional arg.
/// ```rust
/// # use clap::{arg, Command, Arg};
/// let m = Command::new("myprog")
/// .trailing_var_arg(true)
/// .arg(arg!([opt] ...).allow_hyphen_values(true))
/// .get_matches_from(vec!["", "--", "--foo", "bar"]);
///
/// assert_eq!(
/// m.get_many::<String>("opt")
/// .unwrap()
/// .map(|s| s.as_str())
/// .collect::<Vec<_>>(),
/// vec!["--foo", "bar"]
/// );
/// ```
/// whereas
/// ```rust
/// # use clap::{arg, Command, Arg};
/// let m = Command::new("myprog")
/// .trailing_var_arg(true)
/// .disable_escape_arg(true)
/// .arg(arg!([opt] ...).allow_hyphen_values(true))
/// .get_matches_from(vec!["", "--", "--foo", "bar"]);
///
/// assert_eq!(
/// m.get_many::<String>("opt")
/// .unwrap()
/// .map(|s| s.as_str())
/// .collect::<Vec<_>>(),
/// vec!["--", "--foo", "bar"]
/// );
/// ```
/// captures it.
///
/// [`last(true)`]: Arg::last()
#[inline]
pub fn disable_escape_arg(self, yes: bool) -> Self {
if yes {
self.setting(AppSettings::DisableEscapeArg)
} else {
self.unset_setting(AppSettings::DisableEscapeArg)
}
}

/// Specifies that the final positional argument is a "VarArg" and that `clap` should not
/// attempt to parse any further args.
///
Expand Down Expand Up @@ -3599,6 +3657,11 @@ impl<'help> Command<'help> {
self.is_set(AppSettings::AllowNegativeNumbers)
}

/// Report whether [`Command::disable_escape_arg`] is set
pub fn is_disable_escape_arg_set(&self) -> bool {
self.is_set(AppSettings::DisableEscapeArg)
}

/// Report whether [`Command::trailing_var_arg`] is set
pub fn is_trailing_var_arg_set(&self) -> bool {
self.is_set(AppSettings::TrailingVarArg)
Expand Down
6 changes: 6 additions & 0 deletions src/builder/debug_asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ pub(crate) fn assert_app(cmd: &Command) {
cmd.get_name(),
arg.name
);
assert!(
!cmd.is_disable_escape_arg_set(),
"Command {}: Argument {} has last(true) which conflicts with `disable_escape_arg(true)`",
cmd.get_name(),
arg.name
);
}

assert!(
Expand Down
3 changes: 2 additions & 1 deletion src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
}

if arg_os.is_escape() {
if matches!(&parse_state, ParseState::Opt(opt) | ParseState::Pos(opt) if
if self.cmd.is_disable_escape_arg_set()
|| matches!(&parse_state, ParseState::Opt(opt) | ParseState::Pos(opt) if
self.cmd[opt].is_allow_hyphen_values_set())
{
// ParseResult::MaybeHyphenValue, do nothing
Expand Down
22 changes: 22 additions & 0 deletions tests/builder/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,28 @@ fn leading_double_hyphen_trailingvararg() {
);
}

#[test]
fn disable_escape_arg() {
let m = Command::new("extsubcmd")
.trailing_var_arg(true)
.disable_escape_arg(true)
.arg(arg!(<VAL>))
.arg(arg!([ARGS] ... "some args").allow_hyphen_values(true))
.try_get_matches_from(vec!["", "do-the-thing", "--", "--foo", "bar"])
.unwrap();

assert!(m.contains_id("VAL"));
assert!(m.contains_id("ARGS"));
assert_eq!(m.get_one::<String>("VAL").unwrap(), "do-the-thing");
assert_eq!(
m.get_many::<String>("ARGS")
.unwrap()
.map(|v| v.as_str())
.collect::<Vec<_>>(),
&["--", "--foo", "bar"]
);
}

#[test]
fn disable_help_subcommand() {
let result = Command::new("disablehelp")
Expand Down

0 comments on commit 8f6226a

Please sign in to comment.