Major API revamp

This commit is contained in:
Nathan Fisher 2022-12-25 18:29:09 -05:00
parent 480960ce2b
commit d190050798
14 changed files with 621 additions and 384 deletions

7
Cargo.lock generated
View File

@ -146,6 +146,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.4.1" version = "6.4.1"
@ -181,6 +187,7 @@ dependencies = [
"clap_complete_nushell", "clap_complete_nushell",
"clap_mangen", "clap_mangen",
"hostname", "hostname",
"once_cell",
] ]
[[package]] [[package]]

View File

@ -11,6 +11,7 @@ clap_complete = "4.0.6"
clap_complete_nushell = "0.1.8" clap_complete_nushell = "0.1.8"
clap_mangen = "0.2.5" clap_mangen = "0.2.5"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
once_cell = "1.16.0"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1

View File

@ -1,31 +0,0 @@
use crate::cmd::{bootstrap, echo, head, hostname, r#false, r#true, sleep};
use clap::Command;
pub fn cli() -> Command {
Command::new("shitbox")
.about("The Harbor Freight multitool of embedded Linux")
.version(env!("CARGO_PKG_VERSION"))
.arg_required_else_help(true)
.subcommands([
bootstrap::cli(),
echo::cli(),
r#false::cli(),
head::cli(),
hostname::cli(),
sleep::cli(),
r#true::cli(),
])
}
pub fn run() {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("echo", _matches)) => echo::run(),
Some(("false", _matches)) => r#false::run(),
Some(("head", matches)) => head::run(matches),
Some(("hostname", matches)) => hostname::run(matches),
Some(("sleep", matches)) => sleep::run(matches),
Some(("true", _matches)) => r#true::run(),
_ => {}
}
}

View File

