Generate Bash and Fish completions

This commit is contained in:
Nathan Fisher 2022-12-21 20:19:38 -05:00
parent aa1d311745
commit 728c66fb70
4 changed files with 98 additions and 29 deletions

View File

@ -11,3 +11,9 @@ clap_complete = "4.0.6"
clap_complete_nushell = "0.1.8" clap_complete_nushell = "0.1.8"
clap_mangen = "0.2.5" clap_mangen = "0.2.5"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
[profile.release]
codegen-units = 1
strip = true
lto = true

View File

@ -1,17 +1,18 @@
use crate::cmd; use crate::cmd;
use clap::{value_parser, Arg, ArgMatches, Command}; use clap::{value_parser, Arg, ArgMatches, Command, ArgAction};
use clap_complete::{generate_to, shells};
use clap_mangen::Man; use clap_mangen::Man;
use std::{fs, io, path::PathBuf, process}; use std::{fs, io, path::{Path, PathBuf}, process};
const programs: [&str; 8] = [ const COMMANDS: [fn() -> clap::Command; 8] = [
"shitbox", cmd::bootstrap::cli,
"bootstrap", cmd::echo::cli,
"echo", cmd::r#false::cli,
"false", cmd::head::cli,
"head", cmd::hostname::cli,
"hostname", cmd::r#true::cli,
"true", cmd::sleep::cli,
"sleep", crate::cli::cli,
]; ];
#[must_use] #[must_use]
@ -60,38 +61,37 @@ pub fn cli() -> Command {
Arg::new("all") Arg::new("all")
.help("Install completions for all supported shells") .help("Install completions for all supported shells")
.short('a') .short('a')
.long("all"), .long("all")
.action(ArgAction::SetTrue),
Arg::new("bash") Arg::new("bash")
.help("Bash shell completions") .help("Bash shell completions")
.short('b') .short('b')
.long("bash"), .long("bash")
.action(ArgAction::SetTrue),
Arg::new("fish") Arg::new("fish")
.help("Fish shell completions") .help("Fish shell completions")
.short('f') .short('f')
.long("fish"), .long("fish")
.action(ArgAction::SetTrue),
Arg::new("nu") Arg::new("nu")
.help("Nushell completions") .help("Nushell completions")
.short('n') .short('n')
.long("nu"), .long("nu")
.action(ArgAction::SetTrue),
Arg::new("pwsh") Arg::new("pwsh")
.help("PowerShell completions") .help("PowerShell completions")
.short('p') .short('p')
.long("pwsh"), .long("pwsh")
.action(ArgAction::SetTrue),
]), ]),
]) ])
} }
fn manpage(prefix: &str, cmd: &str) -> Result<(), io::Error> { fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
let (fname, cmd) = match cmd { let cmd = f();
"shitbox" => ("shitbox.1", crate::cli::cli()), let fname = match cmd.get_name() {
"bootstrap" => ("shitbox-bootstrap.1", cli()), "bootstrap" => "shitbox-bootstrap.1".to_string(),
"echo" => ("echo.1", cmd::echo::cli()), s => format!("{s}.1"),
"false" => ("false.1", cmd::r#false::cli()),
"head" => ("head.1", cmd::head::cli()),
"hostname" => ("hostname.1", cmd::hostname::cli()),
"true" => ("true.1", cmd::r#true::cli()),
"sleep" => ("sleep.1", cmd::sleep::cli()),
_ => unimplemented!(),
}; };
let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect(); let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect();
if !outdir.exists() { if !outdir.exists() {
@ -109,7 +109,56 @@ fn manpage(prefix: &str, cmd: &str) -> Result<(), io::Error> {
fn manpages(prefix: &str) -> Result<(), io::Error> { fn manpages(prefix: &str) -> Result<(), io::Error> {
println!("Generating Unix man pages:"); println!("Generating Unix man pages:");
programs.iter().try_for_each(|cmd| manpage(prefix, cmd))?; COMMANDS.iter().try_for_each(|cmd| {
manpage(prefix, &cmd)
})?;
Ok(())
}
fn gen_bash_completion(outdir: &Path, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
let cmd = f();
let name = cmd.get_name();
let mut cmd = cmd.clone();
let path = generate_to(shells::Bash, &mut cmd, name, outdir)?;
println!(" {}", path.display());
Ok(())
}
fn gen_fish_completion(outdir: &Path, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
let cmd = f();
let name = cmd.get_name();
let mut cmd = cmd.clone();
let path = generate_to(shells::Fish, &mut cmd, name, outdir)?;
println!(" {}", path.display());
Ok(())
}
fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
println!("Generating completions:");
if matches.get_flag("bash") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
.iter()
.collect();
if !outdir.exists() {
fs::create_dir_all(&outdir)?;
}
COMMANDS.iter().try_for_each(|cmd| gen_bash_completion(&outdir, &cmd) )?;
}
if matches.get_flag("fish") || matches.get_flag("all") {
let outdir: PathBuf = ["target", "dist", "share", "fish", "completions"]
.iter()
.collect();
if !outdir.exists() {
fs::create_dir_all(&outdir)?;
}
COMMANDS.iter().try_for_each(|cmd| gen_fish_completion(&outdir, &cmd) )?;
}
if matches.get_flag("nu") || matches.get_flag("all") {
}
if matches.get_flag("pwsh") || matches.get_flag("all") {
}
if matches.get_flag("zsh") || matches.get_flag("all") {
}
Ok(()) Ok(())
} }
@ -117,7 +166,19 @@ pub fn run(matches: &ArgMatches) {
if let Some(prefix) = matches.get_one::<String>("prefix") { if let Some(prefix) = matches.get_one::<String>("prefix") {
match matches.subcommand() { match matches.subcommand() {
Some(("manpages", _matches)) => { Some(("manpages", _matches)) => {
if let Err(e) = manpages(&prefix) { if let Err(e) = manpages(prefix) {
eprintln!("{e}");
process::exit(1);
}
}
Some(("completions", matches)) => {
if let Err(e) = completions(prefix, matches) {
eprintln!("{e}");
process::exit(1);
}
}
Some(("all", _matches)) => {
if let Err(e) = manpages(prefix) {
eprintln!("{e}"); eprintln!("{e}");
process::exit(1); process::exit(1);
} }

View File

@ -42,6 +42,7 @@ pub fn cli() -> Command {
.short('n') .short('n')
.long("lines") .long("lines")
.default_value("10") .default_value("10")
.allow_negative_numbers(false)
.value_parser(value_parser!(usize)) .value_parser(value_parser!(usize))
]) ])
} }

View File

@ -19,6 +19,7 @@ pub fn cli() -> Command {
Arg::new("seconds") Arg::new("seconds")
.help("The number of seconds to sleep") .help("The number of seconds to sleep")
.num_args(1) .num_args(1)
.allow_negative_numbers(false)
.value_parser(value_parser!(f64)) .value_parser(value_parser!(f64))
.required(true) .required(true)
.action(ArgAction::Set) .action(ArgAction::Set)