Merge pull request #4792 from epage/defer

feat: Allow deferred initialization of subcommands
This commit is contained in:
Ed Page 2023-06-09 09:33:09 -05:00 committed by GitHub
commit 13534b67d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 26 deletions

View file

@ -105,6 +105,7 @@ pub struct Command {
subcommand_heading: Option<Str>, subcommand_heading: Option<Str>,
external_value_parser: Option<super::ValueParser>, external_value_parser: Option<super::ValueParser>,
long_help_exists: bool, long_help_exists: bool,
deferred: Option<fn(Command) -> Command>,
app_ext: Extensions, app_ext: Extensions,
} }
@ -428,6 +429,30 @@ impl Command {
self 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. /// Catch problems earlier in the development cycle.
/// ///
/// Most error states are handled as asserts under the assumption they are programming mistake /// 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) { pub(crate) fn _build_self(&mut self, expand_help_tree: bool) {
debug!("Command::_build: name={:?}", self.get_name()); debug!("Command::_build: name={:?}", self.get_name());
if !self.settings.is_set(AppSettings::Built) { 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 // Make sure all the globally set flags apply to us as well
self.settings = self.settings | self.g_settings; self.settings = self.settings | self.g_settings;
@ -4652,6 +4681,7 @@ impl Default for Command {
subcommand_heading: Default::default(), subcommand_heading: Default::default(),
external_value_parser: Default::default(), external_value_parser: Default::default(),
long_help_exists: false, long_help_exists: false,
deferred: None,
app_ext: Default::default(), app_ext: Default::default(),
} }
} }

View file

@ -17,7 +17,7 @@ fn get_app() -> Command {
.global(true) .global(true)
.action(ArgAction::Count), .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 { fn get_matches(cmd: Command, argv: &'static str) -> ArgMatches {

View file

@ -5,15 +5,15 @@ use super::utils;
#[test] #[test]
fn subcommand() { fn subcommand() {
let m = Command::new("test") let m = Command::new("test")
.subcommand( .subcommand(Command::new("some").defer(|cmd| {
Command::new("some").arg( cmd.arg(
Arg::new("test") Arg::new("test")
.short('t') .short('t')
.long("test") .long("test")
.action(ArgAction::Set) .action(ArgAction::Set)
.help("testing testing"), .help("testing testing"),
), )
) }))
.arg(Arg::new("other").long("other")) .arg(Arg::new("other").long("other"))
.try_get_matches_from(vec!["myprog", "some", "--test", "testing"]) .try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
.unwrap(); .unwrap();
@ -30,15 +30,15 @@ fn subcommand() {
#[test] #[test]
fn subcommand_none_given() { fn subcommand_none_given() {
let m = Command::new("test") let m = Command::new("test")
.subcommand( .subcommand(Command::new("some").defer(|cmd| {
Command::new("some").arg( cmd.arg(
Arg::new("test") Arg::new("test")
.short('t') .short('t')
.long("test") .long("test")
.action(ArgAction::Set) .action(ArgAction::Set)
.help("testing testing"), .help("testing testing"),
), )
) }))
.arg(Arg::new("other").long("other")) .arg(Arg::new("other").long("other"))
.try_get_matches_from(vec![""]) .try_get_matches_from(vec![""])
.unwrap(); .unwrap();
@ -50,14 +50,16 @@ fn subcommand_none_given() {
fn subcommand_multiple() { fn subcommand_multiple() {
let m = Command::new("test") let m = Command::new("test")
.subcommands(vec![ .subcommands(vec![
Command::new("some").arg( Command::new("some").defer(|cmd| {
Arg::new("test") cmd.arg(
.short('t') Arg::new("test")
.long("test") .short('t')
.action(ArgAction::Set) .long("test")
.help("testing testing"), .action(ArgAction::Set)
), .help("testing testing"),
Command::new("add").arg(Arg::new("roster").short('r')), )
}),
Command::new("add").defer(|cmd| cmd.arg(Arg::new("roster").short('r'))),
]) ])
.arg(Arg::new("other").long("other")) .arg(Arg::new("other").long("other"))
.try_get_matches_from(vec!["myprog", "some", "--test", "testing"]) .try_get_matches_from(vec!["myprog", "some", "--test", "testing"])
@ -148,8 +150,9 @@ Usage: dym [COMMAND]
For more information, try '--help'. For more information, try '--help'.
"; ";
let cmd = Command::new("dym") let cmd = Command::new("dym").subcommand(
.subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests"))); Command::new("subcmd").defer(|cmd| cmd.arg(arg!(-s --subcmdarg <subcmdarg> "tests"))),
);
utils::assert_output(cmd, "dym --subcmarg subcmd", EXPECTED, true); utils::assert_output(cmd, "dym --subcmarg subcmd", EXPECTED, true);
} }
@ -166,8 +169,9 @@ Usage: dym [COMMAND]
For more information, try '--help'. For more information, try '--help'.
"; ";
let cmd = Command::new("dym") let cmd = Command::new("dym").subcommand(
.subcommand(Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests"))); Command::new("subcmd").defer(|cmd| cmd.arg(arg!(-s --subcmdarg <subcmdarg> "tests"))),
);
utils::assert_output(cmd, "dym --subcmarg foo", EXPECTED, true); utils::assert_output(cmd, "dym --subcmarg foo", EXPECTED, true);
} }
@ -427,7 +431,7 @@ fn busybox_like_multicall() {
} }
let cmd = Command::new("busybox") let cmd = Command::new("busybox")
.multicall(true) .multicall(true)
.subcommand(Command::new("busybox").subcommands(applet_commands())) .subcommand(Command::new("busybox").defer(|cmd| cmd.subcommands(applet_commands())))
.subcommands(applet_commands()); .subcommands(applet_commands());
let m = cmd let m = cmd
@ -553,7 +557,9 @@ Options:
.version("1.0.0") .version("1.0.0")
.propagate_version(true) .propagate_version(true)
.multicall(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); utils::assert_output(cmd, "foo bar --help", EXPECTED, false);
} }
@ -573,7 +579,10 @@ Options:
.version("1.0.0") .version("1.0.0")
.propagate_version(true) .propagate_version(true)
.multicall(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); utils::assert_output(cmd, "help foo bar", EXPECTED, false);
} }
@ -593,7 +602,10 @@ Options:
.version("1.0.0") .version("1.0.0")
.propagate_version(true) .propagate_version(true)
.multicall(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(); cmd.build();
let subcmd = cmd.find_subcommand_mut("foo").unwrap(); let subcmd = cmd.find_subcommand_mut("foo").unwrap();
let subcmd = subcmd.find_subcommand_mut("bar").unwrap(); let subcmd = subcmd.find_subcommand_mut("bar").unwrap();

View file

@ -19,7 +19,7 @@ fn with_both() -> Command {
} }
fn with_subcommand() -> 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] #[test]