fix!: Remove unstable-replace feature flag

This has been implemented for 3 years without much traction for
finishing it up.

The subcommand use case can be worked around by creating `Command`s that
just include the relevant logic, very similar to the default subcommand
examples in `git` / `git-derive`.

Using this for flags is covered by #4793.

Without `unstable-replace` being enabled, this still cut 5 KiB from
`cargo bloat --release --example git`.

Closes #2836
Closes #2011
This commit is contained in:
Ed Page 2023-03-28 00:26:18 -05:00
parent 2c19accd6c
commit 56fe5e0ec0
8 changed files with 3 additions and 170 deletions

View file

@ -92,7 +92,6 @@ unicode = ["clap_builder/unicode"] # Support for unicode characters in argument
string = ["clap_builder/string"] # Allow runtime generated strings string = ["clap_builder/string"] # Allow runtime generated strings
# In-work features # In-work features
unstable-replace = ["clap_builder/unstable-replace"]
unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"] unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"]
[lib] [lib]

View file

@ -15,8 +15,8 @@ MSRV?=1.64.0
_FEATURES = minimal default wasm full debug release _FEATURES = minimal default wasm full debug release
_FEATURES_minimal = --no-default-features --features "std" _FEATURES_minimal = --no-default-features --features "std"
_FEATURES_default = _FEATURES_default =
_FEATURES_wasm = --no-default-features --features "std help usage error-context suggestions" --features "deprecated derive cargo env unicode string unstable-replace" _FEATURES_wasm = --no-default-features --features "std help usage error-context suggestions" --features "deprecated derive cargo env unicode string"
_FEATURES_full = --features "deprecated derive cargo env unicode string unstable-replace wrap_help" _FEATURES_full = --features "deprecated derive cargo env unicode string wrap_help"
_FEATURES_next = ${_FEATURES_full} --features unstable-v5 _FEATURES_next = ${_FEATURES_full} --features unstable-v5
_FEATURES_debug = ${_FEATURES_full} --features debug --features clap_complete/debug _FEATURES_debug = ${_FEATURES_full} --features debug --features clap_complete/debug
_FEATURES_release = ${_FEATURES_full} --release _FEATURES_release = ${_FEATURES_full} --release

View file

@ -32,7 +32,7 @@ tag-name = "v{{version}}"
[features] [features]
default = ["std", "color", "help", "usage", "error-context", "suggestions"] default = ["std", "color", "help", "usage", "error-context", "suggestions"]
debug = ["dep:backtrace"] # Enables debug messages debug = ["dep:backtrace"] # Enables debug messages
unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-replace"] # for docs.rs unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string"] # for docs.rs
# Used in default # Used in default
std = [] # support for no_std in a backwards-compatible way std = [] # support for no_std in a backwards-compatible way
@ -51,7 +51,6 @@ unicode = ["dep:unicode-width", "dep:unicase"] # Support for unicode characters
string = [] # Allow runtime generated strings string = [] # Allow runtime generated strings
# In-work features # In-work features
unstable-replace = []
unstable-v5 = ["deprecated"] unstable-v5 = ["deprecated"]
[lib] [lib]

View file

