Fix bootstrap for all commands

This commit is contained in:
Nathan Fisher 2023-02-13 10:38:02 -05:00
parent 423592973f
commit 888cc3272b
5 changed files with 850 additions and 28 deletions

View File

@ -162,7 +162,7 @@ impl Cmd for Bootstrap {
fs::create_dir_all(&outpath)?; fs::create_dir_all(&outpath)?;
println!(" mkdir: {}", outpath.display()); println!(" mkdir: {}", outpath.display());
} }
outpath.push(env!("CARGO_PKG_NAME")); outpath.push(&shitbox::progname().unwrap());
fs::copy(&progpath, &outpath)?; fs::copy(&progpath, &outpath)?;
println!( println!(
" install: {} -> {}", " install: {} -> {}",
@ -175,15 +175,15 @@ impl Cmd for Bootstrap {
links(prefix, usr, matches)?; links(prefix, usr, matches)?;
} }
Some(("manpages", _matches)) => { Some(("manpages", _matches)) => {
manpages(prefix)?; manpages(prefix, usr)?;
} }
Some(("completions", matches)) => { Some(("completions", matches)) => {
completions(prefix, matches)?; completions(prefix, matches, usr)?;
} }
Some(("all", matches)) => { Some(("all", matches)) => {
links(prefix, usr, matches)?; links(prefix, usr, matches)?;
manpages(prefix)?; manpages(prefix, usr)?;
completions(prefix, matches)?; completions(prefix, matches, usr)?;
} }
_ => {} _ => {}
} }
@ -201,7 +201,7 @@ pub trait BootstrapCmd {
fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error>; fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error>;
fn linkpath(&self, prefix: &str, usr: bool) -> Option<PathBuf>; fn linkpath(&self, prefix: &str, usr: bool) -> Option<PathBuf>;
fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box<dyn Error>>; fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box<dyn Error>>;
fn manpage(&self, prefix: &str) -> Result<(), io::Error>; fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error>;
} }
impl BootstrapCmd for dyn Cmd { 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<dyn Error>> { fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box<dyn Error>> {
if let Some(linkpath) = self.linkpath(prefix, usr) { if let Some(linkpath) = self.linkpath(prefix, usr) {
let progname = shitbox::progname().unwrap();
if soft { if soft {
let binpath = match self.path().unwrap() { let binpath = match self.path().unwrap() {
shitbox::Path::Bin => "shitbox", shitbox::Path::Bin => progname,
shitbox::Path::Sbin => "../bin/shitbox", shitbox::Path::Sbin => format!("../bin/{progname}"),
shitbox::Path::UsrBin => { shitbox::Path::UsrBin => {
if usr { if usr {
"../../bin/shitbox" format!("../../bin/{progname}")
} else { } else {
"shitbox" progname
} }
} }
shitbox::Path::UsrSbin => { shitbox::Path::UsrSbin => {
if usr { if usr {
"../../bin/shitbox" format!("../../bin/{progname}")
} else { } else {
"../bin/shitbox" format!("../bin/{progname}")
} }
} }
}; };
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");
binpath.push(env!("CARGO_PKG_NAME")); binpath.push(env!("CARGO_PKG_NAME"));
println!("Generating link for {}", self.cli().get_name());
fs::hard_link(&binpath, &linkpath)?; fs::hard_link(&binpath, &linkpath)?;
println!(" link: {} -> {}", binpath.display(), linkpath.display()); println!(" link: {} -> {}", binpath.display(), linkpath.display());
} }
@ -269,13 +271,17 @@ impl BootstrapCmd for dyn Cmd {
Ok(()) Ok(())
} }
fn manpage(&self, prefix: &str) -> Result<(), io::Error> { fn manpage(&self, prefix: &str, usr: bool) -> Result<(), io::Error> {
let command = self.cli(); let command = self.cli();
let fname = match command.get_name() { let fname = match command.get_name() {
"bootstrap" => "shitbox-bootstrap.1".to_string(), "bootstrap" => format!("{}-bootstrap.1", shitbox::progname().unwrap()),
s => format!("{s}.1"), 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() { if !outdir.exists() {
fs::create_dir_all(&outdir)?; 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:"); println!("Generating Unix man pages:");
COMMANDS COMMANDS
.iter() .iter()
.try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix))?; .try_for_each(|cmd| super::get(cmd).unwrap().manpage(prefix, usr))?;
Ok(()) Ok(())
} }
fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> { fn completions(prefix: &str, matches: &ArgMatches, usr: bool) -> Result<(), io::Error> {
println!("Generating completions:"); 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") { if matches.get_flag("bash") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"] let mut outdir = basedir.clone();
.iter() outdir.push("bash-completion");
.collect(); outdir.push("completion");
COMMANDS.iter().try_for_each(|cmd| { COMMANDS.iter().try_for_each(|cmd| {
let cmd = super::get(cmd).unwrap(); let cmd = super::get(cmd).unwrap();
cmd.completion(&outdir, "bash") cmd.completion(&outdir, "bash")
})?; })?;
} }
if matches.get_flag("fish") || matches.get_flag("all") { 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| { COMMANDS.iter().try_for_each(|cmd| {
let cmd = super::get(cmd).unwrap(); let cmd = super::get(cmd).unwrap();
cmd.completion(&outdir, "fish") cmd.completion(&outdir, "fish")
})?; })?;
} }
if matches.get_flag("nu") || matches.get_flag("all") { 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| { COMMANDS.iter().try_for_each(|cmd| {
let cmd = super::get(cmd).unwrap(); let cmd = super::get(cmd).unwrap();
cmd.completion(&outdir, "nu") cmd.completion(&outdir, "nu")
})?; })?;
} }
if matches.get_flag("pwsh") || matches.get_flag("all") { 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| { COMMANDS.iter().try_for_each(|cmd| {
let cmd = super::get(cmd).unwrap(); let cmd = super::get(cmd).unwrap();
cmd.completion(&outdir, "pwsh") cmd.completion(&outdir, "pwsh")
})?; })?;
} }
if matches.get_flag("zsh") || matches.get_flag("all") { 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| { COMMANDS.iter().try_for_each(|cmd| {
let cmd = super::get(cmd).unwrap(); let cmd = super::get(cmd).unwrap();
cmd.completion(&outdir, "zsh") cmd.completion(&outdir, "zsh")
@ -376,3 +395,4 @@ fn links(prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box<dyn Error>
})?; })?;
Ok(()) Ok(())
} }

