From f14db03eec5e879c0cf1740b8fc808bdc1f448c7 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 14 Sep 2021 21:38:41 +0100 Subject: [PATCH 01/13] Add Multicall setting If the AppSettings::Multicall setting is given then argv0 is parsed as the first subcommand argument or parsed as normal with any other name. --- clap_derive/examples/hostname.rs | 43 ++++++++++++++++++ examples/24_multicall_busybox.rs | 77 ++++++++++++++++++++++++++++++++ src/build/app/debug_asserts.rs | 18 +++++++- src/build/app/mod.rs | 47 +++++++++++++++++++ src/build/app/settings.rs | 59 ++++++++++++++++++++++++ tests/subcommands.rs | 59 ++++++++++++++++++++++++ 6 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 clap_derive/examples/hostname.rs create mode 100644 examples/24_multicall_busybox.rs diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs new file mode 100644 index 00000000000..928b1d96f5b --- /dev/null +++ b/clap_derive/examples/hostname.rs @@ -0,0 +1,43 @@ +//! Example of `hostname`-style multicall program +//! +//! `hostname` is a command to just output the configured hostname. +//! It may also render other network-address related information +//! if linked to under a different name +//! e.g. `dnsdomainname` for the domain name part of the FQDN. +//! +//! `hostname`-style differs from `busybox`-style in that the applets +//! should not ever be available as a subcommand +//! and the name of the executable is the same as an applet. +//! +//! i.e. `hostname` doesn't have a `dnsdomainname` subcommand, +//! `dnsdomainname` must only be run via a soft or hard link to the executable. +//! +//! This behaviour is opted-into by naming an applet subcommand +//! the same as the program name. +//! +//! This is desirable when the executable has a primary purpose +//! rather than being a collection of varied applets, +//! so it is appropriate to name the executable after its purpose, +//! but there is other related functionality that would be convenient to provide +//! and it is convenient for the code to implement it to be in the same executable. +//! +//! This example omits the implementation of displaying address config. + +use clap::Clap; + +#[derive(Clap, Debug)] +#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] +enum Args { + /// Show the configured hostname + Hostname, + /// Show the domain name part of the configured hostname + #[clap(about = "show domain")] + DNSDomainName, +} + +fn main() { + match Args::parse() { + Args::Hostname => println!("www"), + Args::DNSDomainName => println!("example.com"), + } +} diff --git a/examples/24_multicall_busybox.rs b/examples/24_multicall_busybox.rs new file mode 100644 index 00000000000..e40592adf1e --- /dev/null +++ b/examples/24_multicall_busybox.rs @@ -0,0 +1,77 @@ +//! Example of a `busybox-style` multicall program +//! +//! `busybox` is a single executable that contains a variety of applets +//! for a fully functional Linux userland. +//! +//! `busybox`-style differs from `hostname`-style in that there is a launcher program +//! which the applets are available as subcommands. +//! i.e. you can use the `cat` command either as a link named `cat` +//! or as `busybox cat`. +//! +//! This behaviour is opted-into by not naming an applet the same as the main program. +//! +//! This is desirable when the launcher program has additional options +//! or it is useful to run the applet without installing a symlink +//! e.g. for testing purposes, or there may already be a command of that name installed. +//! +//! This example omits every command except true and false, +//! which are the most trivial to implement, +//! but includes the `--install` option as an example of why it can be useful +//! for the main program to take arguments that aren't applet subcommands. + +use std::{ + env::args_os, + fs::{hard_link, read_link}, + path::{Path, PathBuf}, + process::exit, +}; + +use clap::{App, AppSettings, Arg}; + +fn main() { + let mut app = App::new(env!("CARGO_CRATE_NAME")) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::Multicall) + .arg( + Arg::new("install") + .long("install") + .about("Install hardlinks for all subcommands in path") + .exclusive(true) + .takes_value(true) + .default_missing_value("/usr/local/bin") + .use_delimiter(false), + ) + .subcommand(App::new("true").about("does nothing successfully")) + .subcommand(App::new("false").about("does nothing unsuccessfully")); + let matches = app.get_matches_mut(); + if matches.occurrences_of("install") > 0 { + let exec_path = read_link("/proc/self/exe") + .ok() + .or_else(|| { + args_os().next().and_then(|s| { + let p: &Path = s.as_ref(); + if p.is_absolute() { + Some(PathBuf::from(s)) + } else { + None + } + }) + }) + .expect( + "Should be able to read /proc/self/exe or argv0 should be present and absolute", + ); + let mut dest = PathBuf::from(matches.value_of("install").unwrap()); + for applet in app.get_subcommands().map(|c| c.get_name()) { + dest.push(applet); + hard_link(&exec_path, &dest).expect("Should be able to hardlink"); + dest.pop(); + } + exit(0); + } + + exit(match matches.subcommand_name() { + Some("true") => 0, + Some("false") => 1, + _ => 127, + }) +} diff --git a/src/build/app/debug_asserts.rs b/src/build/app/debug_asserts.rs index b451f66af9f..a04c93c381d 100644 --- a/src/build/app/debug_asserts.rs +++ b/src/build/app/debug_asserts.rs @@ -345,8 +345,24 @@ fn assert_app_flags(app: &App) { panic!("{}", s) } } - } + }; + ($a:ident conflicts $($b:ident)|+) => { + if app.is_set($a) { + let mut s = String::new(); + + $( + if app.is_set($b) { + s.push_str(&format!("\nAppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a))); + } + )+ + + if !s.is_empty() { + panic!("{}", s) + } + } + }; } checker!(AllowInvalidUtf8ForExternalSubcommands requires AllowExternalSubcommands); + checker!(Multicall conflicts NoBinaryName); } diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 9409d7327bb..c5987ef37ee 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1950,6 +1950,7 @@ impl<'help> App<'help> { /// .get_matches(); /// ``` /// [`env::args_os`]: std::env::args_os() + /// [`App::try_get_matches_from_mut`]: App::try_get_matches_from_mut() #[inline] pub fn get_matches(self) -> ArgMatches { self.get_matches_from(&mut env::args_os()) @@ -2132,6 +2133,52 @@ impl<'help> App<'help> { T: Into + Clone, { let mut it = Input::from(itr.into_iter()); + + if self.settings.is_set(AppSettings::Multicall) { + if let Some((argv0, _)) = it.next() { + let argv0 = Path::new(&argv0); + if let Some(command) = argv0.file_name().and_then(|f| f.to_str()) { + // Stop borrowing command so we can get another mut ref to it. + let command = command.to_owned(); + debug!( + "App::try_get_matches_from_mut: Parsed command {} from argv", + command + ); + + let subcommand = self + .subcommands + .iter_mut() + .find(|subcommand| subcommand.aliases_to(&command)); + debug!( + "App::try_get_matches_from_mut: Matched subcommand {:?}", + subcommand + ); + + match subcommand { + None if command == self.name => { + debug!("App::try_get_matches_from_mut: no existing applet but matches name"); + debug!( + "App::try_get_matches_from_mut: Setting bin_name to command name" + ); + self.bin_name.get_or_insert(command); + debug!( + "App::try_get_matches_from_mut: Continuing with top-level parser." + ); + return self._do_parse(&mut it); + } + _ => { + debug!("App::try_get_matches_from_mut: existing applet or no program name"); + debug!("App::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it"); + it.insert(&[&command]); + debug!("App::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name"); + self.name.clear(); + self.bin_name = None; + return self._do_parse(&mut it); + } + }; + } + } + }; // Get the name of the program (argument 1 of env::args()) and determine the // actual file // that was used to execute the program. This is because a program called diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index c462626d708..78c3f5119ac 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -50,6 +50,7 @@ bitflags! { const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 42; const INFER_LONG_ARGS = 1 << 43; const IGNORE_ERRORS = 1 << 44; + const MULTICALL = 1 << 45; } } @@ -106,6 +107,8 @@ impl_settings! { AppSettings, AppFlags, => Flags::HELP_REQUIRED, Hidden("hidden") => Flags::HIDDEN, + Multicall("multicall") + => Flags::MULTICALL, NoAutoHelp("noautohelp") => Flags::NO_AUTO_HELP, NoAutoVersion("noautoversion") @@ -646,6 +649,61 @@ pub enum AppSettings { /// [`subcommands`]: crate::App::subcommand() DeriveDisplayOrder, + /// Parse the bin name (argv[0]) as a subcommand + /// + /// Busybox is a common example of a "multicall" executable + /// where the command `cat` is a link to the `busybox` bin + /// and, when `cat` is run, `busybox` acts is if you ran `busybox cat`. + /// + /// This adds a small performance penalty to startup compared to subcommands + /// for comparing argv[0] against applet names. + /// + /// Multicall can't be used with [`NoBinaryName`] since they interpret + /// the command name in incompatible ways. + /// + /// # Examples + /// + /// Multicall applets are defined as subcommands + /// to an app which has the Multicall setting enabled. + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// let mut app = App::new("busybox") + /// .setting(AppSettings::Multicall) + /// .subcommand(App::new("true")) + /// .subcommand(App::new("false")); + /// // When called from the executable's canonical name + /// // its applets can be matched as subcommands. + /// let m = app.try_get_matches_from_mut(&["busybox", "true"]).unwrap(); + /// assert_eq!(m.subcommand_name(), Some("true")); + /// // When called from a link named after an applet that applet is matched. + /// let m = app.get_matches_from(&["true"]); + /// assert_eq!(m.subcommand_name(), Some("true")); + /// ``` + /// + /// If the name of an applet is the name of the command, + /// the applet's name is matched + /// and subcommands may not be provided to select another applet. + /// + /// ```rust + /// # use clap::{App, AppSettings, ErrorKind}; + /// let mut app = App::new("hostname") + /// .setting(AppSettings::Multicall) + /// .subcommand(App::new("hostname")) + /// .subcommand(App::new("dnsdomainname")); + /// let m = app.try_get_matches_from_mut(&["hostname", "dnsdomainname"]); + /// assert!(m.is_err()); + /// assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); + /// let m = app.get_matches_from(&["hostname"]); + /// assert_eq!(m.subcommand_name(), Some("hostname")); + /// ``` + /// + /// [`subcommands`]: crate::App::subcommand() + /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html + /// [`NoBinaryName`]: crate::AppSettings::NoBinaryName + /// [`try_get_matches_from_mut`]: crate::App::try_get_matches_from_mut() + Multicall, + /// Specifies to use the version of the current command for all [`subcommands`]. /// /// Defaults to `false`; subcommands have independent version strings from their parents. @@ -811,6 +869,7 @@ pub enum AppSettings { /// let cmds: Vec<&str> = m.values_of("cmd").unwrap().collect(); /// assert_eq!(cmds, ["command", "set"]); /// ``` + /// [`try_get_matches_from_mut`]: crate::App::try_get_matches_from_mut() NoBinaryName, /// Places the help string for all arguments on the line after the argument. diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 9d6f41d9978..986091faac0 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -507,3 +507,62 @@ For more information try --help true )); } + +#[test] +fn busybox_like_multicall() { + let app = App::new("busybox") + .setting(AppSettings::Multicall) + .arg( + Arg::new("install") + .long("install") + .exclusive(true) + .takes_value(true) + .default_missing_value("/usr/local/bin"), + ) + .subcommand(App::new("true")) + .subcommand(App::new("false")); + + let m = app.clone().get_matches_from(&["busybox", "true"]); + assert_eq!(m.subcommand_name(), Some("true")); + + let m = app.clone().get_matches_from(&["true"]); + assert_eq!(m.subcommand_name(), Some("true")); + + let m = app.clone().get_matches_from(&["busybox", "--install"]); + assert_eq!(m.subcommand_name(), None); + assert_eq!(m.occurrences_of("install"), 1); + + let m = app.clone().try_get_matches_from(&["a.out"]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); + + let m = app.try_get_matches_from(&["true", "--install"]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); +} + +#[test] +fn hostname_like_multicall() { + let mut app = App::new("hostname") + .setting(AppSettings::Multicall) + .subcommand(App::new("hostname")) + .subcommand(App::new("dnsdomainname")); + + let m = app.clone().get_matches_from(&["hostname"]); + assert_eq!(m.subcommand_name(), Some("hostname")); + + let m = app.clone().get_matches_from(&["dnsdomainname"]); + assert_eq!(m.subcommand_name(), Some("dnsdomainname")); + + let m = app.clone().try_get_matches_from(&["a.out"]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); + + let m = app.try_get_matches_from_mut(&["hostname", "hostname"]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); + + let m = app.try_get_matches_from(&["hostname", "dnsdomainname"]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); +} From 11492adb2c9c098239a0c1e93a1671ed3075659b Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 11 Oct 2021 20:14:46 +0100 Subject: [PATCH 02/13] fixup! Fix rename of clap::Clap to Parser --- clap_derive/examples/hostname.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs index 928b1d96f5b..43ce82453fa 100644 --- a/clap_derive/examples/hostname.rs +++ b/clap_derive/examples/hostname.rs @@ -23,9 +23,9 @@ //! //! This example omits the implementation of displaying address config. -use clap::Clap; +use clap::Parser; -#[derive(Clap, Debug)] +#[derive(Parser, Debug)] #[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] enum Args { /// Show the configured hostname From d062f8f3ad922855424073217a5b3e5a38ae68d1 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 11 Oct 2021 20:46:33 +0100 Subject: [PATCH 03/13] fixup! Remove --install from tests/subcommands --- tests/subcommands.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 986091faac0..977248d7f40 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -512,13 +512,6 @@ For more information try --help fn busybox_like_multicall() { let app = App::new("busybox") .setting(AppSettings::Multicall) - .arg( - Arg::new("install") - .long("install") - .exclusive(true) - .takes_value(true) - .default_missing_value("/usr/local/bin"), - ) .subcommand(App::new("true")) .subcommand(App::new("false")); @@ -528,17 +521,9 @@ fn busybox_like_multicall() { let m = app.clone().get_matches_from(&["true"]); assert_eq!(m.subcommand_name(), Some("true")); - let m = app.clone().get_matches_from(&["busybox", "--install"]); - assert_eq!(m.subcommand_name(), None); - assert_eq!(m.occurrences_of("install"), 1); - let m = app.clone().try_get_matches_from(&["a.out"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); - - let m = app.try_get_matches_from(&["true", "--install"]); - assert!(m.is_err()); - assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); } #[test] From 1ba4b98b8ef4632f639f0e567ec6863ea079455d Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 11 Oct 2021 20:51:33 +0100 Subject: [PATCH 04/13] fixup! trim hardlink implementation from busybox example --- examples/24_multicall_busybox.rs | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/examples/24_multicall_busybox.rs b/examples/24_multicall_busybox.rs index e40592adf1e..6a3e61e312d 100644 --- a/examples/24_multicall_busybox.rs +++ b/examples/24_multicall_busybox.rs @@ -19,12 +19,7 @@ //! but includes the `--install` option as an example of why it can be useful //! for the main program to take arguments that aren't applet subcommands. -use std::{ - env::args_os, - fs::{hard_link, read_link}, - path::{Path, PathBuf}, - process::exit, -}; +use std::process::exit; use clap::{App, AppSettings, Arg}; @@ -45,28 +40,7 @@ fn main() { .subcommand(App::new("false").about("does nothing unsuccessfully")); let matches = app.get_matches_mut(); if matches.occurrences_of("install") > 0 { - let exec_path = read_link("/proc/self/exe") - .ok() - .or_else(|| { - args_os().next().and_then(|s| { - let p: &Path = s.as_ref(); - if p.is_absolute() { - Some(PathBuf::from(s)) - } else { - None - } - }) - }) - .expect( - "Should be able to read /proc/self/exe or argv0 should be present and absolute", - ); - let mut dest = PathBuf::from(matches.value_of("install").unwrap()); - for applet in app.get_subcommands().map(|c| c.get_name()) { - dest.push(applet); - hard_link(&exec_path, &dest).expect("Should be able to hardlink"); - dest.pop(); - } - exit(0); + unimplemented!("Make hardlinks to the executable here"); } exit(match matches.subcommand_name() { From 971b6b683e4823cad5e1af68fc5216de45f912e9 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 11 Oct 2021 22:08:51 +0100 Subject: [PATCH 05/13] fixup! Move explanatory text from examples to docstring --- clap_derive/examples/hostname.rs | 21 +--------------- examples/24_multicall_busybox.rs | 14 +---------- src/build/app/settings.rs | 43 ++++++++++++++++++++++++++------ 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs index 43ce82453fa..42d650a70a8 100644 --- a/clap_derive/examples/hostname.rs +++ b/clap_derive/examples/hostname.rs @@ -1,25 +1,6 @@ //! Example of `hostname`-style multicall program //! -//! `hostname` is a command to just output the configured hostname. -//! It may also render other network-address related information -//! if linked to under a different name -//! e.g. `dnsdomainname` for the domain name part of the FQDN. -//! -//! `hostname`-style differs from `busybox`-style in that the applets -//! should not ever be available as a subcommand -//! and the name of the executable is the same as an applet. -//! -//! i.e. `hostname` doesn't have a `dnsdomainname` subcommand, -//! `dnsdomainname` must only be run via a soft or hard link to the executable. -//! -//! This behaviour is opted-into by naming an applet subcommand -//! the same as the program name. -//! -//! This is desirable when the executable has a primary purpose -//! rather than being a collection of varied applets, -//! so it is appropriate to name the executable after its purpose, -//! but there is other related functionality that would be convenient to provide -//! and it is convenient for the code to implement it to be in the same executable. +//! See the documentation for clap::AppSettings::Multicall for rationale. //! //! This example omits the implementation of displaying address config. diff --git a/examples/24_multicall_busybox.rs b/examples/24_multicall_busybox.rs index 6a3e61e312d..8c4429e3504 100644 --- a/examples/24_multicall_busybox.rs +++ b/examples/24_multicall_busybox.rs @@ -1,18 +1,6 @@ //! Example of a `busybox-style` multicall program //! -//! `busybox` is a single executable that contains a variety of applets -//! for a fully functional Linux userland. -//! -//! `busybox`-style differs from `hostname`-style in that there is a launcher program -//! which the applets are available as subcommands. -//! i.e. you can use the `cat` command either as a link named `cat` -//! or as `busybox cat`. -//! -//! This behaviour is opted-into by not naming an applet the same as the main program. -//! -//! This is desirable when the launcher program has additional options -//! or it is useful to run the applet without installing a symlink -//! e.g. for testing purposes, or there may already be a command of that name installed. +//! See the documentation for clap::AppSettings::Multicall for rationale. //! //! This example omits every command except true and false, //! which are the most trivial to implement, diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index 78c3f5119ac..3dd485a24c9 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -651,12 +651,19 @@ pub enum AppSettings { /// Parse the bin name (argv[0]) as a subcommand /// - /// Busybox is a common example of a "multicall" executable - /// where the command `cat` is a link to the `busybox` bin - /// and, when `cat` is run, `busybox` acts is if you ran `busybox cat`. + /// This adds a small performance penalty to startup + /// as it requires comparing the bin name against every subcommand name. + /// + /// A "multicall" executable is a single executable + /// that contains a variety of applets, + /// and decides which applet to run based on the name of the file. + /// The executable can be called from different names by creating hard links + /// or symbolic links to it. /// - /// This adds a small performance penalty to startup compared to subcommands - /// for comparing argv[0] against applet names. + /// This is desirable when it is convenient to store code + /// for many programs in the same file, + /// such as deduplicating code across multiple programs + /// without loading a shared library at runtime. /// /// Multicall can't be used with [`NoBinaryName`] since they interpret /// the command name in incompatible ways. @@ -666,6 +673,16 @@ pub enum AppSettings { /// Multicall applets are defined as subcommands /// to an app which has the Multicall setting enabled. /// + /// Busybox is a common example of a "multicall" executable + /// with a subcommmand for each applet that can be run directly, + /// e.g. with the `cat` applet being run by running `busybox cat`, + /// or with `cat` as a link to the `busybox` binary. + /// + /// This is desirable when the launcher program has additional options + /// or it is useful to run the applet without installing a symlink + /// e.g. to test the applet without installing it + /// or there may already be a command of that name installed. + /// /// ```rust /// # use clap::{App, AppSettings}; /// let mut app = App::new("busybox") @@ -681,9 +698,19 @@ pub enum AppSettings { /// assert_eq!(m.subcommand_name(), Some("true")); /// ``` /// - /// If the name of an applet is the name of the command, - /// the applet's name is matched - /// and subcommands may not be provided to select another applet. + /// `hostname` is another example of a multicall executable. + /// It differs from busybox by not supporting running applets via subcommand + /// and is instead only runnable via links. + /// + /// This is desirable when the executable has a primary purpose + /// rather than being a collection of varied applets, + /// so it is appropriate to name the executable after its purpose, + /// but there is other related functionality that would be convenient to provide + /// and it is convenient for the code to implement it to be in the same executable. + /// + /// This behaviour can be opted-into + /// by naming a subcommand with the same as the program + /// as applet names take priority. /// /// ```rust /// # use clap::{App, AppSettings, ErrorKind}; From 1782f9d4ecac1191e2293ffcc80137cac83299bc Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 Oct 2021 19:57:32 +0100 Subject: [PATCH 06/13] fixup! Make hostname a non-derive example and rename via Cargo.toml --- Cargo.toml | 8 +++++++ clap_derive/examples/hostname.rs | 24 ------------------- clap_derive/examples/multicall.rs | 20 ++++++++++++++++ ...ll_busybox.rs => 24a_multicall_busybox.rs} | 0 examples/24b_multicall_hostname.rs | 23 ++++++++++++++++++ 5 files changed, 51 insertions(+), 24 deletions(-) delete mode 100644 clap_derive/examples/hostname.rs create mode 100644 clap_derive/examples/multicall.rs rename examples/{24_multicall_busybox.rs => 24a_multicall_busybox.rs} (100%) create mode 100644 examples/24b_multicall_hostname.rs diff --git a/Cargo.toml b/Cargo.toml index 2b60560bacc..1dfbcf2ac43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,14 @@ lazy_static = "1" version-sync = "0.9" criterion = "0.3.2" +[[example]] +name = "busybox" +path = "examples/24a_multicall_busybox.rs" + +[[example]] +name = "hostname" +path = "examples/24b_multicall_hostname.rs" + [features] default = [ "std", diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs deleted file mode 100644 index 42d650a70a8..00000000000 --- a/clap_derive/examples/hostname.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Example of `hostname`-style multicall program -//! -//! See the documentation for clap::AppSettings::Multicall for rationale. -//! -//! This example omits the implementation of displaying address config. - -use clap::Parser; - -#[derive(Parser, Debug)] -#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] -enum Args { - /// Show the configured hostname - Hostname, - /// Show the domain name part of the configured hostname - #[clap(about = "show domain")] - DNSDomainName, -} - -fn main() { - match Args::parse() { - Args::Hostname => println!("www"), - Args::DNSDomainName => println!("example.com"), - } -} diff --git a/clap_derive/examples/multicall.rs b/clap_derive/examples/multicall.rs new file mode 100644 index 00000000000..84d23abd69f --- /dev/null +++ b/clap_derive/examples/multicall.rs @@ -0,0 +1,20 @@ +//! Example of multicall program using clap_derive +//! +//! It works just like using subcommands, +//! but you use the setting attribute to set the Multicall flag. + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] +enum Args { + Foo, + Bar, +} + +fn main() { + match Args::parse() { + Args::Foo => println!("foo"), + Args::Bar => println!("bar"), + } +} diff --git a/examples/24_multicall_busybox.rs b/examples/24a_multicall_busybox.rs similarity index 100% rename from examples/24_multicall_busybox.rs rename to examples/24a_multicall_busybox.rs diff --git a/examples/24b_multicall_hostname.rs b/examples/24b_multicall_hostname.rs new file mode 100644 index 00000000000..84f31b7e0aa --- /dev/null +++ b/examples/24b_multicall_hostname.rs @@ -0,0 +1,23 @@ +//! Example of a `hostname-style` multicall program +//! +//! See the documentation for clap::AppSettings::Multicall for rationale. +//! +//! This example omits the implementation of displaying address config + +use std::process::exit; + +use clap::{App, AppSettings}; + +fn main() { + let app = App::new(env!("CARGO_CRATE_NAME")) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::Multicall) + .subcommand(App::new("hostname").about("shot hostname part of FQDN")) + .subcommand(App::new("dnsdomainname").about("show domain name part of FQDN")); + + match app.get_matches().subcommand_name() { + Some("hostname") => println!("www"), + Some("dnsdomainname") => println!("example.com"), + _ => exit(127), + } +} From a31d3b288daeded4babf3b356d329d879ed7140b Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 Oct 2021 20:01:59 +0100 Subject: [PATCH 07/13] fixup! Revert: Make hostname a non-derive example and rename via Cargo.toml tests/examples.rs:examples_are_functional finds examples by stripping `.rs` off the basename --- Cargo.toml | 8 ------- clap_derive/examples/hostname.rs | 24 +++++++++++++++++++ clap_derive/examples/multicall.rs | 20 ---------------- ...all_busybox.rs => 24_multicall_busybox.rs} | 0 examples/24b_multicall_hostname.rs | 23 ------------------ 5 files changed, 24 insertions(+), 51 deletions(-) create mode 100644 clap_derive/examples/hostname.rs delete mode 100644 clap_derive/examples/multicall.rs rename examples/{24a_multicall_busybox.rs => 24_multicall_busybox.rs} (100%) delete mode 100644 examples/24b_multicall_hostname.rs diff --git a/Cargo.toml b/Cargo.toml index 1dfbcf2ac43..2b60560bacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,14 +81,6 @@ lazy_static = "1" version-sync = "0.9" criterion = "0.3.2" -[[example]] -name = "busybox" -path = "examples/24a_multicall_busybox.rs" - -[[example]] -name = "hostname" -path = "examples/24b_multicall_hostname.rs" - [features] default = [ "std", diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs new file mode 100644 index 00000000000..42d650a70a8 --- /dev/null +++ b/clap_derive/examples/hostname.rs @@ -0,0 +1,24 @@ +//! Example of `hostname`-style multicall program +//! +//! See the documentation for clap::AppSettings::Multicall for rationale. +//! +//! This example omits the implementation of displaying address config. + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] +enum Args { + /// Show the configured hostname + Hostname, + /// Show the domain name part of the configured hostname + #[clap(about = "show domain")] + DNSDomainName, +} + +fn main() { + match Args::parse() { + Args::Hostname => println!("www"), + Args::DNSDomainName => println!("example.com"), + } +} diff --git a/clap_derive/examples/multicall.rs b/clap_derive/examples/multicall.rs deleted file mode 100644 index 84d23abd69f..00000000000 --- a/clap_derive/examples/multicall.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Example of multicall program using clap_derive -//! -//! It works just like using subcommands, -//! but you use the setting attribute to set the Multicall flag. - -use clap::Parser; - -#[derive(Parser, Debug)] -#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] -enum Args { - Foo, - Bar, -} - -fn main() { - match Args::parse() { - Args::Foo => println!("foo"), - Args::Bar => println!("bar"), - } -} diff --git a/examples/24a_multicall_busybox.rs b/examples/24_multicall_busybox.rs similarity index 100% rename from examples/24a_multicall_busybox.rs rename to examples/24_multicall_busybox.rs diff --git a/examples/24b_multicall_hostname.rs b/examples/24b_multicall_hostname.rs deleted file mode 100644 index 84f31b7e0aa..00000000000 --- a/examples/24b_multicall_hostname.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Example of a `hostname-style` multicall program -//! -//! See the documentation for clap::AppSettings::Multicall for rationale. -//! -//! This example omits the implementation of displaying address config - -use std::process::exit; - -use clap::{App, AppSettings}; - -fn main() { - let app = App::new(env!("CARGO_CRATE_NAME")) - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::Multicall) - .subcommand(App::new("hostname").about("shot hostname part of FQDN")) - .subcommand(App::new("dnsdomainname").about("show domain name part of FQDN")); - - match app.get_matches().subcommand_name() { - Some("hostname") => println!("www"), - Some("dnsdomainname") => println!("example.com"), - _ => exit(127), - } -} From b2180e9d728936cace355bd6b13d96f01221129c Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 Oct 2021 20:35:59 +0100 Subject: [PATCH 08/13] fixup! Gate Multicall behind unstable feature --- Cargo.toml | 3 ++- README.md | 1 + clap_derive/Cargo.toml | 2 +- src/build/app/debug_asserts.rs | 1 + src/build/app/mod.rs | 1 + src/build/app/settings.rs | 3 +++ tests/subcommands.rs | 2 ++ 7 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b60560bacc..f1cbbaee7e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ yaml = ["yaml-rust"] # In-work features unstable-replace = [] +unstable-multicall = [] [profile.test] opt-level = 1 @@ -117,7 +118,7 @@ lto = true codegen-units = 1 [package.metadata.docs.rs] -features = ["yaml", "regex", "unstable-replace"] +features = ["yaml", "regex", "unstable-replace", "unstable-multicall"] targets = ["x86_64-unknown-linux-gnu"] [workspace] diff --git a/README.md b/README.md index ac617513b0a..e8c8497fe5d 100644 --- a/README.md +++ b/README.md @@ -508,6 +508,7 @@ features = ["std", "suggestions", "color"] These features are opt-in. But be wary that they can contain breaking changes between minor releases. * **unstable-replace**: Enable [`App::replace`](https://github.com/clap-rs/clap/issues/2836) +* **unstable-multicall**: Enable [`AppSettings::Multicall`](https://github.com/clap-rs/clap/issues/XXXX) ### More Information diff --git a/clap_derive/Cargo.toml b/clap_derive/Cargo.toml index 0bb4891eab9..5b8f9e09c2a 100644 --- a/clap_derive/Cargo.toml +++ b/clap_derive/Cargo.toml @@ -38,7 +38,7 @@ heck = "0.3.0" proc-macro-error = "1" [dev-dependencies] -clap = { path = "../" } +clap = { path = "../", features = ["unstable-multicall"] } clap_generate = { path = "../clap_generate" } trybuild = "1.0" rustversion = "1" diff --git a/src/build/app/debug_asserts.rs b/src/build/app/debug_asserts.rs index a04c93c381d..4a612c4531b 100644 --- a/src/build/app/debug_asserts.rs +++ b/src/build/app/debug_asserts.rs @@ -364,5 +364,6 @@ fn assert_app_flags(app: &App) { } checker!(AllowInvalidUtf8ForExternalSubcommands requires AllowExternalSubcommands); + #[cfg(feature = "unstable-multicall")] checker!(Multicall conflicts NoBinaryName); } diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index c5987ef37ee..fb529e6e8d2 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -2134,6 +2134,7 @@ impl<'help> App<'help> { { let mut it = Input::from(itr.into_iter()); + #[cfg(feature = "unstable-multicall")] if self.settings.is_set(AppSettings::Multicall) { if let Some((argv0, _)) = it.next() { let argv0 = Path::new(&argv0); diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index 3dd485a24c9..4a6054e020a 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -50,6 +50,7 @@ bitflags! { const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 42; const INFER_LONG_ARGS = 1 << 43; const IGNORE_ERRORS = 1 << 44; + #[cfg(feature = "unstable-multicall")] const MULTICALL = 1 << 45; } } @@ -107,6 +108,7 @@ impl_settings! { AppSettings, AppFlags, => Flags::HELP_REQUIRED, Hidden("hidden") => Flags::HIDDEN, + #[cfg(feature = "unstable-multicall")] Multicall("multicall") => Flags::MULTICALL, NoAutoHelp("noautohelp") @@ -729,6 +731,7 @@ pub enum AppSettings { /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html /// [`NoBinaryName`]: crate::AppSettings::NoBinaryName /// [`try_get_matches_from_mut`]: crate::App::try_get_matches_from_mut() + #[cfg(feature = "unstable-multicall")] Multicall, /// Specifies to use the version of the current command for all [`subcommands`]. diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 977248d7f40..6c360dd8fba 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -508,6 +508,7 @@ For more information try --help )); } +#[cfg(feature = "unstable-multicall")] #[test] fn busybox_like_multicall() { let app = App::new("busybox") @@ -526,6 +527,7 @@ fn busybox_like_multicall() { assert_eq!(m.unwrap_err().kind, ErrorKind::UnknownArgument); } +#[cfg(feature = "unstable-multicall")] #[test] fn hostname_like_multicall() { let mut app = App::new("hostname") From b808729bbc71ffd5850dfb9c3fc2309fc80ef21d Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 Oct 2021 20:51:26 +0100 Subject: [PATCH 09/13] fixup! Fill in tracking issue number --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8c8497fe5d..b5df4aaf4c2 100644 --- a/README.md +++ b/README.md @@ -508,7 +508,7 @@ features = ["std", "suggestions", "color"] These features are opt-in. But be wary that they can contain breaking changes between minor releases. * **unstable-replace**: Enable [`App::replace`](https://github.com/clap-rs/clap/issues/2836) -* **unstable-multicall**: Enable [`AppSettings::Multicall`](https://github.com/clap-rs/clap/issues/XXXX) +* **unstable-multicall**: Enable [`AppSettings::Multicall`](https://github.com/clap-rs/clap/issues/2861) ### More Information From 694329b36389a6c8c9d2204dbf5b575e0b929816 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 Oct 2021 19:57:32 +0100 Subject: [PATCH 10/13] fixup! Make hostname a non-derive example and rename via Cargo.toml --- Cargo.toml | 8 +++++++ clap_derive/Cargo.toml | 2 +- clap_derive/examples/hostname.rs | 24 ------------------- ...ll_busybox.rs => 24a_multicall_busybox.rs} | 0 examples/24b_multicall_hostname.rs | 23 ++++++++++++++++++ tests/examples.rs | 14 +++++++---- 6 files changed, 41 insertions(+), 30 deletions(-) delete mode 100644 clap_derive/examples/hostname.rs rename examples/{24_multicall_busybox.rs => 24a_multicall_busybox.rs} (100%) create mode 100644 examples/24b_multicall_hostname.rs diff --git a/Cargo.toml b/Cargo.toml index f1cbbaee7e9..250ab0bc1f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,14 @@ lazy_static = "1" version-sync = "0.9" criterion = "0.3.2" +[[example]] +name = "busybox" +path = "examples/24a_multicall_busybox.rs" + +[[example]] +name = "hostname" +path = "examples/24b_multicall_hostname.rs" + [features] default = [ "std", diff --git a/clap_derive/Cargo.toml b/clap_derive/Cargo.toml index 5b8f9e09c2a..0bb4891eab9 100644 --- a/clap_derive/Cargo.toml +++ b/clap_derive/Cargo.toml @@ -38,7 +38,7 @@ heck = "0.3.0" proc-macro-error = "1" [dev-dependencies] -clap = { path = "../", features = ["unstable-multicall"] } +clap = { path = "../" } clap_generate = { path = "../clap_generate" } trybuild = "1.0" rustversion = "1" diff --git a/clap_derive/examples/hostname.rs b/clap_derive/examples/hostname.rs deleted file mode 100644 index 42d650a70a8..00000000000 --- a/clap_derive/examples/hostname.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Example of `hostname`-style multicall program -//! -//! See the documentation for clap::AppSettings::Multicall for rationale. -//! -//! This example omits the implementation of displaying address config. - -use clap::Parser; - -#[derive(Parser, Debug)] -#[clap(name = env!("CARGO_CRATE_NAME"), setting = clap::AppSettings::Multicall)] -enum Args { - /// Show the configured hostname - Hostname, - /// Show the domain name part of the configured hostname - #[clap(about = "show domain")] - DNSDomainName, -} - -fn main() { - match Args::parse() { - Args::Hostname => println!("www"), - Args::DNSDomainName => println!("example.com"), - } -} diff --git a/examples/24_multicall_busybox.rs b/examples/24a_multicall_busybox.rs similarity index 100% rename from examples/24_multicall_busybox.rs rename to examples/24a_multicall_busybox.rs diff --git a/examples/24b_multicall_hostname.rs b/examples/24b_multicall_hostname.rs new file mode 100644 index 00000000000..84f31b7e0aa --- /dev/null +++ b/examples/24b_multicall_hostname.rs @@ -0,0 +1,23 @@ +//! Example of a `hostname-style` multicall program +//! +//! See the documentation for clap::AppSettings::Multicall for rationale. +//! +//! This example omits the implementation of displaying address config + +use std::process::exit; + +use clap::{App, AppSettings}; + +fn main() { + let app = App::new(env!("CARGO_CRATE_NAME")) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::Multicall) + .subcommand(App::new("hostname").about("shot hostname part of FQDN")) + .subcommand(App::new("dnsdomainname").about("show domain name part of FQDN")); + + match app.get_matches().subcommand_name() { + Some("hostname") => println!("www"), + Some("dnsdomainname") => println!("example.com"), + _ => exit(127), + } +} diff --git a/tests/examples.rs b/tests/examples.rs index ab6e2d7ded8..86b9238003d 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -10,7 +10,7 @@ fn run_example>(name: S, args: &[&str]) -> Output { "--example", name.as_ref(), "--features", - "yaml", + "yaml unstable-multicall", "--", ]; all_args.extend_from_slice(args); @@ -32,10 +32,14 @@ fn examples_are_functional() { for path in example_paths { example_count += 1; - let example_name = path - .file_stem() - .and_then(OsStr::to_str) - .expect("unable to determine example name"); + let example_name = match path.file_name().and_then(OsStr::to_str) { + Some("24a_multicall_busybox.rs") => "busybox".into(), + Some("24b_multicall_hostname.rs") => "hostname".into(), + _ => path + .file_stem() + .and_then(OsStr::to_str) + .expect("unable to determine example name"), + }; let help_output = run_example(example_name, &["--help"]); assert!( From f7e9bfed80f74d35b8fc37336137dff80945826c Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 15 Oct 2021 20:27:29 +0100 Subject: [PATCH 11/13] fixup! Add unstable-multicall flag to CI workflows --- .github/workflows/ci-pr.yml | 4 ++-- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/coverage.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index ce33d0c6b94..83b9b199670 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -41,7 +41,7 @@ jobs: test-full: name: Tests (Full) env: - FLAGS: --features 'wrap_help yaml regex unstable-replace' + FLAGS: --features 'wrap_help yaml regex unstable-replace unstable-multicall' strategy: fail-fast: false matrix: @@ -80,7 +80,7 @@ jobs: - name: Default features run: cargo check --all-targets - name: All features + Debug - run: cargo check --all-targets --features "wrap_help yaml regex unstable-replace debug" + run: cargo check --all-targets --features "wrap_help yaml regex unstable-replace unstable-multicall debug" - name: No features run: cargo check --all-targets --no-default-features --features "std cargo" - name: UI Tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08b8df7e1c2..c3d5f8a9d0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,19 +97,19 @@ jobs: if: matrix.features == 'all' with: command: test - args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace" + args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace unstable-multicall" - name: Check debug uses: actions-rs/cargo@v1 if: matrix.features == 'all' with: command: check - args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace debug" + args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace unstable-multicall debug" - name: Test release uses: actions-rs/cargo@v1 if: matrix.features == 'release' with: command: test - args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace" --release + args: --target ${{ matrix.target }} --features "wrap_help yaml regex unstable-replace unstable-multicall" --release nightly: name: Nightly Tests strategy: @@ -139,19 +139,19 @@ jobs: if: matrix.features == 'all' with: command: test - args: --features "wrap_help yaml regex unstable-replace" + args: --features "wrap_help yaml regex unstable-replace unstable-multicall" - name: Check debug uses: actions-rs/cargo@v1 if: matrix.features == 'all' with: command: check - args: --features "wrap_help yaml regex unstable-replace debug" + args: --features "wrap_help yaml regex unstable-replace unstable-multicall debug" - name: Test release uses: actions-rs/cargo@v1 if: matrix.features == 'release' with: command: test - args: --features "wrap_help yaml regex unstable-replace" --release + args: --features "wrap_help yaml regex unstable-replace unstable-multicall" --release wasm: name: Wasm Check runs-on: ubuntu-latest @@ -172,4 +172,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --target ${{ matrix.target }} --features "yaml regex unstable-replace" + args: --target ${{ matrix.target }} --features "yaml regex unstable-replace unstable-multicall" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6f68c286ae9..eabb04ba1da 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -31,7 +31,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: llvm-cov - args: --features "wrap_help yaml regex unstable-replace" --lcov --output-path lcov.info + args: --features "wrap_help yaml regex unstable-replace unstable-multicall" --lcov --output-path lcov.info - name: Coveralls uses: coverallsapp/github-action@master with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6d8490d28a5..c0df22a6bd0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,7 +32,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --features "wrap_help yaml regex unstable-replace" -- -D warnings + args: --features "wrap_help yaml regex unstable-replace unstable-multicall" -- -D warnings - name: Format check uses: actions-rs/cargo@v1 with: From 636ecaffa2cffac94891364bab98d1e5e51bb162 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 15 Oct 2021 20:59:33 +0100 Subject: [PATCH 12/13] fixup! make new tests compile but not run without unstable-multicall --- examples/24a_multicall_busybox.rs | 8 +++++--- examples/24b_multicall_hostname.rs | 4 +++- tests/examples.rs | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/examples/24a_multicall_busybox.rs b/examples/24a_multicall_busybox.rs index 8c4429e3504..19e10a66891 100644 --- a/examples/24a_multicall_busybox.rs +++ b/examples/24a_multicall_busybox.rs @@ -12,9 +12,8 @@ use std::process::exit; use clap::{App, AppSettings, Arg}; fn main() { - let mut app = App::new(env!("CARGO_CRATE_NAME")) + let app = App::new(env!("CARGO_CRATE_NAME")) .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::Multicall) .arg( Arg::new("install") .long("install") @@ -26,7 +25,10 @@ fn main() { ) .subcommand(App::new("true").about("does nothing successfully")) .subcommand(App::new("false").about("does nothing unsuccessfully")); - let matches = app.get_matches_mut(); + + #[cfg(feature = "unstable-multicall")] + let app = app.setting(AppSettings::Multicall); + let matches = app.get_matches(); if matches.occurrences_of("install") > 0 { unimplemented!("Make hardlinks to the executable here"); } diff --git a/examples/24b_multicall_hostname.rs b/examples/24b_multicall_hostname.rs index 84f31b7e0aa..444764401ae 100644 --- a/examples/24b_multicall_hostname.rs +++ b/examples/24b_multicall_hostname.rs @@ -11,10 +11,12 @@ use clap::{App, AppSettings}; fn main() { let app = App::new(env!("CARGO_CRATE_NAME")) .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::Multicall) .subcommand(App::new("hostname").about("shot hostname part of FQDN")) .subcommand(App::new("dnsdomainname").about("show domain name part of FQDN")); + #[cfg(feature = "unstable-multicall")] + let app = app.setting(AppSettings::Multicall); + match app.get_matches().subcommand_name() { Some("hostname") => println!("www"), Some("dnsdomainname") => println!("example.com"), diff --git a/tests/examples.rs b/tests/examples.rs index 86b9238003d..6bfd2e5256d 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -10,7 +10,7 @@ fn run_example>(name: S, args: &[&str]) -> Output { "--example", name.as_ref(), "--features", - "yaml unstable-multicall", + "yaml", "--", ]; all_args.extend_from_slice(args); @@ -33,8 +33,18 @@ fn examples_are_functional() { example_count += 1; let example_name = match path.file_name().and_then(OsStr::to_str) { - Some("24a_multicall_busybox.rs") => "busybox".into(), - Some("24b_multicall_hostname.rs") => "hostname".into(), + Some("24a_multicall_busybox.rs") => { + #[cfg(not(feature = "unstable-multicall"))] + continue; + #[allow(unreachable_code)] + "busybox".into() + }, + Some("24b_multicall_hostname.rs") => { + #[cfg(not(feature = "unstable-multicall"))] + continue; + #[allow(unreachable_code)] + "hostname".into() + }, _ => path .file_stem() .and_then(OsStr::to_str) From b92f2c0339a60a25d00c95bef334270fa041173b Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 15 Oct 2021 21:14:07 +0100 Subject: [PATCH 13/13] fixup! formatting lint --- src/build/app/mod.rs | 4 +++- tests/examples.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index fb529e6e8d2..67a6496abda 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -2168,7 +2168,9 @@ impl<'help> App<'help> { return self._do_parse(&mut it); } _ => { - debug!("App::try_get_matches_from_mut: existing applet or no program name"); + debug!( + "App::try_get_matches_from_mut: existing applet or no program name" + ); debug!("App::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it"); it.insert(&[&command]); debug!("App::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name"); diff --git a/tests/examples.rs b/tests/examples.rs index 6bfd2e5256d..15863f926c6 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -38,13 +38,13 @@ fn examples_are_functional() { continue; #[allow(unreachable_code)] "busybox".into() - }, + } Some("24b_multicall_hostname.rs") => { #[cfg(not(feature = "unstable-multicall"))] continue; #[allow(unreachable_code)] "hostname".into() - }, + } _ => path .file_stem() .and_then(OsStr::to_str)