@ -24,7 +24,6 @@ use crate::output::fmt::Stream;
use crate::output::{fmt::Colorizer, write_help, Usage}; use crate::output::{fmt::Colorizer, write_help, Usage};
use crate::parser::{ArgMatcher, ArgMatches, Parser}; use crate::parser::{ArgMatcher, ArgMatches, Parser};
use crate::util::ChildGraph; use crate::util::ChildGraph;
use crate::util::FlatMap;
use crate::util::{color::ColorChoice, Id}; use crate::util::{color::ColorChoice, Id};
use crate::{Error, INTERNAL_ERROR_MSG}; use crate::{Error, INTERNAL_ERROR_MSG};
@ -99,7 +98,6 @@ pub struct Command {
g_settings: AppFlags, g_settings: AppFlags,
args: MKeyMap, args: MKeyMap,
subcommands: Vec<Command>, subcommands: Vec<Command>,
replacers: FlatMap<Str, Vec<Str>>,
groups: Vec<ArgGroup>, groups: Vec<ArgGroup>,
current_help_heading: Option<Str>, current_help_heading: Option<Str>,
current_disp_ord: Option<usize>, current_disp_ord: Option<usize>,
@ -1924,129 +1922,6 @@ impl Command {
self self
} }
/// Replaces an argument or subcommand used on the CLI at runtime with other arguments or subcommands.
///
/// **Note:** This is gated behind [`unstable-replace`](https://github.com/clap-rs/clap/issues/2836)
///
/// When this method is used, `name` is removed from the CLI, and `target`
/// is inserted in its place. Parsing continues as if the user typed
/// `target` instead of `name`.
///
/// This can be used to create "shortcuts" for subcommands, or if a
/// particular argument has the semantic meaning of several other specific
/// arguments and values.
///
/// # Examples
///
/// We'll start with the "subcommand short" example. In this example, let's
/// assume we have a program with a subcommand `module` which can be invoked
/// via `cmd module`. Now let's also assume `module` also has a subcommand
/// called `install` which can be invoked `cmd module install`. If for some
/// reason users needed to be able to reach `cmd module install` via the
/// short-hand `cmd install`, we'd have several options.
///
/// We *could* create another sibling subcommand to `module` called
/// `install`, but then we would need to manage another subcommand and manually
/// dispatch to `cmd module install` handling code. This is error prone and
/// tedious.
///
/// We could instead use [`Command::replace`] so that, when the user types `cmd
/// install`, `clap` will replace `install` with `module install` which will
/// end up getting parsed as if the user typed the entire incantation.
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// let m = Command::new("cmd")
/// .subcommand(Command::new("module")
/// .subcommand(Command::new("install")))
/// .replace("install", &["module", "install"])
/// .get_matches_from(vec!["cmd", "install"]);
///
/// assert!(m.subcommand_matches("module").is_some());
/// assert!(m.subcommand_matches("module").unwrap().subcommand_matches("install").is_some());
/// ```
///
/// Now let's show an argument example!
///
/// Let's assume we have an application with two flags `--save-context` and
/// `--save-runtime`. But often users end up needing to do *both* at the
/// same time. We can add a third flag `--save-all` which semantically means
/// the same thing as `cmd --save-context --save-runtime`. To implement that,
/// we have several options.
///
/// We could create this third argument and manually check if that argument
/// and in our own consumer code handle the fact that both `--save-context`
/// and `--save-runtime` *should* have been used. But again this is error
/// prone and tedious. If we had code relying on checking `--save-context`
/// and we forgot to update that code to *also* check `--save-all` it'd mean
/// an error!
///
/// Luckily we can use [`Command::replace`] so that when the user types
/// `--save-all`, `clap` will replace that argument with `--save-context
/// --save-runtime`, and parsing will continue like normal. Now all our code
/// that was originally checking for things like `--save-context` doesn't
/// need to change!
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgAction};
/// let m = Command::new("cmd")
/// .arg(Arg::new("save-context")
/// .long("save-context")
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("save-runtime")
/// .long("save-runtime")
/// .action(ArgAction::SetTrue))
/// .replace("--save-all", &["--save-context", "--save-runtime"])
/// .get_matches_from(vec!["cmd", "--save-all"]);
///
/// assert!(m.get_flag("save-context"));
/// assert!(m.get_flag("save-runtime"));
/// ```
///
/// This can also be used with options, for example if our application with
/// `--save-*` above also had a `--format=TYPE` option. Let's say it
/// accepted `txt` or `json` values. However, when `--save-all` is used,
/// only `--format=json` is allowed, or valid. We could change the example
/// above to enforce this:
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgAction};
/// let m = Command::new("cmd")
/// .arg(Arg::new("save-context")
/// .long("save-context")
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("save-runtime")
/// .long("save-runtime")
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("format")
/// .long("format")
/// .action(ArgAction::Set)
/// .value_parser(["txt", "json"]))
/// .replace("--save-all", &["--save-context", "--save-runtime", "--format=json"])
/// .get_matches_from(vec!["cmd", "--save-all"]);
///
/// assert!(m.get_flag("save-context"));
/// assert!(m.get_flag("save-runtime"));
/// assert_eq!(m.get_one::<String>("format").unwrap(), "json");
/// ```
///
/// [`Command::replace`]: Command::replace()
#[inline]
#[cfg(feature = "unstable-replace")]
#[must_use]
pub fn replace(
mut self,
name: impl Into<Str>,
target: impl IntoIterator<Item = impl Into<Str>>,
) -> Self {
self.replacers
.insert(name.into(), target.into_iter().map(Into::into).collect());
self
}
/// Exit gracefully if no arguments are present (e.g. `$ myprog`). /// Exit gracefully if no arguments are present (e.g. `$ myprog`).
/// ///
/// **NOTE:** [`subcommands`] count as arguments /// **NOTE:** [`subcommands`] count as arguments
@ -3853,10 +3728,6 @@ impl Command {
self.max_w self.max_w
} }
pub(crate) fn get_replacement(&self, key: &str) -> Option<&[Str]> {
self.replacers.get(key).map(|v| v.as_slice())
}
pub(crate) fn get_keymap(&self) -> &MKeyMap { pub(crate) fn get_keymap(&self) -> &MKeyMap {
&self.args &self.args
} }
@ -4749,7 +4620,6 @@ impl Default for Command {
g_settings: Default::default(), g_settings: Default::default(),
args: Default::default(), args: Default::default(),
subcommands: Default::default(), subcommands: Default::default(),
replacers: Default::default(),
groups: Default::default(), groups: Default::default(),
current_help_heading: Default::default(), current_help_heading: Default::default(),
current_disp_ord: Some(0), current_disp_ord: Some(0),

View file

@ -76,20 +76,6 @@ impl<'cmd> Parser<'cmd> {
let contains_last = self.cmd.get_arguments().any(|x| x.is_last_set()); let contains_last = self.cmd.get_arguments().any(|x| x.is_last_set());
while let Some(arg_os) = raw_args.next(&mut args_cursor) { while let Some(arg_os) = raw_args.next(&mut args_cursor) {
// Recover the replaced items if any.
if let Some(replaced_items) = arg_os
.to_value()
.ok()
.and_then(|a| self.cmd.get_replacement(a))
{
debug!(
"Parser::get_matches_with: found replacer: {:?}, target: {:?}",
arg_os, replaced_items
);
raw_args.insert(&args_cursor, replaced_items);
continue;
}
debug!( debug!(
"Parser::get_matches_with: Begin parsing '{:?}'", "Parser::get_matches_with: Begin parsing '{:?}'",
arg_os.to_value_os(), arg_os.to_value_os(),

View file

@ -25,5 +25,4 @@
//! //!
//! **Warning:** These may contain breaking changes between minor releases. //! **Warning:** These may contain breaking changes between minor releases.
//! //!
//! * **unstable-replace**: Enable [`Command::replace`](https://github.com/clap-rs/clap/issues/2836)
//! * **unstable-v5**: Preview features which will be stable on the v5.0 release //! * **unstable-v5**: Preview features which will be stable on the v5.0 release

View file

@ -225,24 +225,6 @@ Options:
utils::assert_output(cmd, "clap-test --help", INVISIBLE_ALIAS_HELP, false); utils::assert_output(cmd, "clap-test --help", INVISIBLE_ALIAS_HELP, false);
} }
#[test]
#[cfg(feature = "unstable-replace")]
fn replace() {
let m = Command::new("prog")
.subcommand(
Command::new("module").subcommand(Command::new("install").about("Install module")),
)
.replace("install", ["module", "install"])
.try_get_matches_from(vec!["prog", "install"])
.unwrap();
assert_eq!(m.subcommand_name(), Some("module"));
assert_eq!(
m.subcommand_matches("module").unwrap().subcommand_name(),
Some("install")
);
}
#[test] #[test]
fn issue_1031_args_with_same_name() { fn issue_1031_args_with_same_name() {
let res = Command::new("prog") let res = Command::new("prog")

View file

@ -25,8 +25,6 @@ fn ui_tests() {
"string", "string",
#[cfg(feature = "wrap_help")] #[cfg(feature = "wrap_help")]
"wrap_help", "wrap_help",
#[cfg(feature = "unstable-replace")]
"unstable-replace",
] ]
.join(" "); .join(" ");
t.register_bins(trycmd::cargo::compile_examples(["--features", &features]).unwrap()); t.register_bins(trycmd::cargo::compile_examples(["--features", &features]).unwrap());