From 48d42e7b3c93c46bf1ec8b3b73e7d60d8b389279 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sun, 25 Dec 2022 23:50:37 -0500 Subject: [PATCH] Cleanups including creating a static list of commands --- src/cmd/bootstrap/mod.rs | 252 +++++++++++++++++++-------------------- src/cmd/echo/mod.rs | 1 + src/cmd/false/mod.rs | 1 + src/cmd/head/mod.rs | 1 + src/cmd/hostname/mod.rs | 1 + src/cmd/mod.rs | 59 ++++++++- src/cmd/nologin/mod.rs | 3 +- src/cmd/shitbox/mod.rs | 3 +- src/cmd/sleep/mod.rs | 1 + src/cmd/true/mod.rs | 1 + src/lib.rs | 11 +- 11 files changed, 194 insertions(+), 140 deletions(-) diff --git a/src/cmd/bootstrap/mod.rs b/src/cmd/bootstrap/mod.rs index b669421..8f1cf93 100644 --- a/src/cmd/bootstrap/mod.rs +++ b/src/cmd/bootstrap/mod.rs @@ -1,15 +1,15 @@ -use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE, NOLOGIN}; +use super::{Cmd, Commands}; use clap::{Arg, ArgAction, ArgMatches, Command}; -use clap_complete::{generate_to, shells, Generator}; +use clap_complete::shells; use clap_complete_nushell::Nushell; -use clap_mangen::Man; use std::{ error::Error, fs, io::{self, ErrorKind}, - path::{Path, PathBuf}, + path::PathBuf, }; +#[derive(Debug)] pub struct Bootstrap { name: &'static str, path: Option, @@ -20,6 +20,104 @@ pub const BOOTSTRAP: Bootstrap = Bootstrap { path: None, }; +impl Bootstrap { + fn all() -> clap::Command { + Command::new("all").about("Install everything").args([ + Arg::new("soft") + .help("Install soft links instead of hardlinks") + .short('s') + .long("soft") + .action(ArgAction::SetTrue), + 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), + Arg::new("zsh") + .help("Zshell completions") + .short('z') + .long("zsh") + .action(ArgAction::SetTrue), + ]) + } + + fn links() -> clap::Command { + Command::new("links") + .about("Install links for each applet") + .arg( + Arg::new("soft") + .help("Install soft links instead of hardlinks") + .short('s') + .long("soft") + .action(ArgAction::SetTrue), + ) + } + + fn manpages() -> clap::Command { + Command::new("manpages") + .about("Install Unix man pages") + .alias("man") + } + + fn completions() -> clap::Command { + 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), + Arg::new("zsh") + .help("Zshell completions") + .short('z') + .long("zsh") + .action(ArgAction::SetTrue), + ]) + } +} + impl Cmd for Bootstrap { fn name(&self) -> &str { self.name @@ -55,91 +153,10 @@ impl Cmd for Bootstrap { .action(ArgAction::SetTrue), ]) .subcommands([ - Command::new("all").about("Install everything") - .args([ - Arg::new("soft") - .help("Install soft links instead of hardlinks") - .short('s') - .long("soft") - .action(ArgAction::SetTrue), - 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), - Arg::new("zsh") - .help("Zshell completions") - .short('z') - .long("zsh") - .action(ArgAction::SetTrue), - ]), - Command::new("links") - .about("Install links for each applet") - .arg( - Arg::new("soft") - .help("Install soft links instead of hardlinks") - .short('s') - .long("soft") - .action(ArgAction::SetTrue), - ), - 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), - Arg::new("zsh") - .help("Zshell completions") - .short('z') - .long("zsh") - .action(ArgAction::SetTrue), - ]), + Self::all(), + Self::links(), + Self::manpages(), + Self::completions(), ]) } @@ -150,11 +167,9 @@ impl Cmd for Bootstrap { return Err(io::Error::new(ErrorKind::Other, "No input").into()); }; if let Some(prefix) = matches.get_one::("prefix") { - let commands: Commands = Commands { - items: vec![ - &BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX, - ], - }; + let commands = super::COMMANDS + .get() + .ok_or_else(|| io::Error::new(ErrorKind::Other, "Cannot get commands list"))?; let usr = matches.get_flag("usr"); if let Some(progpath) = crate::progpath() { let mut outpath = PathBuf::from(prefix); @@ -166,7 +181,11 @@ impl Cmd for Bootstrap { } outpath.push(env!("CARGO_PKG_NAME")); fs::copy(&progpath, &outpath)?; - println!(" install: {} -> {}", progpath.display(), outpath.display()); + println!( + " install: {} -> {}", + progpath.display(), + outpath.display() + ); } match matches.subcommand() { Some(("links", matches)) => { @@ -194,47 +213,16 @@ impl Cmd for Bootstrap { } } -struct Commands<'a> { - items: Vec<&'a dyn Cmd>, +pub trait Bootstrappable { + fn manpages(&self, prefix: &str) -> Result<(), io::Error>; + fn completions(&self, prefix: &str, matches: &ArgMatches) -> Result<(), io::Error>; + fn links(&self, prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box>; } -impl<'a> Commands<'a> { +impl<'a> Bootstrappable for Commands<'a> { fn manpages(&self, prefix: &str) -> Result<(), io::Error> { println!("Generating Unix man pages:"); - self.items - .iter() - .try_for_each(|cmd| Self::manpage(prefix, cmd))?; - Ok(()) - } - - fn manpage(prefix: &str, cmd: &&dyn Cmd) -> Result<(), io::Error> { - let command = cmd.cli(); - let fname = match cmd.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(command); - let mut buffer: Vec = vec![]; - man.render(&mut buffer)?; - fs::write(&outfile, buffer)?; - println!(" {}", outfile.display()); - Ok(()) - } - - fn completion(outdir: &Path, cmd: &&dyn Cmd, gen: impl Generator) -> Result<(), io::Error> { - let name = cmd.name(); - let mut cmd = cmd.cli(); - if !outdir.exists() { - fs::create_dir_all(outdir)?; - } - let path = generate_to(gen, &mut cmd, name, outdir)?; - println!(" {}", path.display()); + self.items.iter().try_for_each(|cmd| cmd.manpage(prefix))?; Ok(()) } diff --git a/src/cmd/echo/mod.rs b/src/cmd/echo/mod.rs index 099e840..3e848ed 100644 --- a/src/cmd/echo/mod.rs +++ b/src/cmd/echo/mod.rs @@ -3,6 +3,7 @@ use crate::Path; use clap::{Arg, Command}; use std::{env, error::Error}; +#[derive(Debug)] pub struct Echo { name: &'static str, path: Option, diff --git a/src/cmd/false/mod.rs b/src/cmd/false/mod.rs index bf1a861..510e120 100644 --- a/src/cmd/false/mod.rs +++ b/src/cmd/false/mod.rs @@ -3,6 +3,7 @@ use crate::Path; use clap::Command; use std::{error::Error, process}; +#[derive(Debug)] pub struct False { name: &'static str, path: Option, diff --git a/src/cmd/head/mod.rs b/src/cmd/head/mod.rs index c436350..472336d 100644 --- a/src/cmd/head/mod.rs +++ b/src/cmd/head/mod.rs @@ -8,6 +8,7 @@ use std::{ process, }; +#[derive(Debug)] pub struct Head { name: &'static str, path: Option, diff --git a/src/cmd/hostname/mod.rs b/src/cmd/hostname/mod.rs index 0464e4f..f38301c 100644 --- a/src/cmd/hostname/mod.rs +++ b/src/cmd/hostname/mod.rs @@ -3,6 +3,7 @@ use crate::Path; use clap::{Arg, ArgAction, ArgMatches, Command}; use std::{error::Error, io}; +#[derive(Debug)] pub struct Hostname { name: &'static str, path: Option, diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index a1e74cf..9f41f0c 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,5 +1,13 @@ use clap::ArgMatches; -use std::{error::Error, fs, os::unix::fs::symlink, path::PathBuf}; +use clap_complete::{generate_to, Generator}; +use clap_mangen::Man; +use once_cell::sync::OnceCell; +use std::{ + error::Error, + fmt, fs, io, + os::unix::fs::symlink, + path::{Path, PathBuf}, +}; pub mod bootstrap; mod cat; @@ -29,14 +37,35 @@ pub use { self::hostname::{Hostname, HOSTNAME}, bootstrap::{Bootstrap, BOOTSTRAP}, echo::{Echo, ECHO}, - r#false::{False, FALSE}, head::{Head, HEAD}, nologin::{Nologin, NOLOGIN}, + r#false::{False, FALSE}, r#true::{True, TRUE}, shitbox::{Shitbox, SHITBOX}, sleep::{Sleep, SLEEP}, }; -pub trait Cmd { + +#[derive(Debug)] +pub struct Commands<'a> { + pub items: Vec<&'a dyn Cmd>, +} + +pub static COMMANDS: OnceCell = OnceCell::new(); + +impl<'a> Commands<'a> { + fn completion(outdir: &Path, cmd: &&dyn Cmd, gen: impl Generator) -> Result<(), io::Error> { + let name = cmd.name(); + let mut cmd = cmd.cli(); + if !outdir.exists() { + fs::create_dir_all(outdir)?; + } + let path = generate_to(gen, &mut cmd, name, outdir)?; + println!(" {}", path.display()); + Ok(()) + } +} + +pub trait Cmd: fmt::Debug + Sync { fn name(&self) -> &str; fn cli(&self) -> clap::Command; fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box>; @@ -87,8 +116,8 @@ pub trait Cmd { } } }; - symlink(&binpath, &linkpath)?; - println!(" symlink: {} -> {}", binpath, linkpath.display()); + symlink(binpath, &linkpath)?; + println!(" symlink: {binpath} -> {}", linkpath.display()); } else { let mut binpath = PathBuf::from(prefix); binpath.push("bin"); @@ -99,4 +128,24 @@ pub trait Cmd { } Ok(()) } + + fn manpage(&self, prefix: &str) -> Result<(), io::Error> { + let command = self.cli(); + let fname = match self.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(command); + let mut buffer: Vec = vec![]; + man.render(&mut buffer)?; + fs::write(&outfile, buffer)?; + println!(" {}", outfile.display()); + Ok(()) + } } diff --git a/src/cmd/nologin/mod.rs b/src/cmd/nologin/mod.rs index a82a9cf..a3ece9e 100644 --- a/src/cmd/nologin/mod.rs +++ b/src/cmd/nologin/mod.rs @@ -1,7 +1,8 @@ +use super::Cmd; use clap::Command; use std::process; -use super::Cmd; +#[derive(Debug)] pub struct Nologin { name: &'static str, path: Option, diff --git a/src/cmd/shitbox/mod.rs b/src/cmd/shitbox/mod.rs index 5685e83..029547e 100644 --- a/src/cmd/shitbox/mod.rs +++ b/src/cmd/shitbox/mod.rs @@ -1,10 +1,11 @@ -use super::{Cmd, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, SLEEP, TRUE, NOLOGIN}; +use super::{Cmd, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, NOLOGIN, SLEEP, TRUE}; use clap::Command; use std::{ error::Error, io::{self, ErrorKind}, }; +#[derive(Debug)] pub struct Shitbox { name: &'static str, path: Option, diff --git a/src/cmd/sleep/mod.rs b/src/cmd/sleep/mod.rs index 30ce5e5..799a4d1 100644 --- a/src/cmd/sleep/mod.rs +++ b/src/cmd/sleep/mod.rs @@ -3,6 +3,7 @@ use crate::Path; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use std::{env, error::Error, thread, time::Duration}; +#[derive(Debug)] pub struct Sleep { name: &'static str, path: Option, diff --git a/src/cmd/true/mod.rs b/src/cmd/true/mod.rs index 3744e2b..18c36fe 100644 --- a/src/cmd/true/mod.rs +++ b/src/cmd/true/mod.rs @@ -3,6 +3,7 @@ use crate::Path; use clap::Command; use std::{error::Error, process}; +#[derive(Debug)] pub struct True { name: &'static str, path: Option, diff --git a/src/lib.rs b/src/lib.rs index 5fb2f03..1629b13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use std::{env, error::Error, path::PathBuf, string::ToString}; pub mod cmd; -use cmd::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE, NOLOGIN}; +use cmd::{Cmd, Commands, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, NOLOGIN, SHITBOX, SLEEP, TRUE}; #[derive(Debug, Clone, Copy)] pub enum Path { @@ -33,6 +33,15 @@ pub fn progpath() -> Option { } pub fn run() -> Result<(), Box> { + if cmd::COMMANDS.get().is_none() { + cmd::COMMANDS + .set(Commands { + items: vec![ + &BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX, + ], + }) + .expect("Cannot register commands"); + } if let Some(progname) = progname() { match progname.as_str() { "echo" => ECHO.run(None)?,