feat: do error handling with anyhow, slight refactor (#6)

Co-authored-by: user0-07161 <user0thenyancat@proton.me>
This commit is contained in:
User0 2025-09-14 19:42:10 +02:00 committed by GitHub
parent 5413c35fb4
commit 0cd7b3af12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 244 additions and 151 deletions

12
Cargo.lock generated
View file

@ -2,9 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "anyhow"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]] [[package]]
name = "boxutils" name = "boxutils"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -16,6 +25,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "coreutils" name = "coreutils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"boxutils", "boxutils",
"hostname", "hostname",
"num_cpus", "num_cpus",
@ -58,6 +68,7 @@ dependencies = [
name = "rebox" name = "rebox"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"boxutils", "boxutils",
"coreutils", "coreutils",
"shell", "shell",
@ -67,6 +78,7 @@ dependencies = [
name = "shell" name = "shell"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"boxutils", "boxutils",
] ]

View file

@ -11,11 +11,13 @@ resolver = "3"
coreutils = { path = "./coreutils" } coreutils = { path = "./coreutils" }
boxutils = { path = "./utils" } boxutils = { path = "./utils" }
shell = { path = "./shell" } shell = { path = "./shell" }
anyhow = "1.0.99"
[dependencies] [dependencies]
coreutils.workspace = true coreutils.workspace = true
boxutils.workspace = true boxutils.workspace = true
shell.workspace = true shell.workspace = true
anyhow.workspace = true
[[bin]] [[bin]]
name = "box" name = "box"

View file

