From cfb664f8ac1cd35f7f37a37f5c5e402b50fb6d24 Mon Sep 17 00:00:00 2001 From: Swarnim Arun Date: Tue, 19 Dec 2023 18:12:26 +0530 Subject: [PATCH] feat: add completions generation for shells (#426) --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + docs/cli_usage.md | 29 +++++++++++++++++++++++++++++ mkdocs.yml | 3 ++- rust-tests/src/lib.rs | 9 ++------- src/main.rs | 32 +++++++++++++++++++++++++------- 6 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 docs/cli_usage.md diff --git a/Cargo.lock b/Cargo.lock index 457900394..b5a4d28ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,6 +561,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.4.7" @@ -2519,6 +2528,7 @@ dependencies = [ "chrono", "clap", "clap-verbosity-flag", + "clap_complete", "comfy-table", "console", "content_inspector", diff --git a/Cargo.toml b/Cargo.toml index 3b124ca8e..e83773529 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ indexmap = "2.1.0" dunce = "1.0.4" fs-err = "2.11.0" which = "5.0.0" +clap_complete = "4.4.4" [dev-dependencies] insta = { version = "1.34.0", features = ["yaml"] } diff --git a/docs/cli_usage.md b/docs/cli_usage.md new file mode 100644 index 000000000..13e6fd7ed --- /dev/null +++ b/docs/cli_usage.md @@ -0,0 +1,29 @@ +# CLI Usage + +## Shell Completions + +We support shell completions through clap-complete. +You can generate them for your shell using the `--generate` command. + +Eg, +```sh +rattler-build --generate=zsh > ${ZSH_COMPLETIONS_PATH:~/.zsh/completions}/_rattler-build +compinit +``` + +Ensure that whereever you install the is pointed to by your FPATH (for zsh or equivalent in other shells). +Now you can use TAB or your configured completion key. :3 + +```sh +$ rattler-build +build -- Build a package +help -- Print this message or the help of the given subcommand(s) +rebuild -- Rebuild a package +test -- Test a package +``` + +Example for Fish Shell just generate the `completions.fish` and add to `~/.config/fish/completions`. +```sh +rattler-build --generate=fish > ${ZSH_COMPLETIONS_PATH:~/.config/fish/completions}/rattler-build.fish +``` + diff --git a/mkdocs.yml b/mkdocs.yml index a14708f64..54bdf8527 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -78,13 +78,14 @@ nav: - Package specification: package_spec.md - Compilers and cross compilation: compilers.md - - Automatic recipe linting: automatic_linting.md - Testing packages: testing.md - Reproducible builds: rebuild.md - Internals: internals.md - Recipe file: recipe_file.md + - CLI Usage: cli_usage.md + - Automatic recipe linting: automatic_linting.md plugins: - search diff --git a/rust-tests/src/lib.rs b/rust-tests/src/lib.rs index e786bde56..4d178b407 100644 --- a/rust-tests/src/lib.rs +++ b/rust-tests/src/lib.rs @@ -185,13 +185,8 @@ mod tests { let help_test = rattler() // no heap allocations happen here, ideally! .with_args(Vec::<&str>::new()) - .map(|out| out.stderr) - .map(|s| { - #[cfg(target_family = "unix")] - return s.starts_with(b"Usage: rattler-build [OPTIONS]"); - #[cfg(target_family = "windows")] - return s.starts_with(b"Usage: rattler-build.exe [OPTIONS]"); - }) + .map(|out| out.stdout) + .map(|s| s.starts_with(b"Usage: rattler-build [OPTIONS]")) .unwrap(); assert!(help_test); } diff --git a/src/main.rs b/src/main.rs index 4172c627a..46e53ebd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ //! This is the main entry point for the `rattler-build` binary. -use clap::{arg, crate_version, Parser}; +use clap::{arg, crate_version, CommandFactory, Parser}; use clap_verbosity_flag::{InfoLevel, Verbosity}; use dunce::canonicalize; @@ -59,8 +59,12 @@ enum SubCommands { #[derive(Parser)] #[clap(version = crate_version!())] struct App { + // If provided, outputs the completion file for given shell + #[arg(long = "generate", value_enum)] + generator: Option, + #[clap(subcommand)] - subcommand: SubCommands, + subcommand: Option, #[command(flatten)] verbose: Verbosity, @@ -220,11 +224,25 @@ async fn main() -> miette::Result<()> { ) .init(); - match args.subcommand { - SubCommands::Build(args) => run_build_from_args(args, multi_progress).await, - SubCommands::Test(args) => run_test_from_args(args).await, - SubCommands::Rebuild(args) => rebuild_from_args(args).await, - SubCommands::Upload(args) => upload_from_args(args).await, + if let Some(generator) = args.generator { + let mut cmd = App::command(); + tracing::info!("Generating completion file for {generator:?}..."); + fn print_completions(gen: G, cmd: &mut clap::Command) { + clap_complete::generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); + } + print_completions(generator, &mut cmd); + Ok(()) + } else { + match args.subcommand { + Some(SubCommands::Build(args)) => run_build_from_args(args, multi_progress).await, + Some(SubCommands::Test(args)) => run_test_from_args(args).await, + Some(SubCommands::Rebuild(args)) => rebuild_from_args(args).await, + Some(SubCommands::Upload(args)) => upload_from_args(args).await, + None => { + _ = App::command().print_long_help(); + Ok(()) + } + } } }