diff --git a/Cargo.lock b/Cargo.lock index 2574fae78..8178f3305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.4.7" @@ -672,6 +681,7 @@ dependencies = [ "bytesize", "chrono", "clap", + "clap_complete", "glob", "indicatif", "nix", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 592b42b92..cce0706fb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -14,6 +14,7 @@ ansi_term = "0.12.1" bytesize = "1.3.0" chrono = "0.4.31" clap = { version = "4.4.11", features = ["derive"] } +clap_complete = { version = "4.4.6", optional = true } glob = "0.3.1" indicatif = { version = "0.17.7", features = ["improved_unicode"] } pna = { version = "0.5.0", path = "../pna" } @@ -25,6 +26,7 @@ nix = { version = "0.27.1", features = ["user", "fs"] } [features] experimental = [] +generate = ["experimental", "clap_complete"] [[bin]] name = "pna" diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e29d1c182..155f06157 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,7 +4,7 @@ use bytesize::ByteSize; use clap::{value_parser, ArgGroup, Parser, Subcommand, ValueEnum}; use std::path::PathBuf; -#[derive(Parser, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Parser, Clone, Eq, PartialEq, Hash, Debug)] #[command( name = env!("CARGO_PKG_NAME"), version, @@ -47,7 +47,7 @@ pub(crate) enum Verbosity { Verbose, } -#[derive(Subcommand, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Subcommand, Clone, Eq, PartialEq, Hash, Debug)] pub(crate) enum Commands { #[command(visible_alias = "c", about = "Create archive")] Create(CreateArgs), diff --git a/cli/src/command.rs b/cli/src/command.rs index b9c5d5736..813340464 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -6,8 +6,6 @@ pub(super) mod experimental; pub mod extract; pub mod list; -#[cfg(feature = "experimental")] -use self::experimental::ExperimentalCommands; use crate::cli::{CipherAlgorithmArgs, Cli, Commands, PasswordArgs, Verbosity}; use std::io; @@ -18,9 +16,7 @@ pub fn entry(cli: Cli) -> io::Result<()> { Commands::Extract(args) => args.execute(cli.verbosity.verbosity()), Commands::List(args) => args.execute(cli.verbosity.verbosity()), #[cfg(feature = "experimental")] - Commands::Experimental(cmd) => match cmd.command { - ExperimentalCommands::Split(cmd) => cmd.execute(cli.verbosity.verbosity()), - }, + Commands::Experimental(cmd) => cmd.execute(cli.verbosity.verbosity()), } } diff --git a/cli/src/command/experimental.rs b/cli/src/command/experimental.rs index ef4491b44..1c2bb4d86 100644 --- a/cli/src/command/experimental.rs +++ b/cli/src/command/experimental.rs @@ -1,18 +1,44 @@ +#[cfg(feature = "generate")] +use crate::cli::Cli; use crate::{ cli::Verbosity, command::{commons::split_to_parts, Command}, utils::part_name, }; use bytesize::ByteSize; +#[cfg(feature = "generate")] +use clap::CommandFactory; use clap::{Args, Parser, Subcommand}; +#[cfg(feature = "generate")] +use clap_complete::{generate, Generator, Shell}; use pna::{Archive, EntryPart, MIN_CHUNK_BYTES_SIZE, PNA_HEADER}; +#[cfg(feature = "generate")] +use std::env; use std::{fs::File, io, path::PathBuf}; -#[derive(Args, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[command(args_conflicts_with_subcommands = true)] +#[derive(Args, Clone, Eq, PartialEq, Hash, Debug)] +#[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)] pub(crate) struct ExperimentalArgs { #[command(subcommand)] - pub(crate) command: ExperimentalCommands, + pub(crate) command: Option, + #[cfg(feature = "generate")] + #[arg(long, help = "Generate shell auto complete")] + pub(crate) generate: Option, +} + +impl Command for ExperimentalArgs { + fn execute(self, verbosity: Verbosity) -> io::Result<()> { + #[cfg(feature = "generate")] + if let Some(shell) = self.generate { + let cmd = &mut Cli::command(); + print_completions(shell, cmd); + return Ok(()); + }; + match self.command { + Some(ExperimentalCommands::Split(cmd)) => cmd.execute(verbosity), + None => unreachable!(), + } + } } #[derive(Subcommand, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] @@ -91,3 +117,14 @@ fn split_archive(args: SplitCommand, verbosity: Verbosity) -> io::Result<()> { writer.finalize()?; Ok(()) } + +#[cfg(feature = "generate")] +fn print_completions(gen: G, cmd: &mut clap::Command) { + let name = env::args().next().map(|it| PathBuf::from(it)).unwrap(); + generate( + gen, + cmd, + name.file_name().unwrap().to_string_lossy(), + &mut io::stdout(), + ); +}