Cleanups including creating a static list of commands

This commit is contained in:
Nathan Fisher 2022-12-25 23:50:37 -05:00
parent 815a98be4d
commit 48d42e7b3c
11 changed files with 194 additions and 140 deletions

View File

@ -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::{Arg, ArgAction, ArgMatches, Command};
use clap_complete::{generate_to, shells, Generator}; use clap_complete::shells;
use clap_complete_nushell::Nushell; use clap_complete_nushell::Nushell;
use clap_mangen::Man;
use std::{ use std::{
error::Error, error::Error,
fs, fs,
io::{self, ErrorKind}, io::{self, ErrorKind},
path::{Path, PathBuf}, path::PathBuf,
}; };
#[derive(Debug)]
pub struct Bootstrap { pub struct Bootstrap {
name: &'static str, name: &'static str,
path: Option<crate::Path>, path: Option<crate::Path>,
@ -20,6 +20,104 @@ pub const BOOTSTRAP: Bootstrap = Bootstrap {
path: None, 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 { impl Cmd for Bootstrap {
fn name(&self) -> &str { fn name(&self) -> &str {
self.name self.name
@ -55,91 +153,10 @@ impl Cmd for Bootstrap {
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
]) ])
.subcommands([ .subcommands([
Command::new("all").about("Install everything") Self::all(),
.args([ Self::links(),
Arg::new("soft") Self::manpages(),
.help("Install soft links instead of hardlinks") Self::completions(),
.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),
]),
]) ])
} }
@ -150,11 +167,9 @@ impl Cmd for Bootstrap {
return Err(io::Error::new(ErrorKind::Other, "No input").into()); return Err(io::Error::new(ErrorKind::Other, "No input").into());
}; };
if let Some(prefix) = matches.get_one::<String>("prefix") { if let Some(prefix) = matches.get_one::<String>("prefix") {
let commands: Commands = Commands { let commands = super::COMMANDS
items: vec![ .get()
&BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX, .ok_or_else(|| io::Error::new(ErrorKind::Other, "Cannot get commands list"))?;
],
};
let usr = matches.get_flag("usr"); let usr = matches.get_flag("usr");
if let Some(progpath) = crate::progpath() { if let Some(progpath) = crate::progpath() {
let mut outpath = PathBuf::from(prefix); let mut outpath = PathBuf::from(prefix);
@ -166,7 +181,11 @@ impl Cmd for Bootstrap {
} }
outpath.push(env!("CARGO_PKG_NAME")); outpath.push(env!("CARGO_PKG_NAME"));
fs::copy(&progpath, &outpath)?; fs::copy(&progpath, &outpath)?;
println!(" install: {} -> {}", progpath.display(), outpath.display()); println!(
" install: {} -> {}",
progpath.display(),
outpath.display()
);
} }
match matches.subcommand() { match matches.subcommand() {
Some(("links", matches)) => { Some(("links", matches)) => {
@ -194,47 +213,16 @@ impl Cmd for Bootstrap {
} }
} }
struct Commands<'a> { pub trait Bootstrappable {
items: Vec<&'a dyn Cmd>, 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<dyn Error>>;
} }
impl<'a> Commands<'a> { impl<'a> Bootstrappable for Commands<'a> {
fn manpages(&self, prefix: &str) -> Result<(), io::Error> { fn manpages(&self, prefix: &str) -> Result<(), io::Error> {
println!("Generating Unix man pages:"); println!("Generating Unix man pages:");
self.items self.items.iter().try_for_each(|cmd| cmd.manpage(prefix))?;
.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<u8> = 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());
Ok(()) Ok(())
} }

View File

@ -3,6 +3,7 @@ use crate::Path;
use clap::{Arg, Command}; use clap::{Arg, Command};
use std::{env, error::Error}; use std::{env, error::Error};
#[derive(Debug)]
pub struct Echo { pub struct Echo {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -3,6 +3,7 @@ use crate::Path;
use clap::Command; use clap::Command;
use std::{error::Error, process}; use std::{error::Error, process};
#[derive(Debug)]
pub struct False { pub struct False {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -8,6 +8,7 @@ use std::{
process, process,
}; };
#[derive(Debug)]
pub struct Head { pub struct Head {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -3,6 +3,7 @@ use crate::Path;
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use std::{error::Error, io}; use std::{error::Error, io};
#[derive(Debug)]
pub struct Hostname { pub struct Hostname {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -1,5 +1,13 @@
use clap::ArgMatches; 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; pub mod bootstrap;
mod cat; mod cat;
@ -29,14 +37,35 @@ pub use {
self::hostname::{Hostname, HOSTNAME}, self::hostname::{Hostname, HOSTNAME},
bootstrap::{Bootstrap, BOOTSTRAP}, bootstrap::{Bootstrap, BOOTSTRAP},
echo::{Echo, ECHO}, echo::{Echo, ECHO},
r#false::{False, FALSE},
head::{Head, HEAD}, head::{Head, HEAD},
nologin::{Nologin, NOLOGIN}, nologin::{Nologin, NOLOGIN},
r#false::{False, FALSE},
r#true::{True, TRUE}, r#true::{True, TRUE},
shitbox::{Shitbox, SHITBOX}, shitbox::{Shitbox, SHITBOX},
sleep::{Sleep, SLEEP}, sleep::{Sleep, SLEEP},
}; };
pub trait Cmd {
#[derive(Debug)]
pub struct Commands<'a> {
pub items: Vec<&'a dyn Cmd>,
}
pub static COMMANDS: OnceCell<Commands> = 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 name(&self) -> &str;
fn cli(&self) -> clap::Command; fn cli(&self) -> clap::Command;
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>>; fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>>;
@ -87,8 +116,8 @@ pub trait Cmd {
} }
} }
}; };
symlink(&binpath, &linkpath)?; symlink(binpath, &linkpath)?;
println!(" symlink: {} -> {}", binpath, linkpath.display()); println!(" symlink: {binpath} -> {}", linkpath.display());
} else { } else {
let mut binpath = PathBuf::from(prefix); let mut binpath = PathBuf::from(prefix);
binpath.push("bin"); binpath.push("bin");
@ -99,4 +128,24 @@ pub trait Cmd {
} }
Ok(()) 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<u8> = vec![];
man.render(&mut buffer)?;
fs::write(&outfile, buffer)?;
println!(" {}", outfile.display());
Ok(())
}
} }

