use crate::cmd; use clap::{value_parser, Arg, ArgMatches, Command, ArgAction}; use clap_complete::{generate_to, shells}; use clap_mangen::Man; use std::{fs, io, path::{Path, PathBuf}, process}; const COMMANDS: [fn() -> clap::Command; 8] = [ cmd::bootstrap::cli, cmd::echo::cli, cmd::r#false::cli, cmd::head::cli, cmd::hostname::cli, cmd::r#true::cli, cmd::sleep::cli, crate::cli::cli, ]; #[must_use] pub fn cli() -> Command { Command::new("bootstrap") .version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher") .about("Install shitbox into the filesystem") .long_about("Install symlinks, manpages and shell completions") .args([ Arg::new("prefix") .help("The directory path under which to install") .short('p') .long("prefix") .num_args(1) .default_value("/") .required(false), Arg::new("usr") .help("Use /usr") .long_help( "Split the installation so that some applets go into /bin | /sbin\n\ while others are placed into /usr/bin | /usr/sbin", ) .short('u') .long("usr") .default_value("true") .value_parser(value_parser!(bool)), ]) .subcommands([ Command::new("all").about("Install everything"), Command::new("links") .about("Install links for each applet") .arg( Arg::new("soft") .help("Install soft links instead of hardlinks") .short('s') .long("soft"), ), Command::new("manpages") .about("Install Unix man pages") .alias("man"), Command::new("completions") .about("Install shell completions") .alias("comp") .args([ Arg::new("all") .help("Install completions for all supported shells") .short('a') .long("all") .action(ArgAction::SetTrue), Arg::new("bash") .help("Bash shell completions") .short('b') .long("bash") .action(ArgAction::SetTrue), Arg::new("fish") .help("Fish shell completions") .short('f') .long("fish") .action(ArgAction::SetTrue), Arg::new("nu") .help("Nushell completions") .short('n') .long("nu") .action(ArgAction::SetTrue), Arg::new("pwsh") .help("PowerShell completions") .short('p') .long("pwsh") .action(ArgAction::SetTrue), ]), ]) } fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> { let cmd = f(); let fname = match cmd.get_name() { "bootstrap" => "shitbox-bootstrap.1".to_string(), s => format!("{s}.1"), }; let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect(); if !outdir.exists() { fs::create_dir_all(&outdir)?; } let mut outfile = outdir; outfile.push(fname); let man = Man::new(cmd); let mut buffer: Vec = vec![]; man.render(&mut buffer)?; fs::write(&outfile, buffer)?; println!(" {}", outfile.display()); Ok(()) } fn manpages(prefix: &str) -> Result<(), io::Error> { println!("Generating Unix man pages:"); 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(()) } pub fn run(matches: &ArgMatches) { if let Some(prefix) = matches.get_one::("prefix") { match matches.subcommand() { Some(("manpages", _matches)) => { 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}"); process::exit(1); } } _ => {} } } }