diff --git a/Cargo.lock b/Cargo.lock index 51a5157ac..b85f5f1d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,6 +1131,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "clap_mangen", "crossterm 0.27.0", "dialoguer", "dirs 5.0.1", @@ -1305,6 +1306,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "clap_mangen" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3be86020147691e1d2ef58f75346a3d4d94807bfc473e377d52f09f0f7d77f7" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "clru" version = "0.6.1" @@ -5228,6 +5239,12 @@ dependencies = [ "serde", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rsa" version = "0.9.3" diff --git a/cargo-shuttle/Cargo.toml b/cargo-shuttle/Cargo.toml index b443819f1..67c6af52d 100644 --- a/cargo-shuttle/Cargo.toml +++ b/cargo-shuttle/Cargo.toml @@ -20,6 +20,7 @@ cargo_metadata = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["env"] } clap_complete = "4.3.1" +clap_mangen = "0.2.15" crossterm = { workspace = true } dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } dirs = { workspace = true } diff --git a/cargo-shuttle/README.md b/cargo-shuttle/README.md index bbcbb5d14..766c63f1a 100644 --- a/cargo-shuttle/README.md +++ b/cargo-shuttle/README.md @@ -101,7 +101,7 @@ Commands: clean Remove cargo build artifacts in the shuttle environment login Login to the shuttle platform logout Log out of the shuttle platform - generate Generate shell completions + generate Generate shell completions and man page feedback Open an issue on GitHub and provide feedback help Print this message or the help of the given subcommand(s) diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index 298322fd6..e9f0a8142 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -126,17 +126,25 @@ pub enum Command { Login(LoginArgs), /// Log out of the Shuttle platform Logout(LogoutArgs), + /// Generate shell completions and man page + #[command(subcommand)] + Generate(GenerateCommand), + /// Open an issue on GitHub and provide feedback + Feedback, +} + +#[derive(Parser)] +pub enum GenerateCommand { /// Generate shell completions - Generate { - /// Which shell - #[arg(short, long, env, default_value_t = Shell::Bash)] + Shell { + /// The shell to generate shell completion for shell: Shell, /// Output to a file (stdout by default) - #[arg(short, long, env)] + #[arg(short, long)] output: Option, }, - /// Open an issue on GitHub and provide feedback - Feedback, + /// Generate man page to the standard output + Manpage, } #[derive(Parser)] diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 9552a2237..97cfdbe61 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -15,6 +15,9 @@ use std::path::{Path, PathBuf}; use std::process::exit; use std::str::FromStr; +use args::GenerateCommand; +use clap_mangen::Man; + use shuttle_common::{ claims::{ClaimService, InjectPropagation}, constants::{ @@ -146,7 +149,7 @@ impl Shuttle { ) -> Result { if let Some(ref url) = args.api_url { if url != API_URL_DEFAULT { - println!("INFO: Targeting non-standard API: {url}"); + eprintln!("INFO: Targeting non-standard API: {url}"); } if url.ends_with('/') { eprintln!("WARNING: API URL is probably incorrect. Ends with '/': {url}"); @@ -205,7 +208,10 @@ impl Shuttle { self.init(init_args, args.project_args, provided_path_to_init) .await } - Command::Generate { shell, output } => self.complete(shell, output), + Command::Generate(GenerateCommand::Manpage) => self.generate_manpage(), + Command::Generate(GenerateCommand::Shell { shell, output }) => { + self.complete(shell, output) + } Command::Login(login_args) => self.login(login_args).await, Command::Logout(logout_args) => self.logout(logout_args).await, Command::Feedback => self.feedback(), @@ -657,9 +663,37 @@ impl Shuttle { let name = env!("CARGO_PKG_NAME"); let mut app = Command::command(); match output { - Some(v) => generate(shell, &mut app, name, &mut File::create(v)?), + Some(path) => generate(shell, &mut app, name, &mut File::create(path)?), None => generate(shell, &mut app, name, &mut stdout()), }; + Ok(CommandOutcome::Ok) + } + + fn generate_manpage(&self) -> Result { + let app = ShuttleArgs::command(); + let output = std::io::stdout(); + let mut output_handle = output.lock(); + + Man::new(app.clone()).render(&mut output_handle)?; + + for subcommand in app.get_subcommands() { + let primary = Man::new(subcommand.clone()); + primary.render_name_section(&mut output_handle)?; + primary.render_synopsis_section(&mut output_handle)?; + primary.render_description_section(&mut output_handle)?; + primary.render_options_section(&mut output_handle)?; + // For example, `generate` has sub-commands `shell` and `manpage` + if subcommand.has_subcommands() { + primary.render_subcommands_section(&mut output_handle)?; + for sb in subcommand.get_subcommands() { + let secondary = Man::new(sb.clone()); + secondary.render_name_section(&mut output_handle)?; + secondary.render_synopsis_section(&mut output_handle)?; + secondary.render_description_section(&mut output_handle)?; + secondary.render_options_section(&mut output_handle)?; + } + } + } Ok(CommandOutcome::Ok) }