mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
feat: Allow deferred initialization of subcommands
This is mostly targeted at reducing startup time for no-op commands within *very* large applications, like deno (see #4774). This comes at the cost of 1.1 KiB of binary size
This commit is contained in:
parent
475e254d25
commit
d10938dd33
4 changed files with 68 additions and 26 deletions
|
@ -105,6 +105,7 @@ pub struct Command {
|
|||
subcommand_heading: Option<Str>,
|
||||
external_value_parser: Option<super::ValueParser>,
|
||||
long_help_exists: bool,
|
||||
deferred: Option<fn(Command) -> Command>,
|
||||
app_ext: Extensions,
|
||||
}
|
||||
|
||||
|
@ -428,6 +429,30 @@ impl Command {
|
|||
self
|
||||
}
|
||||
|
||||
/// Delay initialization for parts of the `Command`
|
||||
///
|
||||
/// This is useful for large applications to delay definitions of subcommands until they are
|
||||
/// being invoked.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap_builder as clap;
|
||||
/// # use clap::{Command, arg};
|
||||
/// Command::new("myprog")
|
||||
/// .subcommand(Command::new("config")
|
||||
/// .about("Controls configuration features")
|
||||
/// .defer(|cmd| {
|
||||
/// cmd.arg(arg!(<config> "Required configuration file to use"))
|
||||
/// })
|
||||
/// )
|
||||
/// # ;
|
||||
/// ```
|
||||
pub fn defer(mut self, deferred: fn(Command) -> Command) -> Self {
|
||||
self.deferred = Some(deferred);
|
||||
self
|
||||
}
|
||||
|
||||
/// Catch problems earlier in the development cycle.
|
||||
///
|
||||
/// Most error states are handled as asserts under the assumption they are programming mistake
|
||||
|
@ -3824,6 +3849,10 @@ impl Command {
|
|||
pub(crate) fn _build_self(&mut self, expand_help_tree: bool) {
|
||||
debug!("Command::_build: name={:?}", self.get_name());
|
||||
if !self.settings.is_set(AppSettings::Built) {
|
||||
if let Some(deferred) = self.deferred.take() {
|
||||
*self = (deferred)(std::mem::take(self));
|
||||
}
|
||||
|
||||
// Make sure all the globally set flags apply to us as well
|
||||
self.settings = self.settings | self.g_settings;
|
||||
|
||||
|
@ -4652,6 +4681,7 @@ impl Default for Command {
|
|||
subcommand_heading: Default::default(),
|
||||
external_value_parser: Default::default(),
|
||||
long_help_exists: false,
|
||||
deferred: None,
|
||||
app_ext: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ fn get_app() -> Command {
|
|||
.global(true)
|
||||
.action(ArgAction::Count),
|
||||
)
|
||||
.subcommand(Command::new("outer").subcommand(Command::new("inner")))
|
||||
.subcommand(Command::new("outer").defer(|cmd| cmd.subcommand(Command::new("inner"))))
|
||||
}
|
||||
|
||||
fn get_matches(cmd: Command, argv: &'static str) -> ArgMatches {
|
||||
|
|
|
@ -5,15 +5,15 @@ use super::utils;
|
|||
#[test]
|
||||
fn subcommand() {
|
||||
let m = Command::new("test")
|
||||
.subcommand(
|
||||
Command::new("some").arg(
|
||||
.subcommand(Command::new("some").defer(|cmd| {
|
||||
cmd.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.action(ArgAction::Set)
|
||||
.help("testing testing"),
|
||||
),
|
||||
)
|
||||
)
|
||||
}))
|
||||
.arg(Arg::new("other").long("other"))
|
||||
.try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
|
||||
.unwrap();
|
||||
|
@ -30,15 +30,15 @@ fn subcommand() {
|
|||
#[test]
|
||||
fn subcommand_none_given() {
|
||||
let m = Command::new("test")
|
||||
.subcommand(
|
||||
Command::new("some").arg(
|
||||
.subcommand(Command::new("some").defer(|cmd| {
|
||||
cmd.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.action(ArgAction::Set)
|
||||
.help("testing testing"),
|
||||
),
|
||||
)
|
||||
)
|
||||
}))
|
||||
.arg(Arg::new("other").long("other"))
|
||||
.try_get_matches_from(vec![""])
|
||||
.unwrap();
|
||||
|
@ -50,14 +50,16 @@ fn subcommand_none_given() {
|
|||
fn subcommand_multiple() {
|
||||
let m = Command::new("test")
|
||||
.subcommands(vec![
|
||||
Command::new("some").arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.action(ArgAction::Set)
|
||||
.help("testing testing"),
|
||||
),
|
||||
Command::new("add").arg(Arg::new("roster").short('r')),
|
||||
Command::new("some").defer(|cmd| {
|
||||
cmd.arg(
|
||||
Arg::new("test")
|
||||
.short('t')
|
||||
.long("test")
|
||||
.action(ArgAction::Set)
|
||||
.help("testing testing"),
|
||||
)
|
||||
}),
|
||||
Command::new("add").defer(|cmd| cmd.arg(Arg::new("roster").short('r'))),
|
||||
])
|
||||
.arg(Arg::new("other").long("other"))
|
||||
.try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
|
||||
|
@ -148,8 +150,9 @@ Usage: dym [COMMAND]
|
|||
For more information, try '--help'.
|
||||
";
|
||||
|
||||
let cmd = Command::new("dym")
|
||||
.subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests")));
|
||||
let cmd = Command::new("dym").subcommand(
|
||||
Command::new("subcmd").defer(|cmd| cmd.arg(arg!(-s --subcmdarg <subcmdarg> "tests"))),
|
||||
);
|
||||
|
||||
utils::assert_output(cmd, "dym --subcmarg subcmd", EXPECTED, true);
|
||||
}
|
||||
|
@ -166,8 +169,9 @@ Usage: dym [COMMAND]
|
|||
For more information, try '--help'.
|
||||
";
|
||||
|
||||
let cmd = Command::new("dym")
|
||||
.subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests")));
|
||||
let cmd = Command::new("dym").subcommand(
|
||||
Command::new("subcmd").defer(|cmd| cmd.arg(arg!(-s --subcmdarg <subcmdarg> "tests"))),
|
||||
);
|
||||
|
||||
utils::assert_output(cmd, "dym --subcmarg foo", EXPECTED, true);
|
||||
}
|
||||
|
@ -427,7 +431,7 @@ fn busybox_like_multicall() {
|
|||
}
|
||||
let cmd = Command::new("busybox")
|
||||
.multicall(true)
|
||||
.subcommand(Command::new("busybox").subcommands(applet_commands()))
|
||||
.subcommand(Command::new("busybox").defer(|cmd| cmd.subcommands(applet_commands())))
|
||||
.subcommands(applet_commands());
|
||||
|
||||
let m = cmd
|
||||
|
@ -553,7 +557,9 @@ Options:
|
|||
.version("1.0.0")
|
||||
.propagate_version(true)
|
||||
.multicall(true)
|
||||
.subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
|
||||
.subcommand(Command::new("foo").defer(|cmd| {
|
||||
cmd.subcommand(Command::new("bar").defer(|cmd| cmd.arg(Arg::new("value"))))
|
||||
}));
|
||||
utils::assert_output(cmd, "foo bar --help", EXPECTED, false);
|
||||
}
|
||||
|
||||
|
@ -573,7 +579,10 @@ Options:
|
|||
.version("1.0.0")
|
||||
.propagate_version(true)
|
||||
.multicall(true)
|
||||
.subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
|
||||
.subcommand(
|
||||
Command::new("foo")
|
||||
.defer(|cmd| cmd.subcommand(Command::new("bar").arg(Arg::new("value")))),
|
||||
);
|
||||
utils::assert_output(cmd, "help foo bar", EXPECTED, false);
|
||||
}
|
||||
|
||||
|
@ -593,7 +602,10 @@ Options:
|
|||
.version("1.0.0")
|
||||
.propagate_version(true)
|
||||
.multicall(true)
|
||||
.subcommand(Command::new("foo").subcommand(Command::new("bar").arg(Arg::new("value"))));
|
||||
.subcommand(
|
||||
Command::new("foo")
|
||||
.defer(|cmd| cmd.subcommand(Command::new("bar").arg(Arg::new("value")))),
|
||||
);
|
||||
cmd.build();
|
||||
let subcmd = cmd.find_subcommand_mut("foo").unwrap();
|
||||
let subcmd = subcmd.find_subcommand_mut("bar").unwrap();
|
||||
|
|
|
@ -19,7 +19,7 @@ fn with_both() -> Command {
|
|||
}
|
||||
|
||||
fn with_subcommand() -> Command {
|
||||
with_version().subcommand(Command::new("bar").subcommand(Command::new("baz")))
|
||||
with_version().subcommand(Command::new("bar").defer(|cmd| cmd.subcommand(Command::new("baz"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue