Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
41fb50c4cf feat: make a simple nix devshell, integrated with direnv 2026-02-06 15:40:43 +02:00
User0
0cd7b3af12
feat: do error handling with anyhow, slight refactor (#6)
Co-authored-by: user0-07161 <user0thenyancat@proton.me>
2025-09-14 20:42:10 +03:00
43 changed files with 348 additions and 151 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

1
.gitignore vendored
View file

@ -1 +1,2 @@
target
.direnv

12
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use boxutils::commands::Command;
use boxutils::commands::get_args;
use std::env;
@ -7,9 +8,9 @@ use std::io::{self, BufReader, Read, Write};
pub struct Cat;
impl Command for Cat {
fn execute(&self) {
fn execute(&self) -> Result<()> {
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();
if arguments.len() == 0 {
@ -23,6 +24,8 @@ impl Command for Cat {
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 std::collections::HashMap;
use std::env;
@ -8,7 +9,7 @@ use std::time::Instant;
pub struct Dd;
impl Command for Dd {
fn execute(&self) {
fn execute(&self) -> Result<()> {
// dd has its seperate argument parsing
let mut arguments = HashMap::new();
let mut blocksize = 512;
@ -24,19 +25,18 @@ impl Command for Dd {
let (k, v) = bs.split_at(bs.len() - 1);
if v.parse::<u64>().is_ok() {
// assume the bs is specified in bytes,
// because the last char is a number
blocksize = bs.parse::<u64>().unwrap()
// because it can be parsed as u64
blocksize = bs.parse::<u64>()?
} else {
match v {
"K" | "k" => blocksize = k.parse::<u64>().unwrap() * 1024,
"M" => blocksize = k.parse::<u64>().unwrap() * 1024 * 1024,
"G" => blocksize = k.parse::<u64>().unwrap() * 1024 * 1024 * 1024,
"kB" => blocksize = k.parse::<u64>().unwrap() * 1000,
"MB" => blocksize = k.parse::<u64>().unwrap() * 1000 * 1000,
"GB" => blocksize = k.parse::<u64>().unwrap() * 1000 * 1000 * 1000,
"K" | "k" => blocksize = k.parse::<u64>()? * 1024,
"M" => blocksize = k.parse::<u64>()? * 1024 * 1024,
"G" => blocksize = k.parse::<u64>()? * 1024 * 1024 * 1024,
"kB" => blocksize = k.parse::<u64>()? * 1000,
"MB" => blocksize = k.parse::<u64>()? * 1000 * 1000,
"GB" => blocksize = k.parse::<u64>()? * 1000 * 1000 * 1000,
_ => {
eprintln!("Invalid blocksize specified.");
return;
bail!("Invalid blocksize specified.");
}
}
}
@ -46,17 +46,17 @@ impl Command for Dd {
let start = Instant::now();
if let Some(input) = arguments.get("if") {
let mut f = BufReader::new(File::open(input).unwrap());
let _ = f.read_to_end(&mut vecbuf).unwrap();
let mut f = BufReader::new(File::open(input)?);
f.read_to_end(&mut vecbuf)?;
} else {
let _ = io::stdin().read_to_end(&mut vecbuf).unwrap();
io::stdin().read_to_end(&mut vecbuf)?;
}
if let Some(output) = arguments.get("of") {
let buffer = File::create(output);
let _ = buffer.unwrap().write(&vecbuf);
buffer.unwrap().write(&vecbuf)?;
} else {
let _ = io::stdout().write_all(&vecbuf);
io::stdout().write_all(&vecbuf)?;
}
let duration = start.elapsed().as_secs_f64();
@ -77,6 +77,8 @@ impl Command for Dd {
vecbuf.len(),
duration,
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 std::path::Path;
pub struct Dirname;
impl Command for Dirname {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("--help")
.parse_args("dirname");
if args.get_normal_args().len() != 1 || args.get_flag("--help") {
println!("Usage: dirname FILENAME");
return;
bail!("Usage: dirname FILENAME");
}
// note(teesh): we have already checked for argnums, so we're fine :D
@ -30,7 +28,9 @@ impl Command for Dirname {
println!("{}", to_print);
} 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::commands::Command;
use std::fs::File;
use std::io::{self, BufReader, Read, Write};
use std::process::exit;
pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
let mut vecbuf = Vec::new();
@ -19,10 +19,7 @@ pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
}
if d2u {
vecbuf.retain(
|x|
*x != b'\r'
);
vecbuf.retain(|x| *x != b'\r');
} else {
let mut tmpbuf = Vec::new();
vecbuf.iter().enumerate().for_each(|(i, &b)| {
@ -40,7 +37,7 @@ pub fn convert(arguments: &ArgParser, d2u: bool) -> Vec<u8> {
pub struct Dos2Unix;
impl Command for Dos2Unix {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("-u")
.add_flag("-d")
@ -58,19 +55,23 @@ impl Command for Dos2Unix {
}
if args.get_flag("--help") {
println!("Usage: dos2unix [-d] [-u] [FILE]");
print!("\n");
println!("-d: unix2dos");
println!("-u: dos2unix (default)");
exit(0);
let help_text = "\
Usage: dos2unix [-d] [-u] [FILE]
-d: unix2dos
-u: dos2unix (default)\
";
bail!("{}", help_text)
}
let result = convert(&args, dos2unix);
if args.get_normal_args().len() < 1 {
let _ = io::stdout().write_all(&result);
io::stdout().write_all(&result)?;
} 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;
pub struct Echo;
impl Command for Echo {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args: Vec<String> = std::env::args().collect::<Vec<_>>();
let arguments: Vec<String> = boxutils::commands::get_args(String::from("echo"), args);
let message = &arguments.join(" ");
println!("{}", message);
Ok(())
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
use anyhow::{Result, bail};
use boxutils::args::ArgParser;
use boxutils::commands::Command;
use std::fs;
pub struct Ln;
impl Command for Ln {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("--help")
.add_flag("-s")
@ -20,7 +20,7 @@ impl Command for Ln {
let help = args.get_normal_args().len() != 2 || args.get_flag("--help");
if help {
println!("Usage: ln [-sfnbtv] [-S SUF] TARGET LINK");
return;
return Ok(());
}
let to_be_linked = args.get_normal_args()[0].clone();
@ -31,11 +31,11 @@ impl Command for Ln {
}
if args.get_flag("-f") {
if fs::exists(&destination).unwrap() {
if fs::metadata(&destination).unwrap().is_dir() {
fs::remove_dir_all(&destination).unwrap();
if fs::exists(&destination)? {
if fs::metadata(&destination)?.is_dir() {
fs::remove_dir_all(&destination)?;
} else {
fs::remove_file(&destination).unwrap();
fs::remove_file(&destination)?;
}
}
}
@ -47,19 +47,22 @@ impl Command for Ln {
if args.get_flag("-b") {
let suffix = args.get_option("-S").unwrap_or("~");
let new_filename = format!("{}{}", destination, suffix);
let _ = fs::rename(&destination, &new_filename);
fs::rename(&destination, &new_filename)?;
}
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 {
eprintln!("ln: failed to create symlink: {}", e);
bail!("ln: failed to create symlink: {}", e);
}
} else {
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::commands::Command;
use std::fs;
@ -5,7 +6,7 @@ use std::fs;
pub struct Mkdir;
impl Command for Mkdir {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("-p")
.add_flag("--parents")
@ -14,11 +15,11 @@ impl Command for Mkdir {
if args.get_flag("--help") {
println!("Usage: mkdir [DIR1] [DIR2] etc. pp. [-p, --parents]");
return;
return Ok(());
}
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");
@ -27,10 +28,12 @@ impl Command for Mkdir {
for dir in to_create {
if parented {
let _ = fs::create_dir_all(dir);
fs::create_dir_all(dir)?;
} 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::commands::Command;
use num_cpus::get;
@ -5,7 +6,7 @@ use num_cpus::get;
pub struct Nproc;
impl Command for Nproc {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("--help")
.add_flag("--ignore")
@ -23,7 +24,7 @@ Prints the number of available CPUs to stdout.
--ignore=N, --ignore N Ignore N CPUs
"
);
return;
return Ok(());
}
if args.get_flag("--all") {
@ -31,13 +32,13 @@ Prints the number of available CPUs to stdout.
}
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() {
if let Some((k, v)) = argument.split_once('=') {
if k == "--ignore" {
ignore = v.parse().unwrap();
ignore = v.parse()?;
}
}
}
@ -47,5 +48,7 @@ Prints the number of available CPUs to stdout.
} else {
println!("{}", get() as u64 - ignore)
}
Ok(())
}
}

View file

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

View file

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

View file

@ -1,15 +1,16 @@
use anyhow::{Result, bail};
use boxutils::{args::ArgParser, commands::Command};
pub struct Sleep;
impl Command for Sleep {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder().parse_args("sleep");
let mut sleep_for = 0;
for arg in args.get_normal_args() {
if arg.chars().last().unwrap().is_numeric() {
sleep_for += arg.parse::<i32>().unwrap();
sleep_for += arg.parse::<i32>()?;
} else {
let multiplier = match arg.chars().last().unwrap() {
's' => 1,
@ -17,20 +18,21 @@ impl Command for Sleep {
'h' => 3600,
'd' => 86400,
_ => {
println!("Invalid time interval '{}'", arg);
return;
bail!("Invalid time interval '{}'", arg);
}
};
sleep_for += arg[..arg.len() - 1].parse::<i32>().unwrap() * multiplier;
sleep_for += arg[..arg.len() - 1].parse::<i32>()? * multiplier;
}
}
if sleep_for == 0 {
println!("Usage: sleep [N]...");
return;
return Ok(());
}
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::commands::Command;
use std::fs::OpenOptions;
@ -10,7 +11,7 @@ use std::io::Write;
pub struct Tee;
impl Command for Tee {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("--help")
.add_flag("-a")
@ -34,17 +35,19 @@ impl Command for Tee {
if let Ok(this_file) = this_file {
writes.push(Box::new(this_file));
} else {
eprintln!("tee: unable to open file: {}", file);
bail!("tee: unable to open file: {}", file);
}
}
let mut buffer = String::new();
while boxutils::input::repl(&mut buffer) {
for output in &mut writes {
let _ = output.write_all(buffer.as_bytes());
let _ = output.flush();
output.write_all(buffer.as_bytes())?;
output.flush()?;
}
buffer.clear();
}
Ok(())
}
}

View file

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

View file

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

View file

@ -1,4 +1,5 @@
use crate::commands::dos2unix::convert;
use anyhow::Result;
use boxutils::args::ArgParser;
use boxutils::commands::Command;
use std::io::{self, Write};
@ -7,7 +8,7 @@ use std::process::exit;
pub struct Unix2Dos;
impl Command for Unix2Dos {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder()
.add_flag("-u")
.add_flag("-d")
@ -35,10 +36,11 @@ impl Command for Unix2Dos {
let result = convert(&args, dos2unix);
if args.get_normal_args().len() < 1 {
let _ = io::stdout().write_all(&result);
io::stdout().write_all(&result)?;
} 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::commands::Command;
use boxutils::cross::user;
@ -5,16 +6,18 @@ use boxutils::cross::user;
pub struct WhoAmI;
impl Command for WhoAmI {
fn execute(&self) {
fn execute(&self) -> Result<()> {
let args = ArgParser::builder().add_flag("--help").parse_args("whoami");
if args.get_flag("--help") {
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);
Ok(())
}
}

View file

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

77
flake.lock generated Normal file
View file

@ -0,0 +1,77 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1769996383,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"import-tree": {
"locked": {
"lastModified": 1763762820,
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
"owner": "vic",
"repo": "import-tree",
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1770197578,
"narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"import-tree": "import-tree",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

13
flake.nix Normal file
View file

@ -0,0 +1,13 @@
{
description = "rebox: remaking busybox with rust";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
import-tree.url = "github:vic/import-tree";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake {inherit inputs;}
(inputs.import-tree ./packages/nix);
}

7
packages/nix/shell.nix Normal file
View file

@ -0,0 +1,7 @@
{...}: {
perSystem = {pkgs, ...}: {
devShells.default = pkgs.mkShell {
packages = [pkgs.cargo];
};
};
}

5
packages/nix/systems.nix Normal file
View file

@ -0,0 +1,5 @@
{...}: {
systems = [
"x86_64-linux"
];
}

View file

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

View file

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

View file

@ -1,9 +1,10 @@
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 {
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 anyhow::{Result, bail};
use std::process::Command;
pub fn eval(arguments: Vec<&str>) -> Action {
pub fn eval(arguments: Vec<&str>) -> Result<Action> {
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();
match output {
Ok(mut output) => {
output.wait().expect("failed to wait for process exit");
}
Err(err) => println!("{:?}", err)
}
Action::Nothing
}
Command::new(arguments[0])
.args(&arguments[1..])
.spawn()?
.wait()?;
Ok(Action::Nothing)
}

View file

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

View file

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

View file

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

View file

@ -1,9 +1,12 @@
mod boxcmd;
mod registry;
use anyhow::{Error, Result};
use boxutils::commands::being_called_as;
fn main() {
fn main() -> Result<(), Error> {
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"
[dependencies]
anyhow.workspace = true

View file

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

View file

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