@ -1,28 +1,32 @@
use crate::cmd; use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use clap_complete::{generate_to, shells, Generator}; use clap_complete::{generate_to, shells, Generator};
use clap_complete_nushell::Nushell; use clap_complete_nushell::Nushell;
use clap_mangen::Man; use clap_mangen::Man;
use std::{ use std::{
fs, io, error::Error,
fs,
io::{self, ErrorKind},
path::{Path, PathBuf}, path::{Path, PathBuf},
process,
}; };
const COMMANDS: [fn() -> clap::Command; 8] = [ pub struct Bootstrap {
cmd::bootstrap::cli, name: &'static str,
cmd::echo::cli, path: Option<crate::Path>,
cmd::r#false::cli, }
cmd::head::cli,
cmd::hostname::cli,
cmd::r#true::cli,
cmd::sleep::cli,
crate::cli::cli,
];
#[must_use] pub const BOOTSTRAP: Bootstrap = Bootstrap {
pub fn cli() -> Command { name: "bootstrap",
Command::new("bootstrap") path: None,
};
impl Cmd for Bootstrap {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
.about("Install shitbox into the filesystem") .about("Install shitbox into the filesystem")
@ -90,11 +94,58 @@ pub fn cli() -> Command {
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
]), ]),
]) ])
}
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches {
m
} else {
return Err(io::Error::new(ErrorKind::Other, "No input").into());
};
if let Some(prefix) = matches.get_one::<String>("prefix") {
let commands: Commands = Commands {
items: vec![
&BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &TRUE, &SLEEP, &SHITBOX,
],
};
match matches.subcommand() {
Some(("manpages", _matches)) => {
commands.manpages(prefix)?;
}
Some(("completions", matches)) => {
commands.completions(prefix, matches)?;
}
Some(("all", _matches)) => {
commands.manpages(prefix)?;
commands.completions(prefix, matches)?;
}
_ => {}
}
}
Ok(())
}
fn path(&self) -> Option<crate::Path> {
self.path
}
} }
fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> { struct Commands<'a> {
let cmd = f(); items: Vec<&'a dyn Cmd>,
let fname = match cmd.get_name() { }
impl<'a> Commands<'a> {
fn manpages(&self, prefix: &str) -> Result<(), io::Error> {
println!("Generating Unix man pages:");
self.items
.iter()
.try_for_each(|cmd| Self::manpage(prefix, cmd))?;
Ok(())
}
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(), "bootstrap" => "shitbox-bootstrap.1".to_string(),
s => format!("{s}.1"), s => format!("{s}.1"),
}; };
@ -104,95 +155,95 @@ fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
} }
let mut outfile = outdir; let mut outfile = outdir;
outfile.push(fname); outfile.push(fname);
let man = Man::new(cmd); let man = Man::new(command);
let mut buffer: Vec<u8> = vec![]; let mut buffer: Vec<u8> = vec![];
man.render(&mut buffer)?; man.render(&mut buffer)?;
fs::write(&outfile, buffer)?; fs::write(&outfile, buffer)?;
println!(" {}", outfile.display()); println!(" {}", outfile.display());
Ok(()) Ok(())
} }
fn manpages(prefix: &str) -> Result<(), io::Error> { fn completion(outdir: &Path, cmd: &&dyn Cmd, gen: impl Generator) -> Result<(), io::Error> {
println!("Generating Unix man pages:"); let name = cmd.name();
COMMANDS.iter().try_for_each(|cmd| manpage(prefix, &cmd))?; let mut cmd = cmd.cli();
Ok(())
}
fn generate_completions(
outdir: &Path,
f: &dyn Fn() -> Command,
gen: impl Generator,
) -> Result<(), io::Error> {
let cmd = f();
let name = cmd.get_name();
let mut cmd = cmd.clone();
if !outdir.exists() { if !outdir.exists() {
fs::create_dir_all(&outdir)?; fs::create_dir_all(outdir)?;
} }
let path = generate_to(gen, &mut cmd, name, outdir)?; let path = generate_to(gen, &mut cmd, name, outdir)?;
println!(" {}", path.display()); println!(" {}", path.display());
Ok(()) Ok(())
} }
fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> { fn completions(&self, prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
println!("Generating completions:"); println!("Generating completions:");
if matches.get_flag("bash") || matches.get_flag("all") { if matches.get_flag("bash") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"] let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
.iter() .iter()
.collect(); .collect();
COMMANDS self.items
.iter() .iter()
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Bash))?; .try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Bash))?;
} }
if matches.get_flag("fish") || matches.get_flag("all") { if matches.get_flag("fish") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect(); let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
COMMANDS self.items
.iter() .iter()
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Fish))?; .try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Fish))?;
} }
if matches.get_flag("nu") || matches.get_flag("all") { if matches.get_flag("nu") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect(); let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
COMMANDS self.items
.iter() .iter()
.try_for_each(|cmd| generate_completions(&outdir, &cmd, Nushell))?; .try_for_each(|cmd| Self::completion(&outdir, cmd, Nushell))?;
} }
if matches.get_flag("pwsh") || matches.get_flag("all") { if matches.get_flag("pwsh") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect(); let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
COMMANDS self.items
.iter() .iter()
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::PowerShell))?; .try_for_each(|cmd| Self::completion(&outdir, cmd, shells::PowerShell))?;
} }
if matches.get_flag("zsh") || matches.get_flag("all") { if matches.get_flag("zsh") || matches.get_flag("all") {
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect(); let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
COMMANDS self.items
.iter() .iter()
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Zsh))?; .try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Zsh))?;
} }
Ok(()) Ok(())
}
} }
pub fn run(matches: &ArgMatches) { /*
if let Some(prefix) = matches.get_one::<String>("prefix") { fn get_path(prefix: &str, name: &str, usr: bool) -> Option<PathBuf> {
match matches.subcommand() { let mut path = PathBuf::from(prefix);
Some(("manpages", _matches)) => { let binpath = match name {
if let Err(e) = manpages(prefix) { "bootstrap" => Bootstrap::path(),
eprintln!("{e}"); "echo" => Echo::path(),
process::exit(1); "false" => False::path(),
"head" => Head::path(),
"hostname" => Hostname::path(),
"true" => True::path(),
"sleep" => Sleep::path(),
"shitbox" => Shitbox::path(),
_ => todo!(),
};
match binpath {
Some(crate::Path::Bin) => path.push("bin"),
Some(crate::Path::Sbin) => path.push("sbin"),
Some(crate::Path::UsrBin) => {
if usr {
path.push("usr");
} }
path.push("bin");
} }
Some(("completions", matches)) => { Some(crate::Path::UsrSbin) => {
if let Err(e) = completions(prefix, matches) { if usr {
eprintln!("{e}"); path.push("usr");
process::exit(1);
} }
path.push("sbin");
} }
Some(("all", _matches)) => { None => return None,
if let Err(e) = manpages(prefix) {
eprintln!("{e}");
process::exit(1);
}
}
_ => {}
}
} }
path.push(name);
Some(path)
} }
*/

View File

@ -1,12 +1,25 @@
use super::Cmd;
use crate::Path; use crate::Path;
use clap::{Arg, Command}; use clap::{Arg, Command};
use std::env; use std::{env, error::Error};
pub const PATH: Path = Path::Bin; pub struct Echo {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const ECHO: Echo = Echo {
pub fn cli() -> Command { name: "echo",
Command::new("echo") path: Some(Path::Bin),
};
impl Cmd for Echo {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
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")) .version(env!("CARGO_PKG_VERSION"))
@ -17,12 +30,12 @@ pub fn cli() -> Command {
.help("Do not output a trailing newline"), .help("Do not output a trailing newline"),
Arg::new("STRING").num_args(1..), Arg::new("STRING").num_args(1..),
]) ])
} }
pub fn run() { fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let idx = match crate::progname() { let idx = match crate::progname() {
Some(s) if s.as_str() == "echo" => 1, Some(s) if s.as_str() == self.name() => 1,
Some(_) => 2, Some(_) => 2,
None => unreachable!(), None => unreachable!(),
}; };
@ -39,4 +52,10 @@ pub fn run() {
if !n { if !n {
println!(); println!();
} }
Ok(())
}
fn path(&self) -> Option<crate::Path> {
self.path
}
} }

