From e1e8d2234f57bab84086d65097e14a6e4a23489b Mon Sep 17 00:00:00 2001 From: ysthakur <45539777+ysthakur@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:54:51 -0400 Subject: [PATCH] refactor: Allow taking multiple shells at once --- src/gen/bash.rs | 2 +- src/gen/json.rs | 12 ++++++------ src/gen/mod.rs | 6 +++--- src/gen/zsh.rs | 16 ++++++++++------ src/main.rs | 25 +++++++++++++++++-------- src/parse/mod.rs | 9 ++++++--- tests/integration_tests.rs | 27 +++++++++++++++------------ 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/gen/bash.rs b/src/gen/bash.rs index 70e21d1..9d428b3 100644 --- a/src/gen/bash.rs +++ b/src/gen/bash.rs @@ -14,7 +14,7 @@ pub struct BashCompletions; impl Completions for BashCompletions { /// Generate a completion file for Bash - fn generate

(cmd_name: String, _cmd_info: CommandInfo, out_dir: P) -> Result<()> + fn generate

(cmd_name: &str, _cmd_info: &CommandInfo, out_dir: P) -> Result<()> where P: AsRef, { diff --git a/src/gen/json.rs b/src/gen/json.rs index 8815d52..9c766de 100644 --- a/src/gen/json.rs +++ b/src/gen/json.rs @@ -13,7 +13,7 @@ impl Completions for JsonCompletions { /// Generate JSON representing the parsed options /// /// This should probably use a real JSON library but whatever - fn generate

(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()> + fn generate

(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()> where P: AsRef, { @@ -37,11 +37,11 @@ impl Completions for JsonCompletions { /// * `indent` - The indentation level (how many subcommands in we are) /// * `last` - Whether this is the last command at this level. Used for deciding /// whether or not to put a trailing comma -fn generate_cmd(cmd: &str, cmd_info: CommandInfo, last: bool, out: &mut Output) { +fn generate_cmd(cmd: &str, cmd_info: &CommandInfo, last: bool, out: &mut Output) { let cmd = quote(cmd); // Avoid trailing commas let end = if last { "}" } else { "}," }; - let mut args = cmd_info.args.into_iter(); + let mut args = cmd_info.args.iter(); if let Some(mut arg) = args.next() { out.writeln(format!("{cmd}: {{")); out.indent(); @@ -80,17 +80,17 @@ fn generate_cmd(cmd: &str, cmd_info: CommandInfo, last: bool, out: &mut Output) out.dedent(); out.writeln("],"); - let mut subcmds = cmd_info.subcommands.into_iter(); + let mut subcmds = cmd_info.subcommands.iter(); if let Some((mut name, mut info)) = subcmds.next() { out.writeln("\"subcommands\": {"); out.indent(); loop { if let Some(next) = subcmds.next() { - generate_cmd(&name, info, false, out); + generate_cmd(&name, &info, false, out); name = next.0; info = next.1; } else { - generate_cmd(&name, info, true, out); + generate_cmd(&name, &info, true, out); break; } } diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 934a5d2..0cda1e8 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -13,13 +13,13 @@ pub use zsh::*; use crate::parse::CommandInfo; pub trait Completions { - fn generate

(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()> + fn generate

(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()> where P: AsRef; - fn generate_all(cmds: I, out_dir: P) -> Result<()> + fn generate_all<'a, I, P>(cmds: I, out_dir: P) -> Result<()> where - I: IntoIterator, + I: Iterator, P: AsRef, { cmds.into_iter().try_for_each(|(cmd_name, cmd_info)| { diff --git a/src/gen/zsh.rs b/src/gen/zsh.rs index 4f2d485..cb769a0 100644 --- a/src/gen/zsh.rs +++ b/src/gen/zsh.rs @@ -42,7 +42,7 @@ impl Completions for ZshCompletions { /// '-b[Make new branch]' /// } /// ``` - fn generate

(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()> + fn generate

(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()> where P: AsRef, { @@ -69,7 +69,7 @@ impl Completions for ZshCompletions { /// named `_foo_bar` fn generate_fn( _cmd_name: &str, - cmd_info: CommandInfo, + cmd_info: &CommandInfo, out: &mut Output, pos: usize, fn_name: &str, @@ -88,9 +88,13 @@ fn generate_fn( } out.indent(); - for opt in cmd_info.args { - let desc = opt.desc.unwrap_or_default(); - for form in opt.forms { + for opt in cmd_info.args.iter() { + let desc = if let Some(desc) = &opt.desc { + &*desc + } else { + "" + }; + for form in opt.forms.iter() { let text = util::quote_bash(format!("{}[{}]", form, desc)); out.writeln(" \\"); out.write(text); @@ -123,7 +127,7 @@ fn generate_fn( out.dedent(); out.writeln("}"); - for (sub_cmd, sub_cmd_info) in cmd_info.subcommands { + for (sub_cmd, sub_cmd_info) in cmd_info.subcommands.iter() { generate_fn( &sub_cmd, sub_cmd_info, diff --git a/src/main.rs b/src/main.rs index 25c104c..18a5a12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ enum Shell { Zsh, /// Generate completions for Bash Bash, - /// Not a shell, but output the parsed options as JSON + /// Output parsed options as JSON Json, } @@ -55,8 +55,9 @@ struct Cli { #[arg(short, long, value_delimiter = ',')] not_subcmds: Vec, - /// Shell to generate completions for - shell: Shell, + /// Shell(s) to generate completions for + #[arg(short, long, value_delimiter = ',', required = true)] + shells: Vec, } fn section_num_parser(s: &str) -> core::result::Result { @@ -74,13 +75,17 @@ fn section_num_parser(s: &str) -> core::result::Result { fn gen_shell( shell: Shell, - manpages: HashMap, + manpages: &HashMap, out_dir: &Path, ) -> Result<()> { match shell { - Shell::Zsh => ::generate_all(manpages, out_dir), - Shell::Json => ::generate_all(manpages, out_dir), - Shell::Bash => ::generate_all(manpages, out_dir), + Shell::Zsh => ::generate_all(manpages.iter(), out_dir), + Shell::Json => { + ::generate_all(manpages.iter(), out_dir) + } + Shell::Bash => { + ::generate_all(manpages.iter(), out_dir) + } } } @@ -102,6 +107,10 @@ fn main() -> Result<()> { } let res = cfg.parse()?; - gen_shell(args.shell, res, &args.out)?; + + for shell in args.shells { + gen_shell(shell, &res, &args.out)?; + } + Ok(()) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b0ddce0..6710d9a 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -12,7 +12,7 @@ use std::{ use anyhow::{anyhow, Result}; use flate2::bufread::GzDecoder; -use log::{debug, error, info, warn}; +use log::{debug, error, info, trace, warn}; use regex::Regex; #[derive(Debug)] @@ -246,7 +246,7 @@ fn filter_pages( } else { // If it's a subcommand, then it might only match the start and // have a hyphen after - mat.end() == cmd.len() || cmd.chars().nth(mat.end() + 1).unwrap() == '-' + mat.end() == cmd.len() || cmd.chars().nth(mat.end()).unwrap() == '-' } } _ => false, @@ -285,6 +285,7 @@ where P: AsRef, { let path = manpage_path.as_ref(); + trace!("Reading man page at {}", path.display()); match path.extension() { Some(ext) => { if ext == "gz" { @@ -327,7 +328,9 @@ fn get_cmd_name(manpage_path: &Path) -> String { fn detect_subcommand(cmd_name: &str, text: &str) -> Option> { let parts = cmd_name.split('-').collect::>(); let as_sub_cmd = parts.join(" "); - if text.contains(&as_sub_cmd) { + if parts.len() > 1 && parts.iter().all(|s| !s.is_empty()) && text.contains(&as_sub_cmd) + { + debug!("Detected {} as subcommand", cmd_name); Some(parts.iter().map(|s| s.to_string()).collect()) } else { None diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c06c98e..77e4e12 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -26,18 +26,21 @@ fn test() { } // The man-completions binary to test - for shell in SHELLS { - let mut cmd = Command::cargo_bin(BIN_NAME).unwrap(); - let cmd = cmd - .env("MANPATH", &in_dir) - .env( - "RUST_LOG", - env::var("RUST_LOG").unwrap_or(String::from("info")), - ) - .args([shell, "--out", &out_dir.display().to_string()]) - .stderr(Stdio::inherit()); - cmd.assert().success(); - } + let mut cmd = Command::cargo_bin(BIN_NAME).unwrap(); + let cmd = cmd + .env("MANPATH", &in_dir) + .env( + "RUST_LOG", + env::var("RUST_LOG").unwrap_or(String::from("info")), + ) + .args([ + "--shells", + &SHELLS.join(","), + "--out", + &out_dir.display().to_string(), + ]) + .stderr(Stdio::inherit()); + cmd.assert().success(); // Files that didn't get generated let mut not_generated = Vec::new();