@ -5,5 +5,8 @@ edition = "2024"
[dependencies] [dependencies]
boxutils.workspace = true boxutils.workspace = true
anyhow.workspace = true
# TODO: get rid of these
hostname = "0.4.0" hostname = "0.4.0"
num_cpus = "1.16.0" num_cpus = "1.16.0"

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use boxutils::encoding::base32; use boxutils::encoding::base32;
@ -11,7 +12,7 @@ use std::io::Read;
pub struct Base32; pub struct Base32;
impl Command for Base32 { impl Command for Base32 {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-d") // decode flag .add_flag("-d") // decode flag
.parse_args("base32"); .parse_args("base32");
@ -22,7 +23,7 @@ impl Command for Base32 {
let mut file: Box<dyn Read> = match &args.get_normal_args()[..] { let mut file: Box<dyn Read> = match &args.get_normal_args()[..] {
[] => Box::new(std::io::stdin()), [] => Box::new(std::io::stdin()),
[file] => Box::new(OpenOptions::new().read(true).open(file).unwrap()), [file] => Box::new(OpenOptions::new().read(true).open(file).unwrap()),
_ => panic!("base32: multiple files provided"), _ => bail!("base32: multiple files provided"),
}; };
let mut buffer = String::new(); let mut buffer = String::new();
@ -35,5 +36,7 @@ impl Command for Base32 {
println!("{}", data); println!("{}", data);
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use boxutils::encoding::base64; use boxutils::encoding::base64;
@ -11,7 +12,7 @@ use std::io::Read;
pub struct Base64; pub struct Base64;
impl Command for Base64 { impl Command for Base64 {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-d") // decode flag .add_flag("-d") // decode flag
.parse_args("base64"); .parse_args("base64");
@ -22,7 +23,7 @@ impl Command for Base64 {
let mut file: Box<dyn Read> = match &args.get_normal_args()[..] { let mut file: Box<dyn Read> = match &args.get_normal_args()[..] {
[] => Box::new(std::io::stdin()), [] => Box::new(std::io::stdin()),
[file] => Box::new(OpenOptions::new().read(true).open(file).unwrap()), [file] => Box::new(OpenOptions::new().read(true).open(file).unwrap()),
_ => panic!("base64: multiple files provided"), _ => bail!("base64: multiple files provided"),
}; };
let mut buffer = String::new(); let mut buffer = String::new();
@ -35,5 +36,7 @@ impl Command for Base64 {
println!("{}", data); println!("{}", data);
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use boxutils::commands::Command; use boxutils::commands::Command;
use boxutils::commands::get_args; use boxutils::commands::get_args;
use std::env; use std::env;
@ -7,9 +8,9 @@ use std::io::{self, BufReader, Read, Write};
pub struct Cat; pub struct Cat;
impl Command for Cat { impl Command for Cat {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args: Vec<String> = env::args().collect::<Vec<_>>().clone(); let args: Vec<String> = env::args().collect::<Vec<_>>().clone();
let arguments: Vec<String> = get_args(String::from("cat"), args); let arguments: Vec<String> = get_args("cat".to_owned(), args);
let mut vecbuf = Vec::new(); let mut vecbuf = Vec::new();
if arguments.len() == 0 { if arguments.len() == 0 {
@ -23,6 +24,8 @@ impl Command for Cat {
let _ = vecbuf.append(&mut tmpbuf); let _ = vecbuf.append(&mut tmpbuf);
} }
let _ = io::stdout().write_all(&vecbuf); io::stdout().write_all(&vecbuf)?;
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::commands::Command; use boxutils::commands::Command;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@ -8,7 +9,7 @@ use std::time::Instant;
pub struct Dd; pub struct Dd;
impl Command for Dd { impl Command for Dd {
fn execute(&self) { fn execute(&self) -> Result<()> {
// dd has its seperate argument parsing // dd has its seperate argument parsing
let mut arguments = HashMap::new(); let mut arguments = HashMap::new();
let mut blocksize = 512; let mut blocksize = 512;
@ -24,19 +25,18 @@ impl Command for Dd {
let (k, v) = bs.split_at(bs.len() - 1); let (k, v) = bs.split_at(bs.len() - 1);
if v.parse::<u64>().is_ok() { if v.parse::<u64>().is_ok() {
// assume the bs is specified in bytes, // assume the bs is specified in bytes,
// because the last char is a number // because it can be parsed as u64
blocksize = bs.parse::<u64>().unwrap() blocksize = bs.parse::<u64>()?
} else { } else {
match v { match v {
"K" | "k" => blocksize = k.parse::<u64>().unwrap() * 1024, "K" | "k" => blocksize = k.parse::<u64>()? * 1024,
"M" => blocksize = k.parse::<u64>().unwrap() * 1024 * 1024, "M" => blocksize = k.parse::<u64>()? * 1024 * 1024,
"G" => blocksize = k.parse::<u64>().unwrap() * 1024 * 1024 * 1024, "G" => blocksize = k.parse::<u64>()? * 1024 * 1024 * 1024,
"kB" => blocksize = k.parse::<u64>().unwrap() * 1000, "kB" => blocksize = k.parse::<u64>()? * 1000,
"MB" => blocksize = k.parse::<u64>().unwrap() * 1000 * 1000, "MB" => blocksize = k.parse::<u64>()? * 1000 * 1000,
"GB" => blocksize = k.parse::<u64>().unwrap() * 1000 * 1000 * 1000, "GB" => blocksize = k.parse::<u64>()? * 1000 * 1000 * 1000,
_ => { _ => {
eprintln!("Invalid blocksize specified."); bail!("Invalid blocksize specified.");
return;
} }
} }
} }
@ -46,17 +46,17 @@ impl Command for Dd {
let start = Instant::now(); let start = Instant::now();
if let Some(input) = arguments.get("if") { if let Some(input) = arguments.get("if") {
let mut f = BufReader::new(File::open(input).unwrap()); let mut f = BufReader::new(File::open(input)?);
let _ = f.read_to_end(&mut vecbuf).unwrap(); f.read_to_end(&mut vecbuf)?;
} else { } else {
let _ = io::stdin().read_to_end(&mut vecbuf).unwrap(); io::stdin().read_to_end(&mut vecbuf)?;
} }
if let Some(output) = arguments.get("of") { if let Some(output) = arguments.get("of") {
let buffer = File::create(output); let buffer = File::create(output);
let _ = buffer.unwrap().write(&vecbuf); buffer.unwrap().write(&vecbuf)?;
} else { } else {
let _ = io::stdout().write_all(&vecbuf); io::stdout().write_all(&vecbuf)?;
} }
let duration = start.elapsed().as_secs_f64(); let duration = start.elapsed().as_secs_f64();
@ -77,6 +77,8 @@ impl Command for Dd {
vecbuf.len(), vecbuf.len(),
duration, duration,
kb_per_sec kb_per_sec
) );
Ok(())
} }
} }

View file

@ -1,18 +1,16 @@
use std::path::Path; use anyhow::{Result, bail};
use boxutils::{args::ArgParser, commands::Command}; use boxutils::{args::ArgParser, commands::Command};
use std::path::Path;
pub struct Dirname; pub struct Dirname;
impl Command for Dirname { impl Command for Dirname {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("--help") .add_flag("--help")
.parse_args("dirname"); .parse_args("dirname");
if args.get_normal_args().len() != 1 || args.get_flag("--help") { if args.get_normal_args().len() != 1 || args.get_flag("--help") {
println!("Usage: dirname FILENAME"); bail!("Usage: dirname FILENAME");
return;
} }
// note(teesh): we have already checked for argnums, so we're fine :D // note(teesh): we have already checked for argnums, so we're fine :D
@ -30,7 +28,9 @@ impl Command for Dirname {
println!("{}", to_print); println!("{}", to_print);
} else { } else {
println!("dirname: could not get parent") bail!("dirname: could not get parent")
} }
Ok(())
} }
} }

View file

@ -1,8 +1,8 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::fs::File; use std::fs::File;
use std::io::{self, BufReader, Read, Write}; use std::io::{self, BufReader, Read, Write};
use std::process::exit;
pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> { pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
let mut vecbuf = Vec::new(); let mut vecbuf = Vec::new();
@ -19,10 +19,7 @@ pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
} }
if d2u { if d2u {
vecbuf.retain( vecbuf.retain(|x| *x != b'\r');
|x|
*x != b'\r'
);
} else { } else {
let mut tmpbuf = Vec::new(); let mut tmpbuf = Vec::new();
vecbuf.iter().enumerate().for_each(|(i, &b)| { vecbuf.iter().enumerate().for_each(|(i, &b)| {
@ -40,7 +37,7 @@ pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
pub struct Dos2Unix; pub struct Dos2Unix;
impl Command for Dos2Unix { impl Command for Dos2Unix {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-u") .add_flag("-u")
.add_flag("-d") .add_flag("-d")
@ -58,19 +55,23 @@ impl Command for Dos2Unix {
} }
if args.get_flag("--help") { if args.get_flag("--help") {
println!("Usage: dos2unix [-d] [-u] [FILE]"); let help_text = "\
print!("\n"); Usage: dos2unix [-d] [-u] [FILE]
println!("-d: unix2dos");
println!("-u: dos2unix (default)"); -d: unix2dos
exit(0); -u: dos2unix (default)\
";
bail!("{}", help_text)
} }
let result = convert(&args, dos2unix); let result = convert(&args, dos2unix);
if args.get_normal_args().len() < 1 { if args.get_normal_args().len() < 1 {
let _ = io::stdout().write_all(&result); io::stdout().write_all(&result)?;
} else { } else {
let _ = std::fs::write(args.get_normal_args()[0].clone(), &result); std::fs::write(args.get_normal_args()[0].clone(), &result)?;
} }
Ok(())
} }
} }

View file

@ -1,12 +1,15 @@
use anyhow::Result;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Echo; pub struct Echo;
impl Command for Echo { impl Command for Echo {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args: Vec<String> = std::env::args().collect::<Vec<_>>(); let args: Vec<String> = std::env::args().collect::<Vec<_>>();
let arguments: Vec<String> = boxutils::commands::get_args(String::from("echo"), args); let arguments: Vec<String> = boxutils::commands::get_args(String::from("echo"), args);
let message = &arguments.join(" "); let message = &arguments.join(" ");
println!("{}", message); println!("{}", message);
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
@ -35,7 +36,7 @@ impl Env {
} }
impl Command for Env { impl Command for Env {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-i") .add_flag("-i")
.add_flag("-0") .add_flag("-0")
@ -59,20 +60,20 @@ impl Command for Env {
if to_run.is_empty() { if to_run.is_empty() {
Env::print_all_vars(); Env::print_all_vars();
return; return Ok(());
} }
if let Some((command, args)) = to_run.split_first() { if let Some((command, args)) = to_run.split_first() {
std::process::Command::new(command) std::process::Command::new(command)
.args(args) .args(args)
.spawn() .spawn()?
.expect("env: failed to start command") .wait()?;
.wait()
.expect("env: could not wait for command");
} }
if null_terminate { if null_terminate {
print!("\0"); print!("\0");
} }
Ok(())
} }
} }

View file

@ -1,10 +1,11 @@
use anyhow::Result;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::process::exit; use std::process::exit;
pub struct False; pub struct False;
impl Command for False { impl Command for False {
fn execute(&self) { fn execute(&self) -> Result<()> {
exit(1); exit(1);
} }
} }

View file

@ -1,9 +1,12 @@
use anyhow::Result;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Hello; pub struct Hello;
impl Command for Hello { impl Command for Hello {
fn execute(&self) { fn execute(&self) -> Result<()> {
println!("Hello, world!"); println!("Hello, world!");
Ok(())
} }
} }

View file

@ -1,10 +1,11 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Hostname; pub struct Hostname;
impl Command for Hostname { impl Command for Hostname {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("--help") .add_flag("--help")
.parse_args("hostname"); .parse_args("hostname");
@ -19,5 +20,7 @@ impl Command for Hostname {
}; };
println!("{}", hostname.to_string_lossy()); println!("{}", hostname.to_string_lossy());
Ok(())
} }
} }

View file

@ -1,11 +1,11 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::fs; use std::fs;
pub struct Ln; pub struct Ln;
impl Command for Ln { impl Command for Ln {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("--help") .add_flag("--help")
.add_flag("-s") .add_flag("-s")
@ -20,7 +20,7 @@ impl Command for Ln {
let help = args.get_normal_args().len() != 2 || args.get_flag("--help"); let help = args.get_normal_args().len() != 2 || args.get_flag("--help");
if help { if help {
println!("Usage: ln [-sfnbtv] [-S SUF] TARGET LINK"); println!("Usage: ln [-sfnbtv] [-S SUF] TARGET LINK");
return; return Ok(());
} }
let to_be_linked = args.get_normal_args()[0].clone(); let to_be_linked = args.get_normal_args()[0].clone();
@ -31,11 +31,11 @@ impl Command for Ln {
} }
if args.get_flag("-f") { if args.get_flag("-f") {
if fs::exists(&destination).unwrap() { if fs::exists(&destination)? {
if fs::metadata(&destination).unwrap().is_dir() { if fs::metadata(&destination)?.is_dir() {
fs::remove_dir_all(&destination).unwrap(); fs::remove_dir_all(&destination)?;
} else { } else {
fs::remove_file(&destination).unwrap(); fs::remove_file(&destination)?;
} }
} }
} }
@ -47,19 +47,22 @@ impl Command for Ln {
if args.get_flag("-b") { if args.get_flag("-b") {
let suffix = args.get_option("-S").unwrap_or("~"); let suffix = args.get_option("-S").unwrap_or("~");
let new_filename = format!("{}{}", destination, suffix); let new_filename = format!("{}{}", destination, suffix);
let _ = fs::rename(&destination, &new_filename); fs::rename(&destination, &new_filename)?;
} }
if args.get_flag("-s") { if args.get_flag("-s") {
let symlink_result = boxutils::cross::fs::symlink(to_be_linked, destination, dereference); let symlink_result =
boxutils::cross::fs::symlink(to_be_linked, destination, dereference);
if let Err(e) = symlink_result { if let Err(e) = symlink_result {
eprintln!("ln: failed to create symlink: {}", e); bail!("ln: failed to create symlink: {}", e);
} }
} else { } else {
if let Err(e) = boxutils::cross::fs::hard_link(to_be_linked, destination, dereference) { if let Err(e) = boxutils::cross::fs::hard_link(to_be_linked, destination, dereference) {
eprintln!("ln: failed to create hard link: {}", e); bail!("ln: failed to create hard link: {}", e);
} }
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::fs; use std::fs;
@ -5,7 +6,7 @@ use std::fs;
pub struct Mkdir; pub struct Mkdir;
impl Command for Mkdir { impl Command for Mkdir {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-p") .add_flag("-p")
.add_flag("--parents") .add_flag("--parents")
@ -14,11 +15,11 @@ impl Command for Mkdir {
if args.get_flag("--help") { if args.get_flag("--help") {
println!("Usage: mkdir [DIR1] [DIR2] etc. pp. [-p, --parents]"); println!("Usage: mkdir [DIR1] [DIR2] etc. pp. [-p, --parents]");
return; return Ok(());
} }
if args.get_normal_args().len() == 0 { if args.get_normal_args().len() == 0 {
panic!("Usage: mkdir [DIR1] [DIR1] etc. pp. [-p, --parents]"); bail!("Usage: mkdir [DIR1] [DIR1] etc. pp. [-p, --parents]");
} }
let parented = args.get_flag("-p") || args.get_flag("--parents"); let parented = args.get_flag("-p") || args.get_flag("--parents");
@ -27,10 +28,12 @@ impl Command for Mkdir {
for dir in to_create { for dir in to_create {
if parented { if parented {
let _ = fs::create_dir_all(dir); fs::create_dir_all(dir)?;
} else { } else {
let _ = fs::create_dir(dir); fs::create_dir(dir)?;
} }
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use num_cpus::get; use num_cpus::get;
@ -5,7 +6,7 @@ use num_cpus::get;
pub struct Nproc; pub struct Nproc;
impl Command for Nproc { impl Command for Nproc {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("--help") .add_flag("--help")
.add_flag("--ignore") .add_flag("--ignore")
@ -23,7 +24,7 @@ Prints the number of available CPUs to stdout.
--ignore=N, --ignore N Ignore N CPUs --ignore=N, --ignore N Ignore N CPUs
" "
); );
return; return Ok(());
} }
if args.get_flag("--all") { if args.get_flag("--all") {
@ -31,13 +32,13 @@ Prints the number of available CPUs to stdout.
} }
if args.get_flag("--ignore") { if args.get_flag("--ignore") {
ignore = args.get_option("--ignore").unwrap().parse().unwrap(); ignore = args.get_option("--ignore").unwrap().parse()?;
} }
for argument in args.get_normal_args() { for argument in args.get_normal_args() {
if let Some((k, v)) = argument.split_once('=') { if let Some((k, v)) = argument.split_once('=') {
if k == "--ignore" { if k == "--ignore" {
ignore = v.parse().unwrap(); ignore = v.parse()?;
} }
} }
} }
@ -47,5 +48,7 @@ Prints the number of available CPUs to stdout.
} else { } else {
println!("{}", get() as u64 - ignore) println!("{}", get() as u64 - ignore)
} }
Ok(())
} }
} }

View file

@ -1,18 +1,21 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Pwd; pub struct Pwd;
impl Command for Pwd { impl Command for Pwd {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder().add_flag("--help").parse_args("yes"); let args = ArgParser::builder().add_flag("--help").parse_args("yes");
if args.get_flag("--help") { if args.get_flag("--help") {
println!("Usage: pwd"); println!("Usage: pwd");
return; return Ok(());
} }
let pwd = std::env::current_dir().unwrap(); let pwd = std::env::current_dir()?;
println!("{}", pwd.display()); println!("{}", pwd.display());
Ok(())
} }
} }

View file

@ -1,9 +1,10 @@
use anyhow::{Result, bail};
use boxutils::{args::ArgParser, commands::Command}; use boxutils::{args::ArgParser, commands::Command};
pub struct Seq; pub struct Seq;
impl Command for Seq { impl Command for Seq {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_option("-s") .add_option("-s")
.add_flag("-w") .add_flag("-w")
@ -31,7 +32,7 @@ impl Command for Seq {
incnum = inc.parse().expect("seq: invalid inc number"); incnum = inc.parse().expect("seq: invalid inc number");
lastnum = last.parse().expect("seq: invalid last number"); lastnum = last.parse().expect("seq: invalid last number");
} }
_ => panic!("seq: malformed arguments"), _ => bail!("seq: malformed arguments"),
} }
let mut accumulator = firstnum; let mut accumulator = firstnum;
@ -62,5 +63,7 @@ impl Command for Seq {
print!("{}", separator); print!("{}", separator);
} }
} }
Ok(())
} }
} }

View file

@ -1,15 +1,16 @@
use anyhow::{Result, bail};
use boxutils::{args::ArgParser, commands::Command}; use boxutils::{args::ArgParser, commands::Command};
pub struct Sleep; pub struct Sleep;
impl Command for Sleep { impl Command for Sleep {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder().parse_args("sleep"); let args = ArgParser::builder().parse_args("sleep");
let mut sleep_for = 0; let mut sleep_for = 0;
for arg in args.get_normal_args() { for arg in args.get_normal_args() {
if arg.chars().last().unwrap().is_numeric() { if arg.chars().last().unwrap().is_numeric() {
sleep_for += arg.parse::<i32>().unwrap(); sleep_for += arg.parse::<i32>()?;
} else { } else {
let multiplier = match arg.chars().last().unwrap() { let multiplier = match arg.chars().last().unwrap() {
's' => 1, 's' => 1,
@ -17,20 +18,21 @@ impl Command for Sleep {
'h' => 3600, 'h' => 3600,
'd' => 86400, 'd' => 86400,
_ => { _ => {
println!("Invalid time interval '{}'", arg); bail!("Invalid time interval '{}'", arg);
return;
} }
}; };
sleep_for += arg[..arg.len() - 1].parse::<i32>().unwrap() * multiplier; sleep_for += arg[..arg.len() - 1].parse::<i32>()? * multiplier;
} }
} }
if sleep_for == 0 { if sleep_for == 0 {
println!("Usage: sleep [N]..."); println!("Usage: sleep [N]...");
return; return Ok(());
} }
std::thread::sleep(std::time::Duration::from_secs(sleep_for as u64)); std::thread::sleep(std::time::Duration::from_secs(sleep_for as u64));
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::fs::OpenOptions; use std::fs::OpenOptions;
@ -10,7 +11,7 @@ use std::io::Write;
pub struct Tee; pub struct Tee;
impl Command for Tee { impl Command for Tee {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("--help") .add_flag("--help")
.add_flag("-a") .add_flag("-a")
@ -34,17 +35,19 @@ impl Command for Tee {
if let Ok(this_file) = this_file { if let Ok(this_file) = this_file {
writes.push(Box::new(this_file)); writes.push(Box::new(this_file));
} else { } else {
eprintln!("tee: unable to open file: {}", file); bail!("tee: unable to open file: {}", file);
} }
} }
let mut buffer = String::new(); let mut buffer = String::new();
while boxutils::input::repl(&mut buffer) { while boxutils::input::repl(&mut buffer) {
for output in &mut writes { for output in &mut writes {
let _ = output.write_all(buffer.as_bytes()); output.write_all(buffer.as_bytes())?;
let _ = output.flush(); output.flush()?;
} }
buffer.clear(); buffer.clear();
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
@ -194,7 +195,7 @@ impl Test {
} }
impl Command for Test { impl Command for Test {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flags(STRING_TESTS.into()) .add_flags(STRING_TESTS.into())
.add_flags(NUMBER_TESTS.into()) .add_flags(NUMBER_TESTS.into())
@ -202,7 +203,7 @@ impl Command for Test {
.parse_args(if self.bracket { "[" } else { "test" }); .parse_args(if self.bracket { "[" } else { "test" });
if self.bracket && !args.get_flag("]") { if self.bracket && !args.get_flag("]") {
panic!("[: missing ]"); bail!("[: missing ]");
} }
if STRING_TESTS.iter().any(|&x| args.get_flag(x)) { if STRING_TESTS.iter().any(|&x| args.get_flag(x)) {
@ -214,5 +215,6 @@ impl Command for Test {
} }
exit_false(); exit_false();
unreachable!();
} }
} }

View file

@ -1,10 +1,11 @@
use anyhow::Result;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::process::exit; use std::process::exit;
pub struct True; pub struct True;
impl Command for True { impl Command for True {
fn execute(&self) { fn execute(&self) -> Result<()> {
exit(0); exit(0);
} }
} }

View file

@ -1,4 +1,5 @@
use crate::commands::dos2unix::convert; use crate::commands::dos2unix::convert;
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use std::io::{self, Write}; use std::io::{self, Write};
@ -7,7 +8,7 @@ use std::process::exit;
pub struct Unix2Dos; pub struct Unix2Dos;
impl Command for Unix2Dos { impl Command for Unix2Dos {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder() let args = ArgParser::builder()
.add_flag("-u") .add_flag("-u")
.add_flag("-d") .add_flag("-d")
@ -35,10 +36,11 @@ impl Command for Unix2Dos {
let result = convert(&args, dos2unix); let result = convert(&args, dos2unix);
if args.get_normal_args().len() < 1 { if args.get_normal_args().len() < 1 {
let _ = io::stdout().write_all(&result); io::stdout().write_all(&result)?;
} else { } else {
let _ = std::fs::write(args.get_normal_args()[0].clone(), &result); std::fs::write(args.get_normal_args()[0].clone(), &result)?;
} }
Ok(())
} }
} }

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
use boxutils::cross::user; use boxutils::cross::user;
@ -5,16 +6,18 @@ use boxutils::cross::user;
pub struct WhoAmI; pub struct WhoAmI;
impl Command for WhoAmI { impl Command for WhoAmI {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder().add_flag("--help").parse_args("whoami"); let args = ArgParser::builder().add_flag("--help").parse_args("whoami");
if args.get_flag("--help") { if args.get_flag("--help") {
println!("Usage: whoami"); println!("Usage: whoami");
return; return Ok(());
} }
let username = user::get_username().unwrap_or_else(|| "unknown".to_string()); let username = user::get_username().unwrap_or("unknown".to_string());
println!("{}", username); println!("{}", username);
Ok(())
} }
} }

View file

@ -1,15 +1,16 @@
use anyhow::Result;
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Yes; pub struct Yes;
impl Command for Yes { impl Command for Yes {
fn execute(&self) { fn execute(&self) -> Result<()> {
let args = ArgParser::builder().add_flag("--help").parse_args("yes"); let args = ArgParser::builder().add_flag("--help").parse_args("yes");
if args.get_flag("--help") { if args.get_flag("--help") {
println!("Usage: yes [STRING]"); println!("Usage: yes [STRING]");
return; return Ok(());
} }
let string = if args.get_normal_args().is_empty() { let string = if args.get_normal_args().is_empty() {

View file

@ -5,3 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
boxutils.workspace = true boxutils.workspace = true
anyhow.workspace = true

View file

@ -1,14 +1,17 @@
use boxutils::commands::Command; use boxutils::commands::Command;
use std::env; use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
use std::process; use std::process;
use anyhow::{Error, Result};
use crate::built_in::{Action, run_if_exists}; use crate::built_in::{Action, run_if_exists};
pub struct Ash; pub struct Ash;
impl Command for Ash { impl Command for Ash {
fn execute(&self) { fn execute(&self) -> Result<(), Error> {
let mut path = env::current_dir().unwrap().display().to_string(); let mut path = env::current_dir().unwrap().display().to_string();
loop { loop {
@ -20,13 +23,13 @@ impl Command for Ash {
print!("{} {} ", path.clone(), userchar); print!("{} {} ", path.clone(), userchar);
let _ = io::stdout().flush(); let _ = io::stdout().flush();
let mut input = String::new(); let mut input = String::new();
io::stdin().read_line(&mut input).unwrap(); io::stdin().read_line(&mut input)?;
let mut full_cmd = input.trim().split_whitespace(); let mut full_cmd = input.trim().split_whitespace();
let command = full_cmd.next().unwrap_or(""); let command = full_cmd.next().unwrap_or("");
let arguments: Vec<&str> = full_cmd.collect(); let arguments: Vec<&str> = full_cmd.collect();
if let Some(action) = run_if_exists(command.to_string(), arguments.clone()) { if let Some(action) = run_if_exists(command.to_string(), arguments.clone()) {
match action { match action? {
Action::Exit => { Action::Exit => {
break; break;
} }
@ -39,17 +42,13 @@ impl Command for Ash {
Action::Nothing => {} Action::Nothing => {}
} }
} else { } else {
let out = process::Command::new(command) process::Command::new(command)
.args(arguments.clone()) .args(arguments.clone())
.spawn(); .spawn()?
.wait()?;
}
}
match out { Ok(())
Ok(mut out) => {
let _ = out.wait();
}
Err(err) => println!("{:?}", err),
}
}
}
} }
} }

View file

@ -1,9 +1,10 @@
use crate::built_in::Action; use crate::built_in::Action;
use anyhow::{Result, bail};
pub fn cd(arguments: Vec<&str>) -> Action { pub fn cd(arguments: Vec<&str>) -> Result<Action> {
if arguments.len() < 1 { if arguments.len() < 1 {
panic!("cd expects **one** argument"); bail!("cd expects **one** argument");
} }
Action::ChangeDirectory(arguments[0].to_owned().clone()) Ok(Action::ChangeDirectory(arguments[0].to_owned().clone()))
} }

View file

@ -1,17 +1,16 @@
use crate::built_in::Action; use crate::built_in::Action;
use anyhow::{Result, bail};
use std::process::Command; use std::process::Command;
pub fn eval(arguments: Vec<&str>) -> Action { pub fn eval(arguments: Vec<&str>) -> Result<Action> {
if arguments.len() < 1 { if arguments.len() < 1 {
panic!("eval expects **one or more** arguments"); bail!("eval expects **one or more** arguments");
} }
let output = Command::new(arguments[0]).args(&arguments[1..]).spawn(); Command::new(arguments[0])
match output { .args(&arguments[1..])
Ok(mut output) => { .spawn()?
output.wait().expect("failed to wait for process exit"); .wait()?;
} Ok(Action::Nothing)
Err(err) => println!("{:?}", err)
}
Action::Nothing
} }

View file

@ -1,5 +1,6 @@
use crate::built_in::Action; use crate::built_in::Action;
use anyhow::Result;
pub fn exit(_: Vec<&str>) -> Action { pub fn exit(_: Vec<&str>) -> Result<Action> {
Action::Exit Ok(Action::Exit)
} }

View file

@ -1,21 +1,23 @@
mod cd; mod cd;
mod exit;
mod eval; mod eval;
mod exit;
use anyhow::Result;
#[derive(Debug)] #[derive(Debug)]
pub enum Action { pub enum Action {
Exit, Exit,
ChangeDirectory(String), ChangeDirectory(String),
Nothing Nothing,
} }
fn get_function(command: String) -> Option<fn(Vec<&str>) -> Action> { fn get_function(command: String) -> Option<fn(Vec<&str>) -> Result<Action>> {
let registry = [ let registry = [
("exit", exit::exit as fn(Vec<&str>) -> Action), ("exit", exit::exit as fn(Vec<&str>) -> Result<Action>),
("cd", cd::cd as fn(Vec<&str>) -> Action), ("cd", cd::cd as fn(Vec<&str>) -> Result<Action>),
("eval", eval::eval as fn(Vec<&str>) -> Action) ("eval", eval::eval as fn(Vec<&str>) -> Result<Action>),
]; ];
let mut function: Option<fn(Vec<&str>) -> Action> = None; let mut function: Option<fn(Vec<&str>) -> Result<Action>> = None;
for entry in registry { for entry in registry {
if entry.0 == &command { if entry.0 == &command {
@ -26,7 +28,7 @@ fn get_function(command: String) -> Option<fn(Vec<&str>) -> Action> {
function function
} }
pub fn run_if_exists(command: String, arguments: Vec<&str>) -> Option<Action> { pub fn run_if_exists(command: String, arguments: Vec<&str>) -> Option<Result<Action>> {
let function = get_function(command); let function = get_function(command);
if let Some(function) = function { if let Some(function) = function {
let action = function(arguments); let action = function(arguments);

View file

@ -1,11 +1,12 @@
use super::registry::get_registry; use super::registry::get_registry;
use anyhow::{Result, bail};
use boxutils::args::ArgParser; use boxutils::args::ArgParser;
use boxutils::commands::Command; use boxutils::commands::Command;
pub struct Boxcmd; pub struct Boxcmd;
impl Command for Boxcmd { impl Command for Boxcmd {
fn execute(&self) { fn execute(&self) -> Result<()> {
let parser = ArgParser::builder().parse_args("box"); let parser = ArgParser::builder().parse_args("box");
let registry = get_registry(); let registry = get_registry();
@ -15,11 +16,10 @@ impl Command for Boxcmd {
continue; continue;
} }
registry.execute(&command); registry.execute(&command)?;
return;
} }
println!( bail!(
"No valid command provided. Included commands:\n{}", "No valid command provided. Included commands:\n{}",
registry.list().join(", ") registry.list().join(", ")
); );

View file

@ -1,9 +1,12 @@
mod boxcmd; mod boxcmd;
mod registry; mod registry;
use anyhow::{Error, Result};
use boxutils::commands::being_called_as; use boxutils::commands::being_called_as;
fn main() { fn main() -> Result<(), Error> {
let utility_name = being_called_as(); let utility_name = being_called_as();
registry::get_registry().execute(&utility_name); registry::get_registry().execute(&utility_name)?;
Ok(())
} }

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow.workspace = true

View file

@ -2,6 +2,8 @@ use std::env;
use std::mem; use std::mem;
use std::path::Path; use std::path::Path;
use anyhow::{Error, Result};
pub fn being_called_as() -> String { pub fn being_called_as() -> String {
let args = env::args().collect::<Vec<String>>(); let args = env::args().collect::<Vec<String>>();
let exe_path = args[0].clone(); let exe_path = args[0].clone();
@ -34,5 +36,5 @@ pub fn get_args(commandname: String, args: Vec<String>) -> Vec<String> {
} }
pub trait Command { pub trait Command {
fn execute(&self); fn execute(&self) -> Result<(), Error>;
} }

View file

@ -1,6 +1,8 @@
use super::commands::Command; use super::commands::Command;
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::{Error, Result, bail};
pub struct CommandRegistry { pub struct CommandRegistry {
commands: HashMap<String, Box<dyn Command>>, commands: HashMap<String, Box<dyn Command>>,
} }
@ -28,23 +30,31 @@ impl CommandRegistry {
self.commands.get(name) self.commands.get(name)
} }
pub fn execute(&self, name: &str) { pub fn execute(&self, name: &str) -> Result<(), Error> {
if let Some(command) = self.get(name) { if let Some(command) = self.get(name) {
command.execute(); command.execute()?;
} else { } else {
println!("Command not found: {}", name); bail!("Command not found: {}", name);
} }
Ok(())
} }
} }
mod tests { mod tests {
#[allow(unused_imports)] // why the heck is rust saying it's unused??
use anyhow::{Error, Result};
#[test] #[test]
fn test_register() { fn test_register() {
use super::Command; use super::Command;
use super::CommandRegistry; use super::CommandRegistry;
struct TestCommand; struct TestCommand;
impl Command for TestCommand { impl Command for TestCommand {
fn execute(&self) {} fn execute(&self) -> Result<(), Error> {
Ok(())
}
} }
let mut registry = CommandRegistry::new(); let mut registry = CommandRegistry::new();
@ -53,18 +63,23 @@ mod tests {
} }
#[test] #[test]
fn test_execute() { fn test_execute() -> Result<(), Error> {
use super::Command; use super::Command;
use super::CommandRegistry; use super::CommandRegistry;
struct TestCommand; struct TestCommand;
impl Command for TestCommand { impl Command for TestCommand {
fn execute(&self) { fn execute(&self) -> Result<(), Error> {
println!("TestCommand executed"); println!("TestCommand executed");
Ok(())
} }
} }
let mut registry = CommandRegistry::new(); let mut registry = CommandRegistry::new();
registry.register("test", Box::new(TestCommand)); registry.register("test", Box::new(TestCommand));
registry.execute("test"); registry.execute("test")?;
Ok(())
} }
} }