2022-12-25 22:27:17 -05:00
|
|
|
use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE, NOLOGIN};
|
2022-12-25 19:30:36 -05:00
|
|
|
use clap::{Arg, ArgAction, ArgMatches, Command};
|
2022-12-22 19:09:44 -05:00
|
|
|
use clap_complete::{generate_to, shells, Generator};
|
|
|
|
use clap_complete_nushell::Nushell;
|
2022-12-20 19:23:41 -05:00
|
|
|
use clap_mangen::Man;
|
2022-12-22 19:09:44 -05:00
|
|
|
use std::{
|
2022-12-25 18:29:09 -05:00
|
|
|
error::Error,
|
|
|
|
fs,
|
|
|
|
io::{self, ErrorKind},
|
2022-12-22 19:09:44 -05:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2022-12-20 19:23:41 -05:00
|
|
|
|
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")
|
2022-12-25 19:30:36 -05:00
|
|
|
.action(ArgAction::SetTrue),
|
2022-12-25 22:27:17 -05:00
|
|
|
Arg::new("soft")
|
|
|
|
.help("Install soft links instead of hardlinks")
|
|
|
|
.short('s')
|
|
|
|
.long("soft")
|
|
|
|
.action(ArgAction::SetTrue),
|
2022-12-25 18:29:09 -05:00
|
|
|
])
|
|
|
|
.subcommands([
|
2022-12-25 22:27:17 -05:00
|
|
|
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),
|
|
|
|
]),
|
2022-12-25 18:29:09 -05:00
|
|
|
Command::new("links")
|
|
|
|
.about("Install links for each applet")
|
|
|
|
.arg(
|
|
|
|
Arg::new("soft")
|
|
|
|
.help("Install soft links instead of hardlinks")
|
|
|
|
.short('s')
|
2022-12-25 22:27:17 -05:00
|
|
|
.long("soft")
|
|
|
|
.action(ArgAction::SetTrue),
|
2022-12-25 18:29:09 -05:00
|
|
|
),
|
|
|
|
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-25 22:27:17 -05:00
|
|
|
Arg::new("zsh")
|
|
|
|
.help("Zshell completions")
|
|
|
|
.short('z')
|
|
|
|
.long("zsh")
|
|
|
|
.action(ArgAction::SetTrue),
|
2022-12-25 18:29:09 -05:00
|
|
|
]),
|
|
|
|
])
|
|
|
|
}
|
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![
|
2022-12-25 22:27:17 -05:00
|
|
|
&BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX,
|
2022-12-25 18:29:09 -05:00
|
|
|
],
|
|
|
|
};
|
2022-12-25 19:30:36 -05:00
|
|
|
let usr = matches.get_flag("usr");
|
2022-12-25 22:27:17 -05:00
|
|
|
if let Some(progpath) = crate::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(env!("CARGO_PKG_NAME"));
|
|
|
|
fs::copy(&progpath, &outpath)?;
|
|
|
|
println!(" install: {} -> {}", progpath.display(), outpath.display());
|
|
|
|
}
|
2022-12-25 18:29:09 -05:00
|
|
|
match matches.subcommand() {
|
2022-12-25 19:30:36 -05:00
|
|
|
Some(("links", matches)) => {
|
|
|
|
commands.links(prefix, usr, matches)?;
|
|
|
|
}
|
2022-12-25 18:29:09 -05:00
|
|
|
Some(("manpages", _matches)) => {
|
|
|
|
commands.manpages(prefix)?;
|
|
|
|
}
|
|
|
|
Some(("completions", matches)) => {
|
|
|
|
commands.completions(prefix, matches)?;
|
|
|
|
}
|
2022-12-25 22:27:17 -05:00
|
|
|
Some(("all", matches)) => {
|
|
|
|
commands.links(prefix, usr, matches)?;
|
2022-12-25 18:29:09 -05:00
|
|
|
commands.manpages(prefix)?;
|
|
|
|
commands.completions(prefix, matches)?;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
2022-12-20 19:23:41 -05:00
|
|
|
}
|
|
|
|
|
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-20 19:23:41 -05:00
|
|
|
|
2022-12-25 19:30:36 -05:00
|
|
|
fn links(&self, prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box<dyn Error>> {
|
|
|
|
println!("Generating links:");
|
2022-12-25 22:27:17 -05:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
2022-12-25 19:30:36 -05:00
|
|
|
let soft = cmd.get_flag("soft");
|
|
|
|
self.items
|
|
|
|
.iter()
|
|
|
|
.try_for_each(|cmd| cmd.link(prefix, usr, soft))?;
|
|
|
|
Ok(())
|
2022-12-20 19:23:41 -05:00
|
|
|
}
|
|
|
|
}
|