View File

@ -1,7 +1,8 @@
use super::Cmd;
use clap::Command; use clap::Command;
use std::process; use std::process;
use super::Cmd;
#[derive(Debug)]
pub struct Nologin { pub struct Nologin {
name: &'static str, name: &'static str,
path: Option<crate::Path>, path: Option<crate::Path>,

View File

@ -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 clap::Command;
use std::{ use std::{
error::Error, error::Error,
io::{self, ErrorKind}, io::{self, ErrorKind},
}; };
#[derive(Debug)]
pub struct Shitbox { pub struct Shitbox {
name: &'static str, name: &'static str,
path: Option<crate::Path>, path: Option<crate::Path>,

View File

@ -3,6 +3,7 @@ use crate::Path;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use std::{env, error::Error, thread, time::Duration}; use std::{env, error::Error, thread, time::Duration};
#[derive(Debug)]
pub struct Sleep { pub struct Sleep {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -3,6 +3,7 @@ use crate::Path;
use clap::Command; use clap::Command;
use std::{error::Error, process}; use std::{error::Error, process};
#[derive(Debug)]
pub struct True { pub struct True {
name: &'static str, name: &'static str,
path: Option<Path>, path: Option<Path>,

View File

@ -2,7 +2,7 @@
use std::{env, error::Error, path::PathBuf, string::ToString}; use std::{env, error::Error, path::PathBuf, string::ToString};
pub mod cmd; 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)] #[derive(Debug, Clone, Copy)]
pub enum Path { pub enum Path {
@ -33,6 +33,15 @@ pub fn progpath() -> Option<PathBuf> {
} }
pub fn run() -> Result<(), Box<dyn Error>> { pub fn run() -> Result<(), Box<dyn Error>> {
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() { if let Some(progname) = progname() {
match progname.as_str() { match progname.as_str() {
"echo" => ECHO.run(None)?, "echo" => ECHO.run(None)?,