Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Multicall executables as subcommands with a Multicall setting #2817

Merged
merged 13 commits into from Oct 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"]
fishface60 marked this conversation as resolved.
Show resolved Hide resolved
targets = ["x86_64-unknown-linux-gnu"]

[workspace]
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -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/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),
epage marked this conversation as resolved.
Show resolved Hide resolved
)
.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 @@ -345,8 +345,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);
kbknapp marked this conversation as resolved.
Show resolved Hide resolved
}
50 changes: 50 additions & 0 deletions src/build/app/mod.rs
Expand Up @@ -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())
Expand Down Expand Up @@ -2132,6 +2133,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