Skip to content

Commit

Permalink
Merge #2817
Browse files Browse the repository at this point in the history
2817: Add support for Multicall executables as subcommands with a Multicall setting r=pksunkara a=fishface60



Co-authored-by: Richard Maw <richard.maw@gmail.com>
  • Loading branch information
bors[bot] and fishface60 committed Oct 16, 2021
2 parents afd9efc + b92f2c0 commit b835ce9
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 17 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-pr.yml
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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"
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Expand Up @@ -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:
Expand Down
11 changes: 10 additions & 1 deletion Cargo.toml
Expand Up @@ -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",
Expand Down Expand Up @@ -108,6 +116,7 @@ yaml = ["yaml-rust"]

# In-work features
unstable-replace = []
unstable-multicall = []

[profile.test]
opt-level = 1
Expand All @@ -117,7 +126,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]
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -478,6 +478,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/2861)

### More Information

Expand Down
41 changes: 41 additions & 0 deletions examples/24a_multicall_busybox.rs
@@ -0,0 +1,41 @@
//! Example of a `busybox-style` multicall program
//!
//! 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,
//! 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::process::exit;

use clap::{App, AppSettings, Arg};

fn main() {
let app = App::new(env!("CARGO_CRATE_NAME"))
.setting(AppSettings::ArgRequiredElseHelp)
.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"));

#[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");
}

exit(match matches.subcommand_name() {
Some("true") => 0,
Some("false") => 1,
_ => 127,
})
}
25 changes: 25 additions & 0 deletions examples/24b_multicall_hostname.rs
@@ -0,0 +1,25 @@
//! 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)
.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"),
_ => exit(127),
}
}
19 changes: 18 additions & 1 deletion src/build/app/debug_asserts.rs
Expand Up @@ -358,8 +358,25 @@ 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);
#[cfg(feature = "unstable-multicall")]
checker!(Multicall conflicts NoBinaryName);
}
50 changes: 50 additions & 0 deletions src/build/app/mod.rs
Expand Up @@ -1954,6 +1954,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())
Expand Down Expand Up @@ -2136,6 +2137,55 @@ impl<'help> App<'help> {
T: Into<OsString> + Clone,
{
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);
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
Expand Down

0 comments on commit b835ce9

Please sign in to comment.