From 888cc3272b8ea0dcc62d2ccc686144e47c32e647 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 13 Feb 2023 10:38:02 -0500 Subject: [PATCH] Fix `bootstrap` for all commands --- corebox/commands/bootstrap/mod.rs | 72 ++++-- hashbox/commands/bootstrap/mod.rs | 398 ++++++++++++++++++++++++++++++ hashbox/commands/mod.rs | 5 +- utilbox/commands/bootstrap/mod.rs | 398 ++++++++++++++++++++++++++++++ utilbox/commands/mod.rs | 5 +- 5 files changed, 850 insertions(+), 28 deletions(-) create mode 100644 hashbox/commands/bootstrap/mod.rs create mode 100644 utilbox/commands/bootstrap/mod.rs diff --git a/corebox/commands/bootstrap/mod.rs b/corebox/commands/bootstrap/mod.rs index 9380c87..d4751c8 100644 --- a/corebox/commands/bootstrap/mod.rs +++ b/corebox/commands/bootstrap/mod.rs @@ -162,7 +162,7 @@ impl Cmd for Bootstrap { fs::create_dir_all(&outpath)?; println!(" mkdir: {}", outpath.display()); } - outpath.push(env!("CARGO_PKG_NAME")); + outpath.push(&shitbox::progname().unwrap()); fs::copy(&progpath, &outpath)?; println!( " install: {} -> {}", @@ -175,15 +175,15 @@ impl Cmd for Bootstrap { links(prefix, usr, matches)?; } Some(("manpages", _matches)) => { - manpages(prefix)?; + manpages(prefix, usr)?; } Some(("completions", matches)) => { - completions(prefix, matches)?; + completions(prefix, matches, usr)?; } Some(("all", matches)) => { links(prefix, usr, matches)?; - manpages(prefix)?; - completions(prefix, matches)?; + manpages(prefix, usr)?; + completions(prefix, matches, usr)?; } _ => {} } @@ -201,7 +201,7 @@ pub trait BootstrapCmd { fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error>; fn linkpath(&self, prefix: &str, usr: bool) -> Option; fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box>; - fn manpage(&self, prefix: &str) -> Result<(), io::Error>; + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error>; } impl BootstrapCmd for dyn Cmd { @@ -237,31 +237,33 @@ impl BootstrapCmd for dyn Cmd { fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box> { if let Some(linkpath) = self.linkpath(prefix, usr) { + let progname = shitbox::progname().unwrap(); if soft { let binpath = match self.path().unwrap() { - shitbox::Path::Bin => "shitbox", - shitbox::Path::Sbin => "../bin/shitbox", + shitbox::Path::Bin => progname, + shitbox::Path::Sbin => format!("../bin/{progname}"), shitbox::Path::UsrBin => { if usr { - "../../bin/shitbox" + format!("../../bin/{progname}") } else { - "shitbox" + progname } } shitbox::Path::UsrSbin => { if usr { - "../../bin/shitbox" + format!("../../bin/{progname}") } else { - "../bin/shitbox" + format!("../bin/{progname}") } } }; - symlink(binpath, &linkpath)?; + symlink(&binpath, &linkpath)?; println!(" symlink: {binpath} -> {}", linkpath.display()); } else { let mut binpath = PathBuf::from(prefix); binpath.push("bin"); binpath.push(env!("CARGO_PKG_NAME")); + println!("Generating link for {}", self.cli().get_name()); fs::hard_link(&binpath, &linkpath)?; println!(" link: {} -> {}", binpath.display(), linkpath.display()); } @@ -269,13 +271,17 @@ impl BootstrapCmd for dyn Cmd { Ok(()) } - fn manpage(&self, prefix: &str) -> Result<(), io::Error> { + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error> { let command = self.cli(); let fname = match command.get_name() { - "bootstrap" => "shitbox-bootstrap.1".to_string(), + "bootstrap" => format!("{}-bootstrap.1", shitbox::progname().unwrap()), s => format!("{s}.1"), }; - let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect(); + let outdir: PathBuf = if usr { + [prefix, "usr", "share", "man", "man1"].iter().collect() + } else { + [prefix, "share", "man", "man1"].iter().collect() + }; if !outdir.exists() { fs::create_dir_all(&outdir)?; } @@ -290,48 +296,61 @@ impl BootstrapCmd for dyn Cmd { } } -fn manpages(prefix: &str) -> Result<(), io::Error> { +fn manpages(prefix: &str, usr: bool) -> Result<(), io::Error> { println!("Generating Unix man pages:"); COMMANDS .iter() - .try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix))?; + .try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix, usr))?; Ok(()) } -fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> { +fn completions(prefix: &str, matches: &ArgMatches, usr: bool) -> Result<(), io::Error> { println!("Generating completions:"); + let basedir: PathBuf = if usr { + [prefix, "usr", "share"].iter().collect() + } else { + [prefix, "share"].iter().collect() + }; if matches.get_flag("bash") || matches.get_flag("all") { - let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"] - .iter() - .collect(); + let mut outdir = basedir.clone(); + outdir.push("bash-completion"); + outdir.push("completion"); COMMANDS.iter().try_for_each(|cmd| { let cmd = super::get(cmd).unwrap(); cmd.completion(&outdir, "bash") })?; } if matches.get_flag("fish") || matches.get_flag("all") { - let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect(); + let mut outdir = basedir.clone(); + outdir.push("fish"); + outdir.push("completions"); COMMANDS.iter().try_for_each(|cmd| { let cmd = super::get(cmd).unwrap(); cmd.completion(&outdir, "fish") })?; } if matches.get_flag("nu") || matches.get_flag("all") { - let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect(); + let mut outdir = basedir.clone(); + outdir.push("nu"); + outdir.push("completions"); COMMANDS.iter().try_for_each(|cmd| { let cmd = super::get(cmd).unwrap(); cmd.completion(&outdir, "nu") })?; } if matches.get_flag("pwsh") || matches.get_flag("all") { - let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect(); + let mut outdir = basedir.clone(); + outdir.push("pwsh"); + outdir.push("completions"); COMMANDS.iter().try_for_each(|cmd| { let cmd = super::get(cmd).unwrap(); cmd.completion(&outdir, "pwsh") })?; } if matches.get_flag("zsh") || matches.get_flag("all") { - let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect(); + let mut outdir = basedir.clone(); + outdir.push("zsh"); + outdir.push("site-functions"); COMMANDS.iter().try_for_each(|cmd| { let cmd = super::get(cmd).unwrap(); cmd.completion(&outdir, "zsh") @@ -376,3 +395,4 @@ fn links(prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box })?; Ok(()) } + diff --git a/hashbox/commands/bootstrap/mod.rs b/hashbox/commands/bootstrap/mod.rs new file mode 100644 index 0000000..d4751c8 --- /dev/null +++ b/hashbox/commands/bootstrap/mod.rs @@ -0,0 +1,398 @@ +use super::{Cmd, COMMANDS}; +use clap::{Arg, ArgAction, ArgMatches, Command}; +use clap_complete::{generate_to, shells}; +use clap_complete_nushell::Nushell; +use clap_mangen::Man; +use std::{ + error::Error, + fs, io, + os::unix::fs::symlink, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Default)] +pub struct Bootstrap; + +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") + .conflicts_with_all(["bash", "fish", "nu", "pwsh", "zsh"]) + .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") + .exclusive(true) + .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 cli(&self) -> clap::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") + .action(ArgAction::SetTrue), + Arg::new("soft") + .help("Install soft links instead of hardlinks") + .short('s') + .long("soft") + .action(ArgAction::SetTrue), + ]) + .subcommands([ + Self::all(), + Self::links(), + Self::manpages(), + Self::completions(), + ]) + } + + fn run(&self, matches: &ArgMatches) -> Result<(), Box> { + if let Some(prefix) = matches.get_one::("prefix") { + let usr = matches.get_flag("usr"); + if let Some(progpath) = shitbox::progpath() { + let mut outpath = PathBuf::from(prefix); + outpath.push("bin"); + println!("Installing binary:"); + if !outpath.exists() { + fs::create_dir_all(&outpath)?; + println!(" mkdir: {}", outpath.display()); + } + outpath.push(&shitbox::progname().unwrap()); + fs::copy(&progpath, &outpath)?; + println!( + " install: {} -> {}", + progpath.display(), + outpath.display() + ); + } + match matches.subcommand() { + Some(("links", matches)) => { + links(prefix, usr, matches)?; + } + Some(("manpages", _matches)) => { + manpages(prefix, usr)?; + } + Some(("completions", matches)) => { + completions(prefix, matches, usr)?; + } + Some(("all", matches)) => { + links(prefix, usr, matches)?; + manpages(prefix, usr)?; + completions(prefix, matches, usr)?; + } + _ => {} + } + } + Ok(()) + } + + fn path(&self) -> Option { + None + } +} + +#[allow(clippy::module_name_repetitions)] +pub trait BootstrapCmd { + fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error>; + fn linkpath(&self, prefix: &str, usr: bool) -> Option; + fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box>; + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error>; +} + +impl BootstrapCmd for dyn Cmd { + fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error> { + let cmd = self.cli(); + let name = cmd.get_name(); + let mut cmd = self.cli(); + if !outdir.exists() { + fs::create_dir_all(outdir)?; + } + let path = match gen { + "bash" => generate_to(shells::Bash, &mut cmd, name, outdir)?, + "fish" => generate_to(shells::Fish, &mut cmd, name, outdir)?, + "nu" => generate_to(Nushell, &mut cmd, name, outdir)?, + "pwsh" => generate_to(shells::PowerShell, &mut cmd, name, outdir)?, + "zsh" => generate_to(shells::Zsh, &mut cmd, name, outdir)?, + _ => unimplemented!(), + }; + println!(" {}", path.display()); + Ok(()) + } + + fn linkpath(&self, prefix: &str, usr: bool) -> Option { + let mut path = PathBuf::from(prefix); + let binpath = self.path(); + match binpath { + Some(p) => path.push(p.to_str(usr)), + None => return None, + } + path.push(self.cli().get_name()); + Some(path) + } + + fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box> { + if let Some(linkpath) = self.linkpath(prefix, usr) { + let progname = shitbox::progname().unwrap(); + if soft { + let binpath = match self.path().unwrap() { + shitbox::Path::Bin => progname, + shitbox::Path::Sbin => format!("../bin/{progname}"), + shitbox::Path::UsrBin => { + if usr { + format!("../../bin/{progname}") + } else { + progname + } + } + shitbox::Path::UsrSbin => { + if usr { + format!("../../bin/{progname}") + } else { + format!("../bin/{progname}") + } + } + }; + symlink(&binpath, &linkpath)?; + println!(" symlink: {binpath} -> {}", linkpath.display()); + } else { + let mut binpath = PathBuf::from(prefix); + binpath.push("bin"); + binpath.push(env!("CARGO_PKG_NAME")); + println!("Generating link for {}", self.cli().get_name()); + fs::hard_link(&binpath, &linkpath)?; + println!(" link: {} -> {}", binpath.display(), linkpath.display()); + } + } + Ok(()) + } + + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error> { + let command = self.cli(); + let fname = match command.get_name() { + "bootstrap" => format!("{}-bootstrap.1", shitbox::progname().unwrap()), + s => format!("{s}.1"), + }; + let outdir: PathBuf = if usr { + [prefix, "usr", "share", "man", "man1"].iter().collect() + } else { + [prefix, "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 manpages(prefix: &str, usr: bool) -> Result<(), io::Error> { + println!("Generating Unix man pages:"); + COMMANDS + .iter() + .try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix, usr))?; + Ok(()) +} + +fn completions(prefix: &str, matches: &ArgMatches, usr: bool) -> Result<(), io::Error> { + println!("Generating completions:"); + let basedir: PathBuf = if usr { + [prefix, "usr", "share"].iter().collect() + } else { + [prefix, "share"].iter().collect() + }; + if matches.get_flag("bash") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("bash-completion"); + outdir.push("completion"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "bash") + })?; + } + if matches.get_flag("fish") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("fish"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "fish") + })?; + } + if matches.get_flag("nu") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("nu"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "nu") + })?; + } + if matches.get_flag("pwsh") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("pwsh"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "pwsh") + })?; + } + if matches.get_flag("zsh") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("zsh"); + outdir.push("site-functions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "zsh") + })?; + } + Ok(()) +} + +fn links(prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box> { + println!("Generating links:"); + let mut binpath = PathBuf::from(prefix); + binpath.push("bin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + binpath.pop(); + binpath.push("sbin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + if usr { + binpath.pop(); + binpath.push("usr"); + binpath.push("bin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + binpath.pop(); + binpath.push("sbin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + } + let soft = cmd.get_flag("soft"); + COMMANDS.iter().try_for_each(|c| { + let cmd = super::get(c).unwrap(); + cmd.link(prefix, usr, soft) + })?; + Ok(()) +} + diff --git a/hashbox/commands/mod.rs b/hashbox/commands/mod.rs index 056e88e..9e1cb15 100644 --- a/hashbox/commands/mod.rs +++ b/hashbox/commands/mod.rs @@ -1,6 +1,7 @@ use shitbox::Cmd; mod b2sum; +mod bootstrap; mod hashbox; mod md5sum; mod sha1sum; @@ -15,6 +16,7 @@ mod sha512sum; pub fn get(name: &str) -> Option> { match name { "b2sum" => Some(Box::new(b2sum::B2sum::default())), + "bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())), "hashbox" => Some(Box::new(hashbox::Hashbox::default())), "md5sum" => Some(Box::new(md5sum::Md5sum::default())), "sha1sum" => Some(Box::new(sha1sum::Sha1sum::default())), @@ -26,8 +28,9 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 7] = [ +pub static COMMANDS: [&str; 8] = [ "b2sum", + "bootstrap", "md5sum", "sha1sum", "sha224sum", diff --git a/utilbox/commands/bootstrap/mod.rs b/utilbox/commands/bootstrap/mod.rs new file mode 100644 index 0000000..d4751c8 --- /dev/null +++ b/utilbox/commands/bootstrap/mod.rs @@ -0,0 +1,398 @@ +use super::{Cmd, COMMANDS}; +use clap::{Arg, ArgAction, ArgMatches, Command}; +use clap_complete::{generate_to, shells}; +use clap_complete_nushell::Nushell; +use clap_mangen::Man; +use std::{ + error::Error, + fs, io, + os::unix::fs::symlink, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Default)] +pub struct Bootstrap; + +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") + .conflicts_with_all(["bash", "fish", "nu", "pwsh", "zsh"]) + .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") + .exclusive(true) + .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 cli(&self) -> clap::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") + .action(ArgAction::SetTrue), + Arg::new("soft") + .help("Install soft links instead of hardlinks") + .short('s') + .long("soft") + .action(ArgAction::SetTrue), + ]) + .subcommands([ + Self::all(), + Self::links(), + Self::manpages(), + Self::completions(), + ]) + } + + fn run(&self, matches: &ArgMatches) -> Result<(), Box> { + if let Some(prefix) = matches.get_one::("prefix") { + let usr = matches.get_flag("usr"); + if let Some(progpath) = shitbox::progpath() { + let mut outpath = PathBuf::from(prefix); + outpath.push("bin"); + println!("Installing binary:"); + if !outpath.exists() { + fs::create_dir_all(&outpath)?; + println!(" mkdir: {}", outpath.display()); + } + outpath.push(&shitbox::progname().unwrap()); + fs::copy(&progpath, &outpath)?; + println!( + " install: {} -> {}", + progpath.display(), + outpath.display() + ); + } + match matches.subcommand() { + Some(("links", matches)) => { + links(prefix, usr, matches)?; + } + Some(("manpages", _matches)) => { + manpages(prefix, usr)?; + } + Some(("completions", matches)) => { + completions(prefix, matches, usr)?; + } + Some(("all", matches)) => { + links(prefix, usr, matches)?; + manpages(prefix, usr)?; + completions(prefix, matches, usr)?; + } + _ => {} + } + } + Ok(()) + } + + fn path(&self) -> Option { + None + } +} + +#[allow(clippy::module_name_repetitions)] +pub trait BootstrapCmd { + fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error>; + fn linkpath(&self, prefix: &str, usr: bool) -> Option; + fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box>; + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error>; +} + +impl BootstrapCmd for dyn Cmd { + fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error> { + let cmd = self.cli(); + let name = cmd.get_name(); + let mut cmd = self.cli(); + if !outdir.exists() { + fs::create_dir_all(outdir)?; + } + let path = match gen { + "bash" => generate_to(shells::Bash, &mut cmd, name, outdir)?, + "fish" => generate_to(shells::Fish, &mut cmd, name, outdir)?, + "nu" => generate_to(Nushell, &mut cmd, name, outdir)?, + "pwsh" => generate_to(shells::PowerShell, &mut cmd, name, outdir)?, + "zsh" => generate_to(shells::Zsh, &mut cmd, name, outdir)?, + _ => unimplemented!(), + }; + println!(" {}", path.display()); + Ok(()) + } + + fn linkpath(&self, prefix: &str, usr: bool) -> Option { + let mut path = PathBuf::from(prefix); + let binpath = self.path(); + match binpath { + Some(p) => path.push(p.to_str(usr)), + None => return None, + } + path.push(self.cli().get_name()); + Some(path) + } + + fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box> { + if let Some(linkpath) = self.linkpath(prefix, usr) { + let progname = shitbox::progname().unwrap(); + if soft { + let binpath = match self.path().unwrap() { + shitbox::Path::Bin => progname, + shitbox::Path::Sbin => format!("../bin/{progname}"), + shitbox::Path::UsrBin => { + if usr { + format!("../../bin/{progname}") + } else { + progname + } + } + shitbox::Path::UsrSbin => { + if usr { + format!("../../bin/{progname}") + } else { + format!("../bin/{progname}") + } + } + }; + symlink(&binpath, &linkpath)?; + println!(" symlink: {binpath} -> {}", linkpath.display()); + } else { + let mut binpath = PathBuf::from(prefix); + binpath.push("bin"); + binpath.push(env!("CARGO_PKG_NAME")); + println!("Generating link for {}", self.cli().get_name()); + fs::hard_link(&binpath, &linkpath)?; + println!(" link: {} -> {}", binpath.display(), linkpath.display()); + } + } + Ok(()) + } + + fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error> { + let command = self.cli(); + let fname = match command.get_name() { + "bootstrap" => format!("{}-bootstrap.1", shitbox::progname().unwrap()), + s => format!("{s}.1"), + }; + let outdir: PathBuf = if usr { + [prefix, "usr", "share", "man", "man1"].iter().collect() + } else { + [prefix, "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 manpages(prefix: &str, usr: bool) -> Result<(), io::Error> { + println!("Generating Unix man pages:"); + COMMANDS + .iter() + .try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix, usr))?; + Ok(()) +} + +fn completions(prefix: &str, matches: &ArgMatches, usr: bool) -> Result<(), io::Error> { + println!("Generating completions:"); + let basedir: PathBuf = if usr { + [prefix, "usr", "share"].iter().collect() + } else { + [prefix, "share"].iter().collect() + }; + if matches.get_flag("bash") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("bash-completion"); + outdir.push("completion"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "bash") + })?; + } + if matches.get_flag("fish") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("fish"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "fish") + })?; + } + if matches.get_flag("nu") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("nu"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "nu") + })?; + } + if matches.get_flag("pwsh") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("pwsh"); + outdir.push("completions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "pwsh") + })?; + } + if matches.get_flag("zsh") || matches.get_flag("all") { + let mut outdir = basedir.clone(); + outdir.push("zsh"); + outdir.push("site-functions"); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = super::get(cmd).unwrap(); + cmd.completion(&outdir, "zsh") + })?; + } + Ok(()) +} + +fn links(prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box> { + println!("Generating links:"); + let mut binpath = PathBuf::from(prefix); + binpath.push("bin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + binpath.pop(); + binpath.push("sbin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + if usr { + binpath.pop(); + binpath.push("usr"); + binpath.push("bin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + binpath.pop(); + binpath.push("sbin"); + if !binpath.exists() { + fs::create_dir_all(&binpath)?; + println!(" mkdir: {}", binpath.display()); + } + } + let soft = cmd.get_flag("soft"); + COMMANDS.iter().try_for_each(|c| { + let cmd = super::get(c).unwrap(); + cmd.link(prefix, usr, soft) + })?; + Ok(()) +} + diff --git a/utilbox/commands/mod.rs b/utilbox/commands/mod.rs index 35d573a..89ab2ff 100644 --- a/utilbox/commands/mod.rs +++ b/utilbox/commands/mod.rs @@ -1,6 +1,7 @@ use shitbox::Cmd; mod blkid; +mod bootstrap; mod clear; mod mount; mod mountpoint; @@ -15,6 +16,7 @@ mod utilbox; #[allow(clippy::box_default)] pub fn get(name: &str) -> Option> { match name { + "bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())), "clear" => Some(Box::new(clear::Clear::default())), "mountpoint" => Some(Box::new(mountpoint::Mountpoint::default())), "swaplabel" => Some(Box::new(swaplabel::Swaplabel::default())), @@ -25,7 +27,8 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 6] = [ +pub static COMMANDS: [&str; 7] = [ + "bootstrap", "clear", "mountpoint", "swaplabel",