View File

@ -1,18 +1,36 @@
use super::Cmd;
use crate::Path; use crate::Path;
use clap::Command; use clap::Command;
use std::process; use std::{error::Error, process};
pub const PATH: Path = Path::Bin; pub struct False {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const FALSE: False = False {
pub fn cli() -> Command { name: "false",
Command::new("false") path: Some(Path::Bin),
};
impl Cmd for False {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.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")) .version(env!("CARGO_PKG_VERSION"))
} }
pub fn run() { fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
process::exit(1); process::exit(1);
}
fn path(&self) -> Option<Path> {
self.path
}
} }

View File

@ -1,14 +1,30 @@
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::fs; use std::{
use std::io::{stdin, Read}; error::Error,
use std::process; fs,
io::{self, stdin, Read},
process,
};
pub const PATH: Path = Path::Bin; pub struct Head {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const HEAD: Head = Head {
pub fn cli() -> Command { name: "head",
Command::new("head") path: Some(Path::Bin),
};
impl Cmd for Head {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> Command {
Command::new(self.name)
.version(env!("CARGO_PKG_VERSION")) .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")
@ -45,6 +61,40 @@ pub fn cli() -> Command {
.allow_negative_numbers(false) .allow_negative_numbers(false)
.value_parser(value_parser!(usize)) .value_parser(value_parser!(usize))
]) ])
}
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches {
m
} else {
return Err(io::Error::new(io::ErrorKind::Other, "No input").into());
};
let files = match matches.get_many::<String>("FILES") {
Some(c) => c.map(std::string::ToString::to_string).collect(),
None => vec!["-".to_string()],
};
let header =
!matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("HEADER") };
for (index, file) in files.into_iter().enumerate() {
if index == 1 && header {
println!();
}
head(
&file,
match matches.get_one("LINES") {
Some(c) => *c,
None => 10,
},
header,
matches.get_flag("BYTES"),
);
}
Ok(())
}
fn path(&self) -> Option<Path> {
self.path
}
} }
fn head(file: &str, count: usize, header: bool, bytes: bool) { fn head(file: &str, count: usize, header: bool, bytes: bool) {
@ -90,25 +140,3 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
} }
} }
} }
pub fn run(matches: &ArgMatches) {
let files = match matches.get_many::<String>("FILES") {
Some(c) => c.map(std::string::ToString::to_string).collect(),
None => vec!["-".to_string()],
};
let header = !matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("HEADER") };
for (index, file) in files.into_iter().enumerate() {
if index == 1 && header {
println!();
}
head(
&file,
match matches.get_one("LINES") {
Some(c) => *c,
None => 10,
},
header,
matches.get_flag("BYTES"),
);
}
}

