shitbox/src/cmd/bootstrap/mod.rs

250 lines
8.6 KiB
Rust
Raw Normal View History

2022-12-25 18:29:09 -05:00
use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use clap_complete::{generate_to, shells, Generator};
use clap_complete_nushell::Nushell;
use clap_mangen::Man;
use std::{
2022-12-25 18:29:09 -05:00
error::Error,
fs,
io::{self, ErrorKind},
path::{Path, PathBuf},
};
2022-12-25 18:29:09 -05:00
pub struct Bootstrap {
name: &'static str,
path: Option<crate::Path>,
}
pub const BOOTSTRAP: Bootstrap = Bootstrap {
name: "bootstrap",
path: None,
};
impl Cmd for Bootstrap {
fn name(&self) -> &str {
self.name
}
2022-12-20 12:05:21 -05:00
2022-12-25 18:29:09 -05:00
fn cli(&self) -> clap::Command {
Command::new(self.name)
.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\
2022-12-20 12:05:21 -05:00
while others are placed into /usr/bin | /usr/sbin",
2022-12-25 18:29:09 -05:00
)
.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),
]),
])
}
2022-12-20 12:05:21 -05:00
2022-12-25 18:29:09 -05:00
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches {
m
} else {
return Err(io::Error::new(ErrorKind::Other, "No input").into());
};
if let Some(prefix) = matches.get_one::<String>("prefix") {
let commands: Commands = Commands {
items: vec![
&BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &TRUE, &SLEEP, &SHITBOX,
],
};
match matches.subcommand() {
Some(("manpages", _matches)) => {
commands.manpages(prefix)?;
}
Some(("completions", matches)) => {
commands.completions(prefix, matches)?;
}
Some(("all", _matches)) => {
commands.manpages(prefix)?;
commands.completions(prefix, matches)?;
}
_ => {}
}
}
Ok(())
}
2022-12-25 18:29:09 -05:00
fn path(&self) -> Option<crate::Path> {
self.path
}
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
struct Commands<'a> {
items: Vec<&'a dyn Cmd>,
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
impl<'a> Commands<'a> {
fn manpages(&self, prefix: &str) -> Result<(), io::Error> {
println!("Generating Unix man pages:");
self.items
2022-12-21 20:19:38 -05:00
.iter()
2022-12-25 18:29:09 -05:00
.try_for_each(|cmd| Self::manpage(prefix, cmd))?;
Ok(())
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
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(())
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
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(())
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
fn completions(&self, 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();
self.items
.iter()
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Bash))?;
}
if matches.get_flag("fish") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
self.items
.iter()
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Fish))?;
}
if matches.get_flag("nu") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
self.items
.iter()
.try_for_each(|cmd| Self::completion(&outdir, cmd, Nushell))?;
}
if matches.get_flag("pwsh") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
self.items
.iter()
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::PowerShell))?;
}
if matches.get_flag("zsh") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
self.items
.iter()
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Zsh))?;
}
Ok(())
2022-12-21 20:19:38 -05:00
}
}
2022-12-25 18:29:09 -05:00
/*
fn get_path(prefix: &str, name: &str, usr: bool) -> Option<PathBuf> {
let mut path = PathBuf::from(prefix);
let binpath = match name {
"bootstrap" => Bootstrap::path(),
"echo" => Echo::path(),
"false" => False::path(),
"head" => Head::path(),
"hostname" => Hostname::path(),
"true" => True::path(),
"sleep" => Sleep::path(),
"shitbox" => Shitbox::path(),
_ => todo!(),
};
match binpath {
Some(crate::Path::Bin) => path.push("bin"),
Some(crate::Path::Sbin) => path.push("sbin"),
Some(crate::Path::UsrBin) => {
if usr {
path.push("usr");
2022-12-21 20:19:38 -05:00
}
2022-12-25 18:29:09 -05:00
path.push("bin");
}
Some(crate::Path::UsrSbin) => {
if usr {
path.push("usr");
}
2022-12-25 18:29:09 -05:00
path.push("sbin");
}
2022-12-25 18:29:09 -05:00
None => return None,
}
2022-12-25 18:29:09 -05:00
path.push(name);
Some(path)
}
2022-12-25 18:29:09 -05:00
*/