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();