View File

@ -1,55 +1,67 @@
use super::Cmd;
use crate::Path; use crate::Path;
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use std::process; use std::{error::Error, io};
pub const PATH: Path = Path::Bin; pub struct Hostname {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const HOSTNAME: Hostname = Hostname {
pub fn cli() -> Command { name: "hostname",
Command::new("hostname") path: Some(Path::Bin),
};
impl Cmd for Hostname {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.version(env!("CARGO_PKG_VERSION")) .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.")
.arg( .args([
Arg::new("NAME") Arg::new("NAME")
.help("name to set") .help("name to set"),
)
.arg(
Arg::new("STRIP") Arg::new("STRIP")
.help("Removes any domain information from the printed name.") .help("Removes any domain information from the printed name.")
.short('s') .short('s')
.long("strip") .long("strip")
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
) ])
}
pub fn run(matches: &ArgMatches) {
if let Some(name) = matches.get_one::<String>("NAME") {
if let Err(e) = hostname::set(name) {
eprintln!("{e}");
process::exit(1);
} }
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = matches.unwrap();
if let Some(name) = matches.get_one::<String>("NAME") {
hostname::set(name)?;
Ok(())
} else { } else {
match hostname::get() { let hostname = hostname::get()?;
Ok(hostname) => {
if matches.get_flag("STRIP") { if matches.get_flag("STRIP") {
println!( println!(
"{}", "{}",
if let Some(s) = hostname.to_string_lossy().split('.').next() { if let Some(s) = hostname.to_string_lossy().split('.').next() {
s s
} else { } else {
eprintln!("hostname: missing operand"); return Err(io::Error::new(
process::exit(1); io::ErrorKind::Other,
"hostname: missing operand",
)
.into());
} }
); );
} else { } else {
println!("{}", hostname.to_string_lossy()); println!("{}", hostname.to_string_lossy());
} }
} Ok(())
Err(e) => {
eprintln!("{e}");
process::exit(1);
} }
} }
fn path(&self) -> Option<crate::Path> {
self.path
} }
} }

View File

@ -1,3 +1,6 @@
use clap::ArgMatches;
use std::error::Error;
pub mod bootstrap; pub mod bootstrap;
mod cat; mod cat;
mod chmod; mod chmod;
@ -16,6 +19,25 @@ mod mv;
mod pwd; mod pwd;
mod rm; mod rm;
mod rmdir; mod rmdir;
pub mod shitbox;
pub mod sleep; pub mod sleep;
mod sync; mod sync;
pub mod r#true; pub mod r#true;
pub use {
self::hostname::{Hostname, HOSTNAME},
bootstrap::{Bootstrap, BOOTSTRAP},
echo::{Echo, ECHO},
head::{Head, HEAD},
r#false::{False, FALSE},
r#true::{True, TRUE},
shitbox::{Shitbox, SHITBOX},
sleep::{Sleep, SLEEP},
};
pub trait Cmd {
fn name(&self) -> &str;
fn cli(&self) -> clap::Command;
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>>;
fn path(&self) -> Option<crate::Path>;
}

60
src/cmd/shitbox/mod.rs Normal file
View File

@ -0,0 +1,60 @@
use super::{Cmd, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, SLEEP, TRUE};
use clap::Command;
use std::{
error::Error,
io::{self, ErrorKind},
};
pub struct Shitbox {
name: &'static str,
path: Option<crate::Path>,
}
pub const SHITBOX: Shitbox = Shitbox {
name: "shitbox",
path: None,
};
impl Cmd for Shitbox {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.about("The Harbor Freight multitool of embedded Linux")
.version(env!("CARGO_PKG_VERSION"))
.arg_required_else_help(true)
.subcommands([
BOOTSTRAP.cli(),
ECHO.cli(),
FALSE.cli(),
HEAD.cli(),
HOSTNAME.cli(),
SLEEP.cli(),
TRUE.cli(),
])
}
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
let matches = if let Some(m) = matches {
m
} else {
return Err(Box::new(io::Error::new(ErrorKind::Other, "No input")));
};
match matches.subcommand() {
Some(("echo", _matches)) => ECHO.run(None)?,
Some(("false", _matches)) => FALSE.run(None)?,
Some(("head", matches)) => HEAD.run(Some(matches))?,
Some(("hostname", matches)) => HOSTNAME.run(Some(matches))?,
Some(("sleep", matches)) => SLEEP.run(Some(matches))?,
Some(("true", _matches)) => TRUE.run(None)?,
_ => unimplemented!(),
}
Ok(())
}
fn path(&self) -> Option<crate::Path> {
self.path
}
}

