Skip to content

Commit

Permalink
refactor: Allow taking multiple shells at once
Browse files Browse the repository at this point in the history
  • Loading branch information
ysthakur committed Aug 10, 2023
1 parent 678d4fb commit e1e8d22
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/gen/bash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct BashCompletions;

impl Completions for BashCompletions {
/// Generate a completion file for Bash
fn generate<P>(cmd_name: String, _cmd_info: CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(cmd_name: &str, _cmd_info: &CommandInfo, out_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
Expand Down
12 changes: 6 additions & 6 deletions src/gen/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<P>(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ pub use zsh::*;
use crate::parse::CommandInfo;

pub trait Completions {
fn generate<P>(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()>
where
P: AsRef<Path>;

fn generate_all<I, P>(cmds: I, out_dir: P) -> Result<()>
fn generate_all<'a, I, P>(cmds: I, out_dir: P) -> Result<()>
where
I: IntoIterator<Item = (String, CommandInfo)>,
I: Iterator<Item = (&'a String, &'a CommandInfo)>,
P: AsRef<Path>,
{
cmds.into_iter().try_for_each(|(cmd_name, cmd_info)| {
Expand Down
16 changes: 10 additions & 6 deletions src/gen/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Completions for ZshCompletions {
/// '-b[Make new branch]'
/// }
/// ```
fn generate<P>(cmd_name: String, cmd_info: CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
25 changes: 17 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -55,8 +55,9 @@ struct Cli {
#[arg(short, long, value_delimiter = ',')]
not_subcmds: Vec<String>,

/// Shell to generate completions for
shell: Shell,
/// Shell(s) to generate completions for
#[arg(short, long, value_delimiter = ',', required = true)]
shells: Vec<Shell>,
}

fn section_num_parser(s: &str) -> core::result::Result<u8, String> {
Expand All @@ -74,13 +75,17 @@ fn section_num_parser(s: &str) -> core::result::Result<u8, String> {

fn gen_shell(
shell: Shell,
manpages: HashMap<String, CommandInfo>,
manpages: &HashMap<String, CommandInfo>,
out_dir: &Path,
) -> Result<()> {
match shell {
Shell::Zsh => <ZshCompletions as Completions>::generate_all(manpages, out_dir),
Shell::Json => <JsonCompletions as Completions>::generate_all(manpages, out_dir),
Shell::Bash => <BashCompletions as Completions>::generate_all(manpages, out_dir),
Shell::Zsh => <ZshCompletions as Completions>::generate_all(manpages.iter(), out_dir),
Shell::Json => {
<JsonCompletions as Completions>::generate_all(manpages.iter(), out_dir)
}
Shell::Bash => {
<BashCompletions as Completions>::generate_all(manpages.iter(), out_dir)
}
}
}

Expand All @@ -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(())
}
9 changes: 6 additions & 3 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -285,6 +285,7 @@ where
P: AsRef<Path>,
{
let path = manpage_path.as_ref();
trace!("Reading man page at {}", path.display());
match path.extension() {
Some(ext) => {
if ext == "gz" {
Expand Down Expand Up @@ -327,7 +328,9 @@ fn get_cmd_name(manpage_path: &Path) -> String {
fn detect_subcommand(cmd_name: &str, text: &str) -> Option<Vec<String>> {
let parts = cmd_name.split('-').collect::<Vec<_>>();
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
Expand Down
27 changes: 15 additions & 12 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit e1e8d22

Please sign in to comment.