diff --git a/pkg/bin/shitbox b/pkg/bin/shitbox new file mode 100755 index 0000000..9147543 Binary files /dev/null and b/pkg/bin/shitbox differ diff --git a/pkg/usr/share/man/man1/base32.1 b/pkg/usr/share/man/man1/base32.1 new file mode 100644 index 0000000..ba0806a --- /dev/null +++ b/pkg/usr/share/man/man1/base32.1 @@ -0,0 +1,38 @@ +.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 new file mode 100644 index 0000000..192391d --- /dev/null +++ b/pkg/usr/share/man/man1/dirname.1 @@ -0,0 +1,24 @@ +.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 new file mode 100644 index 0000000..a431e50 --- /dev/null +++ b/pkg/usr/share/man/man1/echo.1 @@ -0,0 +1,26 @@ +.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 new file mode 100644 index 0000000..241f952 --- /dev/null +++ b/pkg/usr/share/man/man1/false.1 @@ -0,0 +1,20 @@ +.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 new file mode 100644 index 0000000..b2b9bd3 --- /dev/null +++ b/pkg/usr/share/man/man1/head.1 @@ -0,0 +1,38 @@ +.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 new file mode 100644 index 0000000..976d14d --- /dev/null +++ b/pkg/usr/share/man/man1/hostname.1 @@ -0,0 +1,26 @@ +.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 new file mode 100644 index 0000000..781b274 --- /dev/null +++ b/pkg/usr/share/man/man1/nologin.1 @@ -0,0 +1,20 @@ +.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 new file mode 100644 index 0000000..b4d61d8 --- /dev/null +++ b/pkg/usr/share/man/man1/shitbox-bootstrap.1 @@ -0,0 +1,46 @@ +.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 new file mode 100644 index 0000000..ad6de53 --- /dev/null +++ b/pkg/usr/share/man/man1/shitbox.1 @@ -0,0 +1,52 @@ +.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 new file mode 100644 index 0000000..22881fe --- /dev/null +++ b/pkg/usr/share/man/man1/sleep.1 @@ -0,0 +1,25 @@ +.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 new file mode 100644 index 0000000..601375b --- /dev/null +++ b/pkg/usr/share/man/man1/true.1 @@ -0,0 +1,20 @@ +.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 6692ef7..1d0b770 100644 --- a/src/cmd/base32/mod.rs +++ b/src/cmd/base32/mod.rs @@ -1,5 +1,5 @@ use super::Cmd; -use clap::{Arg, ArgAction, Command}; +use clap::{value_parser, Arg, ArgAction, Command}; use data_encoding::BASE32; use std::{ fs, @@ -25,8 +25,7 @@ impl Cmd for Base32 { fn cli(&self) -> clap::Command { Command::new("base32") - .version(env!("CARGO_PKG_VERSION")) - .author("The JeanG3nie ") + .author("Nathan Fisher") .about("Base32 encode/decode data and print to standard output") .args([ Arg::new("INPUT") @@ -46,6 +45,7 @@ impl Cmd for Base32 { .help("Wrap encoded lines after n characters") .short('w') .long("wrap") + .value_parser(value_parser!(usize)) .default_value("76"), Arg::new("VERBOSE") .help("Display a header naming each file") @@ -61,20 +61,19 @@ impl Cmd for Base32 { } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { - let matches = match matches { - Some(m) => m, - None => return Err(io::Error::new(io::ErrorKind::Other, "No input").into()), + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "No input").into()); }; let files: Vec<_> = match matches.get_many::("INPUT") { - Some(c) => c.map(|x| x.to_owned()).collect(), + Some(c) => c.map(|x| x.clone()).collect(), None => vec![String::from("-")], }; let len = files.len(); for (index, file) in files.into_iter().enumerate() { if { len > 1 || matches.get_flag("VERBOSE") } && !matches.get_flag("QUIET") { match index { - 0 => println!("===> {} <===", file), - _ => println!("\n===> {} <===", file), + 0 => println!("===> {file} <==="), + _ => println!("\n===> {file} <==="), }; } else if index > 0 { println!(); @@ -111,14 +110,14 @@ fn decode_base32(mut contents: String, ignore: bool) { let decoded = match BASE32.decode(contents.as_bytes()) { Ok(c) => c, Err(e) => { - eprintln!("base32: {}", e); + eprintln!("base32: {e}"); process::exit(1); } }; let output = match String::from_utf8(decoded) { Ok(c) => c, Err(e) => { - eprintln!("base32: {}", e); + eprintln!("base32: {e}"); process::exit(1); } }; @@ -134,7 +133,7 @@ fn encode_base32(contents: &str, wrap: usize) { .map(|c| c.iter().collect::()) .collect::>(); for line in &encoded { - println!("{}", line); + println!("{line}"); } } @@ -144,15 +143,15 @@ fn get_contents(file: &str) -> String { match io::stdin().read_to_string(&mut contents) { Ok(_) => true, Err(e) => { - eprintln!("base32: {}", e); + eprintln!("base32: {e}"); process::exit(1); } }; } else { - contents = match fs::read_to_string(&file) { + contents = match fs::read_to_string(file) { Ok(c) => c, Err(e) => { - eprintln!("base32: {}", e); + eprintln!("base32: {e}"); process::exit(1); } }; diff --git a/src/cmd/base64/mod.rs b/src/cmd/base64/mod.rs index 6fbb25e..eb66578 100644 --- a/src/cmd/base64/mod.rs +++ b/src/cmd/base64/mod.rs @@ -128,7 +128,7 @@ fn get_contents(file: &str) -> Result> { if file == "-" { io::stdin().read_to_string(&mut contents)?; } else { - fs::read_to_string(&file)?; + contents = fs::read_to_string(file)?; } Ok(contents) } diff --git a/src/cmd/bootstrap/mod.rs b/src/cmd/bootstrap/mod.rs index 8f1cf93..e2ac804 100644 --- a/src/cmd/bootstrap/mod.rs +++ b/src/cmd/bootstrap/mod.rs @@ -32,6 +32,7 @@ impl Bootstrap { .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") @@ -88,6 +89,7 @@ impl Bootstrap { .help("Install completions for all supported shells") .short('a') .long("all") + .exclusive(true) .action(ArgAction::SetTrue), Arg::new("bash") .help("Bash shell completions") @@ -161,9 +163,7 @@ impl Cmd for Bootstrap { } fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box> { - let matches = if let Some(m) = matches { - m - } else { + let Some(matches) = matches else { return Err(io::Error::new(ErrorKind::Other, "No input").into()); }; if let Some(prefix) = matches.get_one::("prefix") { diff --git a/src/cmd/dirname/mod.rs b/src/cmd/dirname/mod.rs new file mode 100644 index 0000000..7219b12 --- /dev/null +++ b/src/cmd/dirname/mod.rs @@ -0,0 +1,62 @@ +use super::Cmd; +use clap::{Arg, ArgAction, Command}; +use std::path::Path; + +#[derive(Debug)] +pub struct Dirname { + name: &'static str, + path: Option, +} + +pub const DIRNAME: Dirname = Dirname { + name: "dirname", + path: Some(crate::Path::UsrBin), +}; + +impl Cmd for Dirname { + fn name(&self) -> &str { + self.name + } + + fn cli(&self) -> clap::Command { + Command::new("dirname") + .about("strip last component from file name") + .args([ + Arg::new("zero") + .short('z') + .long("zero") + .help("end each output line with NUL, not newline") + .action(ArgAction::SetTrue), + Arg::new("name").num_args(1..).required(true), + ]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + if let Some(matches) = matches { + if let Some(names) = matches.get_many::("name") { + names.for_each(|name| { + let path = match Path::new(name).parent() { + Some(p) => p, + None => Path::new("."), + }; + let path = path.to_string_lossy(); + let path = if path.is_empty() { + String::from(".") + } else { + path.to_string() + }; + if matches.get_flag("zero") { + print!("{path}\0"); + } else { + println!("{path}"); + } + }); + } + } + Ok(()) + } + + fn path(&self) -> Option { + self.path + } +} diff --git a/src/cmd/echo/mod.rs b/src/cmd/echo/mod.rs index 3e848ed..a12430c 100644 --- a/src/cmd/echo/mod.rs +++ b/src/cmd/echo/mod.rs @@ -23,7 +23,6 @@ impl Cmd for Echo { Command::new(self.name) .about("Display a line of text") .long_about("Echo the STRING(s) to standard output") - .version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher") .args([ Arg::new("inline") diff --git a/src/cmd/factor/mod.rs b/src/cmd/factor/mod.rs new file mode 100644 index 0000000..8c6792c --- /dev/null +++ b/src/cmd/factor/mod.rs @@ -0,0 +1,86 @@ +use crate::Cmd; +use clap::{value_parser, Arg, ArgMatches, Command}; +use std::{ + error::Error, + io::{self, BufRead}, +}; + +#[derive(Debug)] +pub struct Factor { + name: &'static str, + path: Option, +} + +pub const FACTOR: Factor = Factor { + name: "factor", + path: Some(crate::Path::UsrBin), +}; + +impl Cmd for Factor { + fn name(&self) -> &str { + self.name + } + + fn cli(&self) -> Command { + Command::new("factor") + .about("factor numbers") + .after_help( + "Print the prime factors of each specified integer NUMBER. If none are\n\ + specified on the command line, read them from standard input.", + ) + .arg( + Arg::new("number") + .help("the numbers to factor") + .num_args(0..) + .value_parser(value_parser!(u64)), + ) + } + + fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box> { + if let Some(matches) = matches { + match matches.get_many::("number") { + Some(numbers) => { + numbers.for_each(|n| print_factors(*n)); + } + None => { + for line in io::stdin().lock().lines() { + for num in line?.split_whitespace() { + print_factors(num.parse()?); + } + } + } + } + } + Ok(()) + } + + fn path(&self) -> Option { + self.path + } +} + +fn first_factor(num: u64) -> u64 { + if crate::math::is_prime(num) { + return num; + } + for n in 2..=num { + if num % n == 0 { + return n; + } + } + num +} + +fn print_factors(num: u64) { + print!("{num}:"); + let mut n = num; + loop { + let f = first_factor(n); + print!(" {f}"); + if f == n { + break; + } + n /= f; + } + println!(); +} diff --git a/src/cmd/false/mod.rs b/src/cmd/false/mod.rs index 510e120..7a5a1c3 100644 --- a/src/cmd/false/mod.rs +++ b/src/cmd/false/mod.rs @@ -24,7 +24,6 @@ impl Cmd for False { .about("Does nothing unsuccessfully") .long_about("Exit with a status code indicating failure") .author("Nathan Fisher") - .version(env!("CARGO_PKG_VERSION")) } fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box> { diff --git a/src/cmd/head/mod.rs b/src/cmd/head/mod.rs index 472336d..94aafee 100644 --- a/src/cmd/head/mod.rs +++ b/src/cmd/head/mod.rs @@ -2,6 +2,7 @@ use super::Cmd; use crate::Path; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use std::{ + env, error::Error, fs, io::{self, stdin, Read}, @@ -26,14 +27,12 @@ impl Cmd for Head { fn cli(&self) -> Command { Command::new(self.name) - .version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher") .about("Display first lines of a file") .long_about( "Print the first 10 lines of each FILE to standard output.\n\ - With more than one FILE, precede each with a header giving the file name.\n\ - With no FILE, or when FILE is -, read standard input.\n\ - Mandatory arguments to long options are mandatory for short options too." + With more than one FILE, precede each with a header giving the file name.\n\n\ + With no FILE, or when FILE is -, read standard input." ) .args([ Arg::new("FILES") @@ -58,18 +57,45 @@ impl Cmd for Head { .help("Count n number of lines (or bytes if -c is specified).") .short('n') .long("lines") - .default_value("10") .allow_negative_numbers(false) - .value_parser(value_parser!(usize)) + .conflicts_with("num") + .value_parser(value_parser!(usize)), + Arg::new("num") + .short('1') + .short_aliases(['2', '3', '4', '5', '6', '7', '8', '9']) + .hide(true) + .action(ArgAction::Append) ]) } fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box> { - let matches = if let Some(m) = matches { - m - } else { + let args: Vec<_> = env::args().collect(); + let idx = match crate::progname() { + Some(s) if s.as_str() == "head" => 1, + _ => 2, + }; + let mut lines = 10; + if args.len() > idx { + let arg1 = &args[idx]; + if arg1.starts_with('-') && arg1.len() > 1 { + let lines: usize = arg1[1..].parse()?; + let files: Vec<_> = if args.len() > idx + 1 { + args[idx + 1..].iter().map(String::as_str).collect() + } else { + vec!["-"] + }; + for file in &files { + head(file, lines, false, false); + } + return Ok(()); + } + } + let Some(matches) = matches else { return Err(io::Error::new(io::ErrorKind::Other, "No input").into()); }; + if let Some(l) = matches.get_one("LINES") { + lines = *l; + } let files = match matches.get_many::("FILES") { Some(c) => c.map(std::string::ToString::to_string).collect(), None => vec!["-".to_string()], @@ -80,15 +106,7 @@ impl Cmd for Head { if index == 1 && header { println!(); } - head( - &file, - match matches.get_one("LINES") { - Some(c) => *c, - None => 10, - }, - header, - matches.get_flag("BYTES"), - ); + head(&file, lines, header, matches.get_flag("BYTES")); } Ok(()) } diff --git a/src/cmd/hostname/mod.rs b/src/cmd/hostname/mod.rs index f38301c..82bfab3 100644 --- a/src/cmd/hostname/mod.rs +++ b/src/cmd/hostname/mod.rs @@ -21,7 +21,6 @@ impl Cmd for Hostname { fn cli(&self) -> clap::Command { Command::new(self.name) - .version(env!("CARGO_PKG_VERSION")) .author("The JeanG3nie ") .about("Prints the name of the current host. The super-user can set the host name by supplying an argument.") .args([ diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index a3c5d3d..1ee07d6 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -17,7 +17,9 @@ mod chmod; mod cp; mod date; mod dd; +pub mod dirname; pub mod echo; +pub mod factor; pub mod r#false; mod getty; pub mod head; @@ -38,8 +40,11 @@ pub mod r#true; pub use { self::hostname::{Hostname, HOSTNAME}, base32::{Base32, BASE_32}, + self::base64::{Base64, BASE_64}, bootstrap::{Bootstrap, BOOTSTRAP}, + dirname::{Dirname, DIRNAME}, echo::{Echo, ECHO}, + factor::{Factor, FACTOR}, head::{Head, HEAD}, nologin::{Nologin, NOLOGIN}, r#false::{False, FALSE}, diff --git a/src/cmd/nologin/mod.rs b/src/cmd/nologin/mod.rs index a3ece9e..dc2d73d 100644 --- a/src/cmd/nologin/mod.rs +++ b/src/cmd/nologin/mod.rs @@ -20,7 +20,6 @@ impl Cmd for Nologin { fn cli(&self) -> clap::Command { Command::new(self.name) - .version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher") .about("Denies a user account login ability") } diff --git a/src/cmd/shitbox/mod.rs b/src/cmd/shitbox/mod.rs index 86f108f..1e59f19 100644 --- a/src/cmd/shitbox/mod.rs +++ b/src/cmd/shitbox/mod.rs @@ -1,4 +1,6 @@ -use super::{Cmd, BASE_32, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, NOLOGIN, SLEEP, TRUE}; +use super::{ + Cmd, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN, SLEEP, TRUE, +}; use clap::Command; use std::{ error::Error, @@ -25,12 +27,18 @@ impl Cmd for Shitbox { Command::new(self.name) .about("The Harbor Freight multitool of embedded Linux") .version(env!("CARGO_PKG_VERSION")) + .propagate_version(true) .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(), NOLOGIN.cli(), HOSTNAME.cli(), @@ -40,15 +48,16 @@ impl Cmd for Shitbox { } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { - let matches = if let Some(m) = matches { - m - } else { + 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(("nologin", _matches)) => NOLOGIN.run(None)?, diff --git a/src/cmd/sleep/mod.rs b/src/cmd/sleep/mod.rs index 799a4d1..73603d0 100644 --- a/src/cmd/sleep/mod.rs +++ b/src/cmd/sleep/mod.rs @@ -27,7 +27,6 @@ impl Cmd for Sleep { This number must be positive and may contain a decimal fraction.\n\ sleep is commonly used to schedule the execution of other commands" ) - .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) .arg( Arg::new("seconds") diff --git a/src/cmd/true/mod.rs b/src/cmd/true/mod.rs index 18c36fe..56b2d38 100644 --- a/src/cmd/true/mod.rs +++ b/src/cmd/true/mod.rs @@ -23,7 +23,6 @@ impl Cmd for True { Command::new(self.name) .about("Does nothing successfully") .long_about("Exit with a status code indicating success") - .version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher") } diff --git a/src/lib.rs b/src/lib.rs index f0e6d7e..3ddba2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,11 @@ use std::{env, error::Error, path::PathBuf, string::ToString}; pub mod cmd; -use cmd::{ - Cmd, Commands, BASE_32, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, NOLOGIN, SHITBOX, SLEEP, TRUE, +pub use cmd::{ + Cmd, Commands, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN, + SHITBOX, SLEEP, TRUE, }; +pub mod math; #[derive(Debug, Clone, Copy)] pub enum Path { @@ -39,8 +41,8 @@ pub fn run() -> Result<(), Box> { cmd::COMMANDS .set(Commands { items: vec![ - &BASE_32, &BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, - &SHITBOX, + &BASE_32, &BASE_64, &BOOTSTRAP, &DIRNAME, &ECHO, &FALSE, &FACTOR, &HEAD, &HOSTNAME, + &NOLOGIN, &TRUE, &SLEEP, &SHITBOX, ], }) .expect("Cannot register commands"); @@ -48,8 +50,11 @@ pub fn run() -> Result<(), Box> { 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()))?; } diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..0f3677c --- /dev/null +++ b/src/math.rs @@ -0,0 +1,18 @@ +#[must_use] +pub fn is_prime(num: u64) -> bool { + match num { + 0 | 1 => false, + 2 | 3 | 5 | 7 | 11 | 13 => true, + x if x % 2 == 0 => false, + _ => { + let mut x: u64 = 2; + while x < num / 2 { + if num % x == 0 { + return false; + } + x += 1; + } + true + } + } +}