Skip to content

Commit

Permalink
Merge pull request #4792 from epage/defer
Browse files Browse the repository at this point in the history
feat: Allow deferred initialization of subcommands
  • Loading branch information
epage committed Jun 9, 2023
2 parents 475e254 + d10938d commit 13534b6
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 26 deletions.
30 changes: 30 additions & 0 deletions clap_builder/src/builder/command.rs
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/builder/propagate_globals.rs
Expand Up @@ -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 {
Expand Down
60 changes: 36 additions & 24 deletions tests/builder/subcommands.rs
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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"])
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion tests/builder/version.rs
Expand Up @@ -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]
Expand Down

0 comments on commit 13534b6

Please sign in to comment.