diff --git a/Cargo.lock b/Cargo.lock index a8c6f4e..104bb30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,12 +13,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - [[package]] name = "bitflags" version = "1.3.2" @@ -178,12 +172,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "once_cell" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - [[package]] name = "os_str_bytes" version = "6.4.1" @@ -215,7 +203,6 @@ name = "shitbox" version = "0.1.0" dependencies = [ "atty", - "base64", "clap", "clap_complete", "clap_complete_nushell", @@ -223,7 +210,6 @@ dependencies = [ "data-encoding", "hostname", "libc", - "once_cell", "termcolor", ] diff --git a/Cargo.toml b/Cargo.toml index 97eae97..8c672c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] atty = "0.2.14" -base64 = "0.20.0" clap = "4.0.29" clap_complete = "4.0.6" clap_complete_nushell = "0.1.8" @@ -15,7 +14,6 @@ clap_mangen = "0.2.5" data-encoding = "2.3.3" hostname = { version = "0.3", features = ["set"] } libc = "0.2.139" -once_cell = "1.16.0" termcolor = "1.1.3" [profile.release] diff --git a/pkg/bin/shitbox b/pkg/bin/shitbox deleted file mode 100755 index 9147543..0000000 Binary files a/pkg/bin/shitbox and /dev/null differ diff --git a/pkg/usr/share/man/man1/base32.1 b/pkg/usr/share/man/man1/base32.1 deleted file mode 100644 index ba0806a..0000000 --- a/pkg/usr/share/man/man1/base32.1 +++ /dev/null @@ -1,38 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH base32 1 "base32 0.1.0" -.SH NAME -base32 \- Base32 encode/decode data and print to standard output -.SH SYNOPSIS -\fBbase32\fR [\fB\-d\fR|\fB\-\-decode\fR] [\fB\-i\fR|\fB\-\-ignore\-space\fR] [\fB\-w\fR|\fB\-\-wrap\fR] [\fB\-v\fR|\fB\-\-verbose\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIINPUT\fR] -.SH DESCRIPTION -Base32 encode/decode data and print to standard output -.SH OPTIONS -.TP -\fB\-d\fR, \fB\-\-decode\fR -Decode rather than encode -.TP -\fB\-i\fR, \fB\-\-ignore\-space\fR -Ignore whitespace when decoding -.TP -\fB\-w\fR, \fB\-\-wrap\fR [default: 76] -Wrap encoded lines after n characters -.TP -\fB\-v\fR, \fB\-\-verbose\fR -Display a header naming each file -.TP -\fB\-q\fR, \fB\-\-quiet\fR -Do not display header, even with multiple files -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -[\fIINPUT\fR] -The input file to use -.SH VERSION -v0.1.0 -.SH AUTHORS -The JeanG3nie diff --git a/pkg/usr/share/man/man1/dirname.1 b/pkg/usr/share/man/man1/dirname.1 deleted file mode 100644 index 192391d..0000000 --- a/pkg/usr/share/man/man1/dirname.1 +++ /dev/null @@ -1,24 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH dirname 1 "dirname 0.1.0" -.SH NAME -dirname \- strip last component from file name -.SH SYNOPSIS -\fBdirname\fR [\fB\-z\fR|\fB\-\-zero\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] <\fIname\fR> -.SH DESCRIPTION -strip last component from file name -.SH OPTIONS -.TP -\fB\-z\fR, \fB\-\-zero\fR -end each output line with NUL, not newline -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -<\fIname\fR> - -.SH VERSION -v0.1.0 diff --git a/pkg/usr/share/man/man1/echo.1 b/pkg/usr/share/man/man1/echo.1 deleted file mode 100644 index a431e50..0000000 --- a/pkg/usr/share/man/man1/echo.1 +++ /dev/null @@ -1,26 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH echo 1 "echo 0.1.0" -.SH NAME -echo \- Display a line of text -.SH SYNOPSIS -\fBecho\fR [\fB\-n \fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fISTRING\fR] -.SH DESCRIPTION -Echo the STRING(s) to standard output -.SH OPTIONS -.TP -\fB\-n\fR -Do not output a trailing newline -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -[\fISTRING\fR] - -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/pkg/usr/share/man/man1/false.1 b/pkg/usr/share/man/man1/false.1 deleted file mode 100644 index 241f952..0000000 --- a/pkg/usr/share/man/man1/false.1 +++ /dev/null @@ -1,20 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH false 1 "false 0.1.0" -.SH NAME -false \- Does nothing unsuccessfully -.SH SYNOPSIS -\fBfalse\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] -.SH DESCRIPTION -Exit with a status code indicating failure -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/pkg/usr/share/man/man1/head.1 b/pkg/usr/share/man/man1/head.1 deleted file mode 100644 index b2b9bd3..0000000 --- a/pkg/usr/share/man/man1/head.1 +++ /dev/null @@ -1,38 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH head 1 "head 0.1.0" -.SH NAME -head \- Display first lines of a file -.SH SYNOPSIS -\fBhead\fR [\fB\-c\fR|\fB\-\-bytes\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-v\fR|\fB\-\-verbose\fR] [\fB\-n\fR|\fB\-\-lines\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIFILES\fR] -.SH DESCRIPTION -Print the first 10 lines of each FILE to standard output. -With more than one FILE, precede each with a header giving the file name. -.PP -With no FILE, or when FILE is \-, read standard input. -.SH OPTIONS -.TP -\fB\-c\fR, \fB\-\-bytes\fR -Count bytes instead of lines -.TP -\fB\-q\fR, \fB\-\-quiet\fR -Disable printing a header. Overrides \-c -.TP -\fB\-v\fR, \fB\-\-verbose\fR -Each file is preceded by a header consisting of the string "==> XXX <==" where "XXX" is the name of the file. -.TP -\fB\-n\fR, \fB\-\-lines\fR -Count n number of lines (or bytes if \-c is specified). -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -[\fIFILES\fR] -The input file to use -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/pkg/usr/share/man/man1/hostname.1 b/pkg/usr/share/man/man1/hostname.1 deleted file mode 100644 index 976d14d..0000000 --- a/pkg/usr/share/man/man1/hostname.1 +++ /dev/null @@ -1,26 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH hostname 1 "hostname 0.1.0" -.SH NAME -hostname \- Prints the name of the current host. The super\-user can set the host name by supplying an argument. -.SH SYNOPSIS -\fBhostname\fR [\fB\-s\fR|\fB\-\-strip\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fINAME\fR] -.SH DESCRIPTION -Prints the name of the current host. The super\-user can set the host name by supplying an argument. -.SH OPTIONS -.TP -\fB\-s\fR, \fB\-\-strip\fR -Removes any domain information from the printed name. -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -[\fINAME\fR] -name to set -.SH VERSION -v0.1.0 -.SH AUTHORS -The JeanG3nie diff --git a/pkg/usr/share/man/man1/nologin.1 b/pkg/usr/share/man/man1/nologin.1 deleted file mode 100644 index 781b274..0000000 --- a/pkg/usr/share/man/man1/nologin.1 +++ /dev/null @@ -1,20 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH nologin 1 "nologin 0.1.0" -.SH NAME -nologin \- Denies a user account login ability -.SH SYNOPSIS -\fBnologin\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] -.SH DESCRIPTION -Denies a user account login ability -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/pkg/usr/share/man/man1/shitbox-bootstrap.1 b/pkg/usr/share/man/man1/shitbox-bootstrap.1 deleted file mode 100644 index b4d61d8..0000000 --- a/pkg/usr/share/man/man1/shitbox-bootstrap.1 +++ /dev/null @@ -1,46 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH bootstrap 1 "bootstrap 0.1.0" -.SH NAME -bootstrap \- Install shitbox into the filesystem -.SH SYNOPSIS -\fBbootstrap\fR [\fB\-p\fR|\fB\-\-prefix\fR] [\fB\-u\fR|\fB\-\-usr\fR] [\fB\-s\fR|\fB\-\-soft\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR] -.SH DESCRIPTION -Install symlinks, manpages and shell completions -.SH OPTIONS -.TP -\fB\-p\fR, \fB\-\-prefix\fR [default: /] -The directory path under which to install -.TP -\fB\-u\fR, \fB\-\-usr\fR -Split the installation so that some applets go into /bin | /sbin -while others are placed into /usr/bin | /usr/sbin -.TP -\fB\-s\fR, \fB\-\-soft\fR -Install soft links instead of hardlinks -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.SH SUBCOMMANDS -.TP -bootstrap\-all(1) -Install everything -.TP -bootstrap\-links(1) -Install links for each applet -.TP -bootstrap\-manpages(1) -Install Unix man pages -.TP -bootstrap\-completions(1) -Install shell completions -.TP -bootstrap\-help(1) -Print this message or the help of the given subcommand(s) -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/pkg/usr/share/man/man1/shitbox.1 b/pkg/usr/share/man/man1/shitbox.1 deleted file mode 100644 index ad6de53..0000000 --- a/pkg/usr/share/man/man1/shitbox.1 +++ /dev/null @@ -1,52 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH shitbox 1 "shitbox 0.1.0" -.SH NAME -shitbox \- The Harbor Freight multitool of embedded Linux -.SH SYNOPSIS -\fBshitbox\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR] -.SH DESCRIPTION -The Harbor Freight multitool of embedded Linux -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.SH SUBCOMMANDS -.TP -shitbox\-base32(1) -Base32 encode/decode data and print to standard output -.TP -shitbox\-bootstrap(1) -Install shitbox into the filesystem -.TP -shitbox\-echo(1) -Display a line of text -.TP -shitbox\-dirname(1) -strip last component from file name -.TP -shitbox\-false(1) -Does nothing unsuccessfully -.TP -shitbox\-head(1) -Display first lines of a file -.TP -shitbox\-nologin(1) -Denies a user account login ability -.TP -shitbox\-hostname(1) -Prints the name of the current host. The super\-user can set the host name by supplying an argument. -.TP -shitbox\-sleep(1) -Suspend execution for an interval of time -.TP -shitbox\-true(1) -Does nothing successfully -.TP -shitbox\-help(1) -Print this message or the help of the given subcommand(s) -.SH VERSION -v0.1.0 diff --git a/pkg/usr/share/man/man1/sleep.1 b/pkg/usr/share/man/man1/sleep.1 deleted file mode 100644 index 22881fe..0000000 --- a/pkg/usr/share/man/man1/sleep.1 +++ /dev/null @@ -1,25 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH sleep 1 "sleep 0.1.0" -.SH NAME -sleep \- Suspend execution for an interval of time -.SH SYNOPSIS -\fBsleep\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] <\fIseconds\fR> -.SH DESCRIPTION -The sleep utility suspends execution for a minimum of the specified number of seconds. -This number must be positive and may contain a decimal fraction. -sleep is commonly used to schedule the execution of other commands -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -<\fIseconds\fR> -The number of seconds to sleep -.SH VERSION -v0.1.0 -.SH AUTHORS - diff --git a/pkg/usr/share/man/man1/true.1 b/pkg/usr/share/man/man1/true.1 deleted file mode 100644 index 601375b..0000000 --- a/pkg/usr/share/man/man1/true.1 +++ /dev/null @@ -1,20 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH true 1 "true 0.1.0" -.SH NAME -true \- Does nothing successfully -.SH SYNOPSIS -\fBtrue\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] -.SH DESCRIPTION -Exit with a status code indicating success -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information (use `\-h` for a summary) -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.SH VERSION -v0.1.0 -.SH AUTHORS -Nathan Fisher diff --git a/src/cmd/base32/mod.rs b/src/cmd/base32/mod.rs index d27daf9..3839d01 100644 --- a/src/cmd/base32/mod.rs +++ b/src/cmd/base32/mod.rs @@ -14,10 +14,14 @@ pub struct Base32 { path: Option, } -pub const BASE_32: Base32 = Base32 { - name: "base32", - path: Some(crate::Path::UsrBin), -}; +impl Default for Base32 { + fn default() -> Self { + Self { + name: "base32", + path: Some(crate::Path::UsrBin), + } + } +} impl Cmd for Base32 { fn name(&self) -> &str { @@ -129,7 +133,7 @@ fn decode_base32(mut contents: String, ignore: bool) -> Result<(), Box, } -pub const BASE_64: Base64 = Base64 { - name: "base64", - path: Some(crate::Path::UsrBin), -}; +impl Default for Base64 { + fn default() -> Self { + Self { + name: "base64", + path: Some(crate::Path::UsrBin), + } + } +} impl Cmd for Base64 { fn name(&self) -> &str { @@ -125,14 +129,15 @@ fn decode_base64(mut contents: String, ignore: bool) -> Result<(), Box>() .chunks(wrap) diff --git a/src/cmd/bootstrap/mod.rs b/src/cmd/bootstrap/mod.rs index e2ac804..1ecef5d 100644 --- a/src/cmd/bootstrap/mod.rs +++ b/src/cmd/bootstrap/mod.rs @@ -1,12 +1,14 @@ -use super::{Cmd, Commands}; +use super::{Cmd, COMMANDS}; use clap::{Arg, ArgAction, ArgMatches, Command}; -use clap_complete::shells; +use clap_complete::{generate_to, shells}; use clap_complete_nushell::Nushell; +use clap_mangen::Man; use std::{ error::Error, fs, io::{self, ErrorKind}, - path::PathBuf, + os::unix::fs::symlink, + path::{Path, PathBuf}, }; #[derive(Debug)] @@ -15,10 +17,14 @@ pub struct Bootstrap { path: Option, } -pub const BOOTSTRAP: Bootstrap = Bootstrap { - name: "bootstrap", - path: None, -}; +impl Default for Bootstrap { + fn default() -> Self { + Self { + name: "bootstrap", + path: None, + } + } +} impl Bootstrap { fn all() -> clap::Command { @@ -167,9 +173,6 @@ impl Cmd for Bootstrap { return Err(io::Error::new(ErrorKind::Other, "No input").into()); }; if let Some(prefix) = matches.get_one::("prefix") { - let commands = super::COMMANDS - .get() - .ok_or_else(|| io::Error::new(ErrorKind::Other, "Cannot get commands list"))?; let usr = matches.get_flag("usr"); if let Some(progpath) = crate::progpath() { let mut outpath = PathBuf::from(prefix); @@ -189,18 +192,18 @@ impl Cmd for Bootstrap { } match matches.subcommand() { Some(("links", matches)) => { - commands.links(prefix, usr, matches)?; + links(prefix, usr, matches)?; } Some(("manpages", _matches)) => { - commands.manpages(prefix)?; + manpages(prefix)?; } Some(("completions", matches)) => { - commands.completions(prefix, matches)?; + completions(prefix, matches)?; } Some(("all", matches)) => { - commands.links(prefix, usr, matches)?; - commands.manpages(prefix)?; - commands.completions(prefix, matches)?; + links(prefix, usr, matches)?; + manpages(prefix)?; + completions(prefix, matches)?; } _ => {} } @@ -213,59 +216,178 @@ impl Cmd for Bootstrap { } } -pub trait Bootstrappable { - 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>; +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>; } -impl<'a> Bootstrappable for Commands<'a> { - fn manpages(&self, prefix: &str) -> Result<(), io::Error> { - println!("Generating Unix man pages:"); - self.items.iter().try_for_each(|cmd| cmd.manpage(prefix))?; +impl BootstrapCmd for dyn Cmd { + fn completion(&self, outdir: &Path, gen: &str) -> Result<(), io::Error> { + let name = self.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 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))?; + fn linkpath(&self, prefix: &str, usr: bool) -> Option { + let mut path = PathBuf::from(prefix); + let binpath = self.path(); + 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"); + } + path.push("bin"); + } + Some(crate::Path::UsrSbin) => { + if usr { + path.push("usr"); + } + path.push("sbin"); + } + None => return None, } - 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))?; + path.push(self.name()); + Some(path) + } + + fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box> { + if let Some(linkpath) = self.linkpath(prefix, usr) { + if soft { + let binpath = match self.path().unwrap() { + crate::Path::Bin => "shitbox", + crate::Path::Sbin => "../bin/shitbox", + crate::Path::UsrBin => { + if usr { + "../../bin/shitbox" + } else { + "shitbox" + } + } + crate::Path::UsrSbin => { + if usr { + "../../bin/shitbox" + } else { + "../bin/shitbox" + } + } + }; + symlink(binpath, &linkpath)?; + println!(" symlink: {binpath} -> {}", linkpath.display()); + } else { + let mut binpath = PathBuf::from(prefix); + binpath.push("bin"); + binpath.push(env!("CARGO_PKG_NAME")); + fs::hard_link(&binpath, &linkpath)?; + println!(" link: {} -> {}", binpath.display(), linkpath.display()); + } } Ok(()) } - fn links(&self, prefix: &str, usr: bool, cmd: &ArgMatches) -> Result<(), Box> { - println!("Generating links:"); - let mut binpath = PathBuf::from(prefix); + 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 = vec![]; + man.render(&mut buffer)?; + fs::write(&outfile, buffer)?; + println!(" {}", outfile.display()); + Ok(()) + } +} + +fn manpages(prefix: &str) -> Result<(), io::Error> { + println!("Generating Unix man pages:"); + COMMANDS + .iter() + .try_for_each(|cmd| crate::cmd::get(cmd).unwrap().manpage(prefix))?; + Ok(()) +} + +fn completions(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(); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = crate::cmd::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(); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = crate::cmd::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(); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = crate::cmd::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(); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = crate::cmd::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(); + COMMANDS.iter().try_for_each(|cmd| { + let cmd = crate::cmd::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)?; @@ -277,25 +399,11 @@ impl<'a> Bootstrappable for Commands<'a> { 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"); - self.items - .iter() - .try_for_each(|cmd| cmd.link(prefix, usr, soft))?; - Ok(()) } + let soft = cmd.get_flag("soft"); + COMMANDS.iter().try_for_each(|c| { + let cmd = crate::cmd::get(c).unwrap(); + cmd.link(prefix, usr, soft) + })?; + Ok(()) } diff --git a/src/cmd/dirname/mod.rs b/src/cmd/dirname/mod.rs index 7219b12..957b14c 100644 --- a/src/cmd/dirname/mod.rs +++ b/src/cmd/dirname/mod.rs @@ -8,10 +8,14 @@ pub struct Dirname { path: Option, } -pub const DIRNAME: Dirname = Dirname { - name: "dirname", - path: Some(crate::Path::UsrBin), -}; +impl Default for Dirname { + fn default() -> Self { + Self { + name: "dirname", + path: Some(crate::Path::UsrBin), + } + } +} impl Cmd for Dirname { fn name(&self) -> &str { diff --git a/src/cmd/echo/mod.rs b/src/cmd/echo/mod.rs index a12430c..fd801b7 100644 --- a/src/cmd/echo/mod.rs +++ b/src/cmd/echo/mod.rs @@ -9,10 +9,14 @@ pub struct Echo { path: Option, } -pub const ECHO: Echo = Echo { - name: "echo", - path: Some(Path::Bin), -}; +impl Default for Echo { + fn default() -> Self { + Self { + name: "echo", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for Echo { fn name(&self) -> &str { diff --git a/src/cmd/factor/mod.rs b/src/cmd/factor/mod.rs index 1a5e8ae..b363264 100644 --- a/src/cmd/factor/mod.rs +++ b/src/cmd/factor/mod.rs @@ -11,10 +11,14 @@ pub struct Factor { path: Option, } -pub const FACTOR: Factor = Factor { - name: "factor", - path: Some(crate::Path::UsrBin), -}; +impl Default for Factor { + fn default() -> Self { + Self { + name: "factor", + path: Some(crate::Path::UsrBin), + } + } +} impl Cmd for Factor { fn name(&self) -> &str { diff --git a/src/cmd/false/mod.rs b/src/cmd/false/mod.rs index 7a5a1c3..f0eebdb 100644 --- a/src/cmd/false/mod.rs +++ b/src/cmd/false/mod.rs @@ -9,10 +9,14 @@ pub struct False { path: Option, } -pub const FALSE: False = False { - name: "false", - path: Some(Path::Bin), -}; +impl Default for False { + fn default() -> Self { + Self { + name: "false", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for False { fn name(&self) -> &str { diff --git a/src/cmd/head/mod.rs b/src/cmd/head/mod.rs index 8e7cc7e..bff65fd 100644 --- a/src/cmd/head/mod.rs +++ b/src/cmd/head/mod.rs @@ -15,10 +15,14 @@ pub struct Head { path: Option, } -pub const HEAD: Head = Head { - name: "head", - path: Some(Path::Bin), -}; +impl Default for Head { + fn default() -> Self { + Self { + name: "head", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for Head { fn name(&self) -> &str { diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 7a63dba..dfc45d2 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,13 +1,5 @@ use clap::ArgMatches; -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}, -}; +use std::{error::Error, fmt}; pub mod base32; pub mod base64; @@ -38,123 +30,50 @@ mod sync; pub mod r#true; pub use { - self::base64::{Base64, BASE_64}, - self::hostname::{Hostname, HOSTNAME}, - base32::{Base32, BASE_32}, - bootstrap::{Bootstrap, BOOTSTRAP}, - dirname::{Dirname, DIRNAME}, - echo::{Echo, ECHO}, - factor::{Factor, FACTOR}, - head::{Head, HEAD}, - mountpoint::{Mountpoint, MOUNTPOINT}, - nologin::{Nologin, NOLOGIN}, - r#false::{False, FALSE}, - r#true::{True, TRUE}, - shitbox::{Shitbox, SHITBOX}, - sleep::{Sleep, SLEEP}, + self::hostname::Hostname, base32::Base32, base64::Base64, bootstrap::Bootstrap, + dirname::Dirname, echo::Echo, factor::Factor, head::Head, mountpoint::Mountpoint, + nologin::Nologin, r#false::False, r#true::True, shitbox::Shitbox, sleep::Sleep, }; -#[derive(Debug)] -pub struct Commands<'a> { - pub items: Vec<&'a dyn Cmd>, -} - -pub static COMMANDS: OnceCell = 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 cli(&self) -> clap::Command; fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box>; fn path(&self) -> Option; +} - fn linkpath(&self, prefix: &str, usr: bool) -> Option { - let mut path = PathBuf::from(prefix); - let binpath = self.path(); - 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"); - } - path.push("bin"); - } - Some(crate::Path::UsrSbin) => { - if usr { - path.push("usr"); - } - path.push("sbin"); - } - None => return None, - } - path.push(self.name()); - Some(path) - } - - fn link(&self, prefix: &str, usr: bool, soft: bool) -> Result<(), Box> { - if let Some(linkpath) = self.linkpath(prefix, usr) { - if soft { - let binpath = match self.path().unwrap() { - crate::Path::Bin => "shitbox", - crate::Path::Sbin => "../bin/shitbox", - crate::Path::UsrBin => { - if usr { - "../../bin/shitbox" - } else { - "shitbox" - } - } - crate::Path::UsrSbin => { - if usr { - "../../bin/shitbox" - } else { - "../bin/shitbox" - } - } - }; - symlink(binpath, &linkpath)?; - println!(" symlink: {binpath} -> {}", linkpath.display()); - } else { - let mut binpath = PathBuf::from(prefix); - binpath.push("bin"); - binpath.push(env!("CARGO_PKG_NAME")); - fs::hard_link(&binpath, &linkpath)?; - println!(" link: {} -> {}", binpath.display(), linkpath.display()); - } - } - 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 = vec![]; - man.render(&mut buffer)?; - fs::write(&outfile, buffer)?; - println!(" {}", outfile.display()); - Ok(()) +pub fn get(name: &str) -> Option> { + match name { + "base64" => Some(Box::new(Base64::default())), + "base32" => Some(Box::new(Base32::default())), + "bootstrap" => Some(Box::new(Bootstrap::default())), + "dirname" => Some(Box::new(Dirname::default())), + "echo" => Some(Box::new(Echo::default())), + "factor" => Some(Box::new(Factor::default())), + "false" => Some(Box::new(False::default())), + "head" => Some(Box::new(Head::default())), + "mountpoint" => Some(Box::new(Mountpoint::default())), + "nologin" => Some(Box::new(Nologin::default())), + "shitbox" => Some(Box::new(Shitbox::default())), + "sleep" => Some(Box::new(Sleep::default())), + "true" => Some(Box::new(True::default())), + _ => None, } } + +pub static COMMANDS: [&'static str; 14] = [ + "base32", + "base64", + "bootstrap", + "dirname", + "echo", + "false", + "factor", + "head", + "hostname", + "mountpoint", + "nologin", + "true", + "sleep", + "shitbox", +]; diff --git a/src/cmd/mountpoint/mod.rs b/src/cmd/mountpoint/mod.rs index 8638cb0..3165892 100644 --- a/src/cmd/mountpoint/mod.rs +++ b/src/cmd/mountpoint/mod.rs @@ -15,10 +15,14 @@ pub struct Mountpoint { path: Option, } -pub const MOUNTPOINT: Mountpoint = Mountpoint { - name: "mountpoint", - path: Some(crate::Path::Bin), -}; +impl Default for Mountpoint { + fn default() -> Self { + Self { + name: "mountpoint", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for Mountpoint { fn name(&self) -> &str { @@ -38,7 +42,7 @@ impl Cmd for Mountpoint { failure: incorrect invocation, permissions or system error\ \n 32\n \ failure: the directory is not a mountpoint, or device is not a \ - block device on --devno" + block device on --devno", ) .args([ Arg::new("fs-devno") diff --git a/src/cmd/nologin/mod.rs b/src/cmd/nologin/mod.rs index dc2d73d..1b918f3 100644 --- a/src/cmd/nologin/mod.rs +++ b/src/cmd/nologin/mod.rs @@ -8,10 +8,14 @@ pub struct Nologin { path: Option, } -pub const NOLOGIN: Nologin = Nologin { - name: "nologin", - path: Some(crate::Path::Sbin), -}; +impl Default for Nologin { + fn default() -> Self { + Self { + name: "nologin", + path: Some(crate::Path::Sbin), + } + } +} impl Cmd for Nologin { fn name(&self) -> &str { diff --git a/src/cmd/shitbox/mod.rs b/src/cmd/shitbox/mod.rs index 85078bd..bf2d769 100644 --- a/src/cmd/shitbox/mod.rs +++ b/src/cmd/shitbox/mod.rs @@ -1,11 +1,9 @@ -use super::{ - Cmd, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, MOUNTPOINT, - NOLOGIN, SLEEP, TRUE, -}; +use super::{Cmd, COMMANDS}; use clap::Command; use std::{ error::Error, io::{self, ErrorKind}, + process, }; #[derive(Debug)] @@ -14,6 +12,15 @@ pub struct Shitbox { path: Option, } +impl Default for Shitbox { + fn default() -> Self { + Self { + name: "shitbox", + path: None, + } + } +} + pub const SHITBOX: Shitbox = Shitbox { name: "shitbox", path: None, @@ -25,6 +32,18 @@ impl Cmd for Shitbox { } fn cli(&self) -> clap::Command { + let subcommands: Vec = { + let mut s = vec![]; + for c in COMMANDS { + if c == "shitbox" { + continue; + } + if let Some(cmd) = crate::cmd::get(c) { + s.push(cmd.cli()); + } + } + s + }; Command::new(self.name) .about("The Harbor Freight multitool of embedded Linux") .version(env!("CARGO_PKG_VERSION")) @@ -32,42 +51,20 @@ impl Cmd for Shitbox { .arg_required_else_help(true) .subcommand_value_name("APPLET") .subcommand_help_heading("APPLETS") - .subcommands([ - BASE_32.cli(), - BASE_64.cli(), - BOOTSTRAP.cli(), - DIRNAME.cli(), - ECHO.cli(), - FALSE.cli(), - FACTOR.cli(), - HEAD.cli(), - MOUNTPOINT.cli(), - NOLOGIN.cli(), - HOSTNAME.cli(), - SLEEP.cli(), - TRUE.cli(), - ]) + .subcommands(&subcommands) } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { let Some(matches) = matches else { return Err(Box::new(io::Error::new(ErrorKind::Other, "No input"))); }; - match matches.subcommand() { - Some(("base32", matches)) => BASE_32.run(Some(matches))?, - Some(("base64", matches)) => BASE_64.run(Some(matches))?, - Some(("bootstrap", matches)) => BOOTSTRAP.run(Some(matches))?, - Some(("dirname", matches)) => DIRNAME.run(Some(matches))?, - Some(("echo", _matches)) => ECHO.run(None)?, - Some(("factor", matches)) => FACTOR.run(Some(matches))?, - Some(("false", _matches)) => FALSE.run(None)?, - Some(("head", matches)) => HEAD.run(Some(matches))?, - Some(("mountpoint", matches)) => MOUNTPOINT.run(Some(matches))?, - Some(("nologin", _matches)) => NOLOGIN.run(None)?, - Some(("hostname", matches)) => HOSTNAME.run(Some(matches))?, - Some(("sleep", matches)) => SLEEP.run(Some(matches))?, - Some(("true", _matches)) => TRUE.run(None)?, - _ => unimplemented!(), + if let Some((name, matches)) = matches.subcommand() { + if let Some(command) = crate::cmd::get(name) { + if let Err(e) = command.run(Some(matches)) { + eprintln!("Error: {name}: {e}"); + process::exit(1); + } + } } Ok(()) } diff --git a/src/cmd/sleep/mod.rs b/src/cmd/sleep/mod.rs index 73603d0..74b05e0 100644 --- a/src/cmd/sleep/mod.rs +++ b/src/cmd/sleep/mod.rs @@ -9,10 +9,14 @@ pub struct Sleep { path: Option, } -pub const SLEEP: Sleep = Sleep { - name: "sleep", - path: Some(Path::Bin), -}; +impl Default for Sleep { + fn default() -> Self { + Self { + name: "sleep", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for Sleep { fn name(&self) -> &str { diff --git a/src/cmd/true/mod.rs b/src/cmd/true/mod.rs index 56b2d38..1544016 100644 --- a/src/cmd/true/mod.rs +++ b/src/cmd/true/mod.rs @@ -9,10 +9,14 @@ pub struct True { path: Option, } -pub const TRUE: True = True { - name: "true", - path: Some(Path::Bin), -}; +impl Default for True { + fn default() -> Self { + Self { + name: "true", + path: Some(crate::Path::Bin), + } + } +} impl Cmd for True { fn name(&self) -> &str { diff --git a/src/lib.rs b/src/lib.rs index 128a433..1ec2520 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,8 @@ #![warn(clippy::all, clippy::pedantic)] -use std::{env, error::Error, path::PathBuf, string::ToString}; +use std::{env, error::Error, path::PathBuf, process, string::ToString}; pub mod cmd; -pub use cmd::{ - Cmd, Commands, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, - MOUNTPOINT, NOLOGIN, SHITBOX, SLEEP, TRUE, -}; +pub use cmd::Cmd; pub mod math; #[derive(Debug, Clone, Copy)] @@ -37,50 +34,16 @@ pub fn progpath() -> Option { } pub fn run() -> Result<(), Box> { - if cmd::COMMANDS.get().is_none() { - cmd::COMMANDS - .set(Commands { - items: vec![ - &BASE_32, - &BASE_64, - &BOOTSTRAP, - &DIRNAME, - &ECHO, - &FALSE, - &FACTOR, - &HEAD, - &HOSTNAME, - &MOUNTPOINT, - &NOLOGIN, - &TRUE, - &SLEEP, - &SHITBOX, - ], - }) - .expect("Cannot register commands"); - } if let Some(progname) = progname() { - match progname.as_str() { - "base32" => BASE_32.run(Some(&BASE_32.cli().get_matches()))?, - "base64" => BASE_64.run(Some(&BASE_64.cli().get_matches()))?, - "dirname" => DIRNAME.run(Some(&DIRNAME.cli().get_matches()))?, - "echo" => ECHO.run(None)?, - "false" => FALSE.run(None)?, - "factor" => FACTOR.run(Some(&FACTOR.cli().get_matches()))?, - "head" => { - HEAD.run(Some(&HEAD.cli().get_matches()))?; + if let Some(command) = cmd::get(&progname) { + let cli = command.cli(); + if let Err(e) = command.run(Some(&cli.get_matches())) { + eprintln!("{progname}: Error: {e}"); + process::exit(1); } - "hostname" => HOSTNAME.run(Some(&HOSTNAME.cli().get_matches()))?, - "mountpoint" => MOUNTPOINT.run(Some(&MOUNTPOINT.cli().get_matches()))?, - "nologin" => NOLOGIN.run(None)?, - "true" => TRUE.run(None)?, - "shitbox" => { - SHITBOX.run(Some(&SHITBOX.cli().get_matches()))?; - } - "sleep" => { - SLEEP.run(Some(&SLEEP.cli().get_matches()))?; - } - _ => unimplemented!(), + } else { + eprintln!("shitbox: Error: unknown command {progname}"); + process::exit(1); } } Ok(())