View File

@ -1,12 +1,25 @@
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::{env, thread, time::Duration}; use std::{env, error::Error, thread, time::Duration};
pub const PATH: Path = Path::Bin; pub struct Sleep {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const SLEEP: Sleep = Sleep {
pub fn cli() -> Command { name: "sleep",
Command::new("sleep") path: Some(Path::Bin),
};
impl Cmd for Sleep {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.about("Suspend execution for an interval of time") .about("Suspend execution for an interval of time")
.long_about( .long_about(
"The sleep utility suspends execution for a minimum of the specified number of seconds.\n\ "The sleep utility suspends execution for a minimum of the specified number of seconds.\n\
@ -24,14 +37,20 @@ pub fn cli() -> Command {
.required(true) .required(true)
.action(ArgAction::Set) .action(ArgAction::Set)
) )
} }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn run(matches: &ArgMatches) { fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
if let Some(raw) = matches.get_one::<f64>("seconds") { if let Some(raw) = matches.unwrap().get_one::<f64>("seconds") {
let seconds = *raw as u64; let seconds = *raw as u64;
let nanos = ((raw % 1.0) * 10e-9) as u32; let nanos = ((raw % 1.0) * 10e-9) as u32;
let s = Duration::new(seconds, nanos); let s = Duration::new(seconds, nanos);
thread::sleep(s); thread::sleep(s);
} }
Ok(())
}
fn path(&self) -> Option<Path> {
self.path
}
} }

View File

@ -1,18 +1,36 @@
use super::Cmd;
use crate::Path; use crate::Path;
use clap::Command; use clap::Command;
use std::process; use std::{error::Error, process};
pub const PATH: Path = Path::Bin; pub struct True {
name: &'static str,
path: Option<Path>,
}
#[must_use] pub const TRUE: True = True {
pub fn cli() -> Command { name: "true",
Command::new("true") path: Some(Path::Bin),
};
impl Cmd for True {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
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")) .version(env!("CARGO_PKG_VERSION"))
.author("Nathan Fisher") .author("Nathan Fisher")
} }
pub fn run() { fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
process::exit(0); process::exit(0);
}
fn path(&self) -> Option<Path> {
self.path
}
} }

View File

@ -1,9 +1,10 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
use std::{env, path::PathBuf, string::ToString}; use std::{env, error::Error, path::PathBuf, string::ToString};
mod cli;
pub mod cmd; pub mod cmd;
use cmd::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
#[derive(Debug, Clone, Copy)]
pub enum Path { pub enum Path {
Bin, Bin,
Sbin, Sbin,
@ -23,17 +24,24 @@ pub fn progname() -> Option<String> {
.flatten() .flatten()
} }
pub fn run() { 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() {
"echo" => cmd::echo::run(), "echo" => ECHO.run(None)?,
"false" => cmd::r#false::run(), "false" => FALSE.run(None)?,
"head" => cmd::head::run(&cmd::head::cli().get_matches()), "head" => {
"hostname" => cmd::hostname::run(&cmd::hostname::cli().get_matches()), HEAD.run(Some(&HEAD.cli().get_matches()))?;
"true" => cmd::r#true::run(), }
"shitbox" => cli::run(), "hostname" => HOSTNAME.run(Some(&HOSTNAME.cli().get_matches()))?,
"sleep" => cmd::sleep::run(&cmd::sleep::cli().get_matches()), "true" => TRUE.run(None)?,
"shitbox" => {
SHITBOX.run(Some(&SHITBOX.cli().get_matches()))?;
}
"sleep" => {
SLEEP.run(Some(&SLEEP.cli().get_matches()))?;
}
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
Ok(())
} }

View File

@ -1,3 +1,8 @@
use std::process;
fn main() { fn main() {
shitbox::run(); if let Err(e) = shitbox::run() {
eprintln!("{e}");
process::exit(1);
}
} }