Merge branch 'odin' of git.hitchhiker-linux.org:jeang3nie/shitbox into odin

This commit is contained in:
Nathan Fisher 2023-01-03 23:21:42 -05:00
commit dff8561875
28 changed files with 582 additions and 51 deletions

BIN
pkg/bin/shitbox Executable file

Binary file not shown.

View File

@ -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 <jeang3nie@hitchhiker\-linux.org>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <jeang3nie@hitchhiker\-linux.org>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,5 @@
use super::Cmd; use super::Cmd;
use clap::{Arg, ArgAction, Command}; use clap::{value_parser, Arg, ArgAction, Command};
use data_encoding::BASE32; use data_encoding::BASE32;
use std::{ use std::{
fs, fs,
@ -25,8 +25,7 @@ impl Cmd for Base32 {
fn cli(&self) -> clap::Command { fn cli(&self) -> clap::Command {
Command::new("base32") Command::new("base32")
.version(env!("CARGO_PKG_VERSION")) .author("Nathan Fisher")
.author("The JeanG3nie <jeang3nie@hitchhiker-linux.org>")
.about("Base32 encode/decode data and print to standard output") .about("Base32 encode/decode data and print to standard output")
.args([ .args([
Arg::new("INPUT") Arg::new("INPUT")
@ -46,6 +45,7 @@ impl Cmd for Base32 {
.help("Wrap encoded lines after n characters") .help("Wrap encoded lines after n characters")
.short('w') .short('w')
.long("wrap") .long("wrap")
.value_parser(value_parser!(usize))
.default_value("76"), .default_value("76"),
Arg::new("VERBOSE") Arg::new("VERBOSE")
.help("Display a header naming each file") .help("Display a header naming each file")
@ -61,20 +61,19 @@ impl Cmd for Base32 {
} }
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> { fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
let matches = match matches { let Some(matches) = matches else {
Some(m) => m, return Err(io::Error::new(io::ErrorKind::Other, "No input").into());
None => return Err(io::Error::new(io::ErrorKind::Other, "No input").into()),
}; };
let files: Vec<_> = match matches.get_many::<String>("INPUT") { let files: Vec<_> = match matches.get_many::<String>("INPUT") {
Some(c) => c.map(|x| x.to_owned()).collect(), Some(c) => c.map(|x| x.clone()).collect(),
None => vec![String::from("-")], None => vec![String::from("-")],
}; };
let len = files.len(); let len = files.len();
for (index, file) in files.into_iter().enumerate() { for (index, file) in files.into_iter().enumerate() {
if { len > 1 || matches.get_flag("VERBOSE") } && !matches.get_flag("QUIET") { if { len > 1 || matches.get_flag("VERBOSE") } && !matches.get_flag("QUIET") {
match index { match index {
0 => println!("===> {} <===", file), 0 => println!("===> {file} <==="),
_ => println!("\n===> {} <===", file), _ => println!("\n===> {file} <==="),
}; };
} else if index > 0 { } else if index > 0 {
println!(); println!();
@ -111,14 +110,14 @@ fn decode_base32(mut contents: String, ignore: bool) {
let decoded = match BASE32.decode(contents.as_bytes()) { let decoded = match BASE32.decode(contents.as_bytes()) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
eprintln!("base32: {}", e); eprintln!("base32: {e}");
process::exit(1); process::exit(1);
} }
}; };
let output = match String::from_utf8(decoded) { let output = match String::from_utf8(decoded) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
eprintln!("base32: {}", e); eprintln!("base32: {e}");
process::exit(1); process::exit(1);
} }
}; };
@ -134,7 +133,7 @@ fn encode_base32(contents: &str, wrap: usize) {
.map(|c| c.iter().collect::<String>()) .map(|c| c.iter().collect::<String>())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
for line in &encoded { 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) { match io::stdin().read_to_string(&mut contents) {
Ok(_) => true, Ok(_) => true,
Err(e) => { Err(e) => {
eprintln!("base32: {}", e); eprintln!("base32: {e}");
process::exit(1); process::exit(1);
} }
}; };
} else { } else {
contents = match fs::read_to_string(&file) { contents = match fs::read_to_string(file) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
eprintln!("base32: {}", e); eprintln!("base32: {e}");
process::exit(1); process::exit(1);
} }
}; };

View File

@ -128,7 +128,7 @@ fn get_contents(file: &str) -> Result<String, Box<dyn Error>> {
if file == "-" { if file == "-" {
io::stdin().read_to_string(&mut contents)?; io::stdin().read_to_string(&mut contents)?;
} else { } else {
fs::read_to_string(&file)?; contents = fs::read_to_string(file)?;
} }
Ok(contents) Ok(contents)
} }

View File

@ -32,6 +32,7 @@ impl Bootstrap {
.help("Install completions for all supported shells") .help("Install completions for all supported shells")
.short('a') .short('a')
.long("all") .long("all")
.conflicts_with_all(["bash", "fish", "nu", "pwsh", "zsh"])
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("bash") Arg::new("bash")
.help("Bash shell completions") .help("Bash shell completions")
@ -88,6 +89,7 @@ impl Bootstrap {
.help("Install completions for all supported shells") .help("Install completions for all supported shells")
.short('a') .short('a')
.long("all") .long("all")
.exclusive(true)
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
Arg::new("bash") Arg::new("bash")
.help("Bash shell completions") .help("Bash shell completions")
@ -161,9 +163,7 @@ impl Cmd for Bootstrap {
} }
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> { fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches { let Some(matches) = matches else {
m
} else {
return Err(io::Error::new(ErrorKind::Other, "No input").into()); return Err(io::Error::new(ErrorKind::Other, "No input").into());
}; };
if let Some(prefix) = matches.get_one::<String>("prefix") { if let Some(prefix) = matches.get_one::<String>("prefix") {

62
src/cmd/dirname/mod.rs Normal file
View File

@ -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<crate::Path>,
}
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<dyn std::error::Error>> {
if let Some(matches) = matches {
if let Some(names) = matches.get_many::<String>("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<crate::Path> {
self.path
}
}

View File

@ -23,7 +23,6 @@ impl Cmd for Echo {
Command::new(self.name) Command::new(self.name)
.about("Display a line of text") .about("Display a line of text")
.long_about("Echo the STRING(s) to standard output") .long_about("Echo the STRING(s) to standard output")
.version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
.args([ .args([
Arg::new("inline") Arg::new("inline")

86
src/cmd/factor/mod.rs Normal file
View File

@ -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<crate::Path>,
}
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<dyn Error>> {
if let Some(matches) = matches {
match matches.get_many::<u64>("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<crate::Path> {
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!();
}

View File

@ -24,7 +24,6 @@ impl Cmd for False {
.about("Does nothing unsuccessfully") .about("Does nothing unsuccessfully")
.long_about("Exit with a status code indicating failure") .long_about("Exit with a status code indicating failure")
.author("Nathan Fisher") .author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
} }
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> { fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {

View File

@ -2,6 +2,7 @@ use super::Cmd;
use crate::Path; use crate::Path;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use std::{ use std::{
env,
error::Error, error::Error,
fs, fs,
io::{self, stdin, Read}, io::{self, stdin, Read},
@ -26,14 +27,12 @@ impl Cmd for Head {
fn cli(&self) -> Command { fn cli(&self) -> Command {
Command::new(self.name) Command::new(self.name)
.version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
.about("Display first lines of a file") .about("Display first lines of a file")
.long_about( .long_about(
"Print the first 10 lines of each FILE to standard output.\n\ "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 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.\n\ With no FILE, or when FILE is -, read standard input."
Mandatory arguments to long options are mandatory for short options too."
) )
.args([ .args([
Arg::new("FILES") Arg::new("FILES")
@ -58,18 +57,45 @@ impl Cmd for Head {
.help("Count n number of lines (or bytes if -c is specified).") .help("Count n number of lines (or bytes if -c is specified).")
.short('n') .short('n')
.long("lines") .long("lines")
.default_value("10")
.allow_negative_numbers(false) .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<dyn Error>> { fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches { let args: Vec<_> = env::args().collect();
m let idx = match crate::progname() {
} else { 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()); 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::<String>("FILES") { let files = match matches.get_many::<String>("FILES") {
Some(c) => c.map(std::string::ToString::to_string).collect(), Some(c) => c.map(std::string::ToString::to_string).collect(),
None => vec!["-".to_string()], None => vec!["-".to_string()],
@ -80,15 +106,7 @@ impl Cmd for Head {
if index == 1 && header { if index == 1 && header {
println!(); println!();
} }
head( head(&file, lines, header, matches.get_flag("BYTES"));
&file,
match matches.get_one("LINES") {
Some(c) => *c,
None => 10,
},
header,
matches.get_flag("BYTES"),
);
} }
Ok(()) Ok(())
} }

View File

@ -21,7 +21,6 @@ impl Cmd for Hostname {
fn cli(&self) -> clap::Command { fn cli(&self) -> clap::Command {
Command::new(self.name) Command::new(self.name)
.version(env!("CARGO_PKG_VERSION"))
.author("The JeanG3nie <jeang3nie@hitchhiker-linux.org>") .author("The JeanG3nie <jeang3nie@hitchhiker-linux.org>")
.about("Prints the name of the current host. The super-user can set the host name by supplying an argument.") .about("Prints the name of the current host. The super-user can set the host name by supplying an argument.")
.args([ .args([

View File

@ -17,7 +17,9 @@ mod chmod;
mod cp; mod cp;
mod date; mod date;
mod dd; mod dd;
pub mod dirname;
pub mod echo; pub mod echo;
pub mod factor;
pub mod r#false; pub mod r#false;
mod getty; mod getty;
pub mod head; pub mod head;
@ -38,8 +40,11 @@ pub mod r#true;
pub use { pub use {
self::hostname::{Hostname, HOSTNAME}, self::hostname::{Hostname, HOSTNAME},
base32::{Base32, BASE_32}, base32::{Base32, BASE_32},
self::base64::{Base64, BASE_64},
bootstrap::{Bootstrap, BOOTSTRAP}, bootstrap::{Bootstrap, BOOTSTRAP},
dirname::{Dirname, DIRNAME},
echo::{Echo, ECHO}, echo::{Echo, ECHO},
factor::{Factor, FACTOR},
head::{Head, HEAD}, head::{Head, HEAD},
nologin::{Nologin, NOLOGIN}, nologin::{Nologin, NOLOGIN},
r#false::{False, FALSE}, r#false::{False, FALSE},

View File

@ -20,7 +20,6 @@ impl Cmd for Nologin {
fn cli(&self) -> clap::Command { fn cli(&self) -> clap::Command {
Command::new(self.name) Command::new(self.name)
.version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
.about("Denies a user account login ability") .about("Denies a user account login ability")
} }

View File

@ -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 clap::Command;
use std::{ use std::{
error::Error, error::Error,
@ -25,12 +27,18 @@ impl Cmd for Shitbox {
Command::new(self.name) Command::new(self.name)
.about("The Harbor Freight multitool of embedded Linux") .about("The Harbor Freight multitool of embedded Linux")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.propagate_version(true)
.arg_required_else_help(true) .arg_required_else_help(true)
.subcommand_value_name("APPLET")
.subcommand_help_heading("APPLETS")
.subcommands([ .subcommands([
BASE_32.cli(), BASE_32.cli(),
BASE_64.cli(),
BOOTSTRAP.cli(), BOOTSTRAP.cli(),
DIRNAME.cli(),
ECHO.cli(), ECHO.cli(),
FALSE.cli(), FALSE.cli(),
FACTOR.cli(),
HEAD.cli(), HEAD.cli(),
NOLOGIN.cli(), NOLOGIN.cli(),
HOSTNAME.cli(), HOSTNAME.cli(),
@ -40,15 +48,16 @@ impl Cmd for Shitbox {
} }
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> { fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches { let Some(matches) = matches else {
m
} else {
return Err(Box::new(io::Error::new(ErrorKind::Other, "No input"))); return Err(Box::new(io::Error::new(ErrorKind::Other, "No input")));
}; };
match matches.subcommand() { match matches.subcommand() {
Some(("base32", matches)) => BASE_32.run(Some(matches))?, Some(("base32", matches)) => BASE_32.run(Some(matches))?,
Some(("base64", matches)) => BASE_64.run(Some(matches))?,
Some(("bootstrap", matches)) => BOOTSTRAP.run(Some(matches))?, Some(("bootstrap", matches)) => BOOTSTRAP.run(Some(matches))?,
Some(("dirname", matches)) => DIRNAME.run(Some(matches))?,
Some(("echo", _matches)) => ECHO.run(None)?, Some(("echo", _matches)) => ECHO.run(None)?,
Some(("factor", matches)) => FACTOR.run(Some(matches))?,
Some(("false", _matches)) => FALSE.run(None)?, Some(("false", _matches)) => FALSE.run(None)?,
Some(("head", matches)) => HEAD.run(Some(matches))?, Some(("head", matches)) => HEAD.run(Some(matches))?,
Some(("nologin", _matches)) => NOLOGIN.run(None)?, Some(("nologin", _matches)) => NOLOGIN.run(None)?,

View File

@ -27,7 +27,6 @@ impl Cmd for Sleep {
This number must be positive and may contain a decimal fraction.\n\ This number must be positive and may contain a decimal fraction.\n\
sleep is commonly used to schedule the execution of other commands" sleep is commonly used to schedule the execution of other commands"
) )
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS")) .author(env!("CARGO_PKG_AUTHORS"))
.arg( .arg(
Arg::new("seconds") Arg::new("seconds")

View File

@ -23,7 +23,6 @@ impl Cmd for True {
Command::new(self.name) Command::new(self.name)
.about("Does nothing successfully") .about("Does nothing successfully")
.long_about("Exit with a status code indicating success") .long_about("Exit with a status code indicating success")
.version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
} }

View File

@ -2,9 +2,11 @@
use std::{env, error::Error, path::PathBuf, string::ToString}; use std::{env, error::Error, path::PathBuf, string::ToString};
pub mod cmd; pub mod cmd;
use cmd::{ pub use cmd::{
Cmd, Commands, BASE_32, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, NOLOGIN, SHITBOX, SLEEP, TRUE, Cmd, Commands, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN,
SHITBOX, SLEEP, TRUE,
}; };
pub mod math;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Path { pub enum Path {
@ -39,8 +41,8 @@ pub fn run() -> Result<(), Box<dyn Error>> {
cmd::COMMANDS cmd::COMMANDS
.set(Commands { .set(Commands {
items: vec![ items: vec![
&BASE_32, &BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &BASE_32, &BASE_64, &BOOTSTRAP, &DIRNAME, &ECHO, &FALSE, &FACTOR, &HEAD, &HOSTNAME,
&SHITBOX, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX,
], ],
}) })
.expect("Cannot register commands"); .expect("Cannot register commands");
@ -48,8 +50,11 @@ pub fn run() -> Result<(), Box<dyn Error>> {
if let Some(progname) = progname() { if let Some(progname) = progname() {
match progname.as_str() { match progname.as_str() {
"base32" => BASE_32.run(Some(&BASE_32.cli().get_matches()))?, "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)?, "echo" => ECHO.run(None)?,
"false" => FALSE.run(None)?, "false" => FALSE.run(None)?,
"factor" => FACTOR.run(Some(&FACTOR.cli().get_matches()))?,
"head" => { "head" => {
HEAD.run(Some(&HEAD.cli().get_matches()))?; HEAD.run(Some(&HEAD.cli().get_matches()))?;
} }

18
src/math.rs Normal file
View File

@ -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
}
}
}