View File

@ -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<dyn Error>> {
if let Some(prefix) = matches.get_one::<String>("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<shitbox::Path> {
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<PathBuf>;
fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box<dyn Error>>;
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<PathBuf> {
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<dyn Error>> {
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<u8> = 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<dyn Error>> {
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(())
}

View File

@ -1,6 +1,7 @@
use shitbox::Cmd; use shitbox::Cmd;
mod b2sum; mod b2sum;
mod bootstrap;
mod hashbox; mod hashbox;
mod md5sum; mod md5sum;
mod sha1sum; mod sha1sum;
@ -15,6 +16,7 @@ mod sha512sum;
pub fn get(name: &str) -> Option<Box<dyn Cmd>> { pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
match name { match name {
"b2sum" => Some(Box::new(b2sum::B2sum::default())), "b2sum" => Some(Box::new(b2sum::B2sum::default())),
"bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())),
"hashbox" => Some(Box::new(hashbox::Hashbox::default())), "hashbox" => Some(Box::new(hashbox::Hashbox::default())),
"md5sum" => Some(Box::new(md5sum::Md5sum::default())), "md5sum" => Some(Box::new(md5sum::Md5sum::default())),
"sha1sum" => Some(Box::new(sha1sum::Sha1sum::default())), "sha1sum" => Some(Box::new(sha1sum::Sha1sum::default())),
@ -26,8 +28,9 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
} }
} }
pub static COMMANDS: [&str; 7] = [ pub static COMMANDS: [&str; 8] = [
"b2sum", "b2sum",
"bootstrap",
"md5sum", "md5sum",
"sha1sum", "sha1sum",
"sha224sum", "sha224sum",

View File

@ -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<dyn Error>> {
if let Some(prefix) = matches.get_one::<String>("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<shitbox::Path> {
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<PathBuf>;
fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box<dyn Error>>;
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<PathBuf> {
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<dyn Error>> {
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<u8> = 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<dyn Error>> {
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(())
}

View File

@ -1,6 +1,7 @@
use shitbox::Cmd; use shitbox::Cmd;
mod blkid; mod blkid;
mod bootstrap;
mod clear; mod clear;
mod mount; mod mount;
mod mountpoint; mod mountpoint;
@ -15,6 +16,7 @@ mod utilbox;
#[allow(clippy::box_default)] #[allow(clippy::box_default)]
pub fn get(name: &str) -> Option<Box<dyn Cmd>> { pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
match name { match name {
"bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())),
"clear" => Some(Box::new(clear::Clear::default())), "clear" => Some(Box::new(clear::Clear::default())),
"mountpoint" => Some(Box::new(mountpoint::Mountpoint::default())), "mountpoint" => Some(Box::new(mountpoint::Mountpoint::default())),
"swaplabel" => Some(Box::new(swaplabel::Swaplabel::default())), "swaplabel" => Some(Box::new(swaplabel::Swaplabel::default())),
@ -25,7 +27,8 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
} }
} }
pub static COMMANDS: [&str; 6] = [ pub static COMMANDS: [&str; 7] = [
"bootstrap",
"clear", "clear",
"mountpoint", "mountpoint",
"swaplabel", "swaplabel",