From d98ab111eebfcf5e6327daaec17b5de578d64a41 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Mon, 19 Feb 2024 17:09:07 -0700 Subject: Path refresh refactor, comments, and error messages The 'eval' function was renamed to 'repl'. The code to refresh the $PATH was moved into it's own function: 'prefresh', and is now being called in three locations: - At the beginning of 'repl', before the main loop - Inside the main loop, possibly during a path search if the command is not initially found - Inside the main loop, if the call to Command::spawn() throws an error, and the error is ErrorKind::NotFound Doc comments were added for each function, as well as a few more comments throughout that detail the program's control flow. The error messages in the main repl loop were cleaned up to have a more consistent style, and to provide more/better detail. --- src/main.rs | 145 ++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index c27bad7..1dc0217 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,70 @@ use ctrlc; use std::fs; -use std::io; -use std::io::Write; +use std::io::{self, Write}; use std::path::Path; use std::process::Command; -fn eval(paths: &[&str], prompt: &str) { +/// Refresh the shell's $PATH +/// +/// This function caches all valid paths within within the directories +/// specified. +/// +/// # Arguments +/// * `paths` - A reference to a vector that holds a list to the shell $PATHs +/// +/// # Returns +/// * `bins: Vec` - A new cache of all valid file paths in $PATH +/// +/// # Examples +/// ``` +/// let paths = vec!["/bin"]; +/// let paths = paths.into_iter().map(Path::new).collect(); +/// let mut bins = prefresh(&paths); +/// ... +/// // A situation occurs where the $PATH needs to be refreshed +/// bins = prefresh(&paths) +/// ``` +fn prefresh(paths: &Vec<&Path>) -> Vec { let mut bins: Vec = Vec::new(); + for path in paths { + let files = fs::read_dir(path).expect( + format!( + "dwvsh: error: unable to read the contents of {}", + path.display().to_string() + ) + .as_str(), + ); + + for file in files { + bins.push(file.unwrap().path().display().to_string()); + } + } + + bins +} + +/// Starts the main shell loop +/// +/// # Arguments +/// * `paths` - A reference to a vector that holds a list to the shell $PATHs +/// * `prompt` - A string slice indicating the shell's prompt +/// +/// # Examples +/// ``` +/// fn main() { +/// let paths = vec!["/bin"]; +/// let paths = paths.into_iter().map(Path::new).collect(); +/// let prompt = "|> "; +/// ... +/// repl(&paths, prompt); +/// } +/// ``` +fn repl(paths: &Vec<&Path>, prompt: &str) { + // Initial path refresh on startup + let mut bins: Vec = prefresh(paths); + + // Main shell loop loop { // Output the prompt io::stdout().flush().unwrap(); @@ -18,7 +75,7 @@ fn eval(paths: &[&str], prompt: &str) { let mut input = String::new(); let bytes = io::stdin() .read_line(&mut input) - .expect("Unable to evaluate the input string"); + .expect("dwvsh: error: unable to evaluate the input string"); // Check if we've reached EOF (i.e. ) if bytes == 0 { @@ -34,11 +91,11 @@ fn eval(paths: &[&str], prompt: &str) { break; } - // Parse command and arguments + // Parse command let mut split = input.split(' '); - let cmd = match split.next() { + let mut cmd = match split.next() { Some(str) if str.trim().is_empty() => continue, - Some(str) => str.trim(), + Some(str) => String::from(str.trim()), None => continue, }; @@ -62,7 +119,7 @@ fn eval(paths: &[&str], prompt: &str) { match std::env::set_current_dir(path) { Ok(_) => continue, Err(_) => { - println!("cd: Unable to change into {}", path); + println!("cd: unable to change into {}", path); continue; } } @@ -70,7 +127,6 @@ fn eval(paths: &[&str], prompt: &str) { // Check if the file exists, if given a pull or relative path // TODO: Check if file at the path is executable (i.e. +x) - let mut cmd = String::from(cmd); if !Path::new(cmd.as_str()).exists() { let b = bins.clone(); // Check if the command exists in $PATH if a full or relative path @@ -79,32 +135,23 @@ fn eval(paths: &[&str], prompt: &str) { // If the command is not found the first time, try refreshing the // path first, and only print an error if if it's not found after // the path refresh - cmd = String::from( - match b - .clone() - .iter() - .find(|b| b.split("/").last().unwrap() == cmd) - { - Some(cmd) => cmd, - None => { - for path in paths { - let files = - fs::read_dir(path).expect("Unable to read files in your path"); - for file in files { - bins.push(file.unwrap().path().display().to_string()); - } - } - - match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { - Some(cmd) => cmd, - None => { - println!("dwvsh: error: command not found..."); - continue; - } + cmd = match b + .clone() + .iter() + .find(|b| b.split("/").last().unwrap() == cmd) + { + Some(cmd) => cmd.clone(), + None => { + bins = prefresh(&paths); + match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { + Some(cmd) => cmd.clone(), + None => { + println!("dwvsh: {}: command not found", cmd); + continue; } } - }, - ); + } + } } // Run the command (and wait for it to finish) @@ -112,15 +159,23 @@ fn eval(paths: &[&str], prompt: &str) { Ok(ch) => ch, Err(err) => { match err.kind() { - std::io::ErrorKind::PermissionDenied => println!( - "dwvsh: error: permission denied trying to fork to {}...", - &cmd - ), - // TODO: Refresh paths if error kind is NotFound - std::io::ErrorKind::NotFound => println!("dwvsh: error: command not found..."), - _ => println!("dwvsh: error: unable to fork..."), + // Occurs when the user doesn't have read access, or if the +x bit is not set + std::io::ErrorKind::PermissionDenied => { + println!("dwvsh: permission denied: trying to fork to {}", &cmd) + } + + // Occurs if a command was removed from the path + // If this is the case, refresh the path + std::io::ErrorKind::NotFound => { + bins = prefresh(&paths); + println!("dwvsh: {}: command not found", cmd); + } + + // Otherwise print the OS error + _ => println!("dwvsh: fork: {}", err), } + // Restart the loop continue; } }; @@ -128,10 +183,13 @@ fn eval(paths: &[&str], prompt: &str) { } } +/// Shell entry +/// +/// Shell setup and entry fn main() { // Define paths // TODO: Hardcoded path should only be the fallback - let paths = [ + let paths = vec![ "/bin", "/sbin", "/usr/bin", @@ -139,6 +197,7 @@ fn main() { "/usr/local/bin", "/usr/local/sbin", ]; + let paths = paths.into_iter().map(Path::new).collect(); // Set the prompt let prompt = "|> "; @@ -148,8 +207,8 @@ fn main() { print!("\n{}", prompt); io::stdout().flush().unwrap(); }) - .expect("Unable to set handler"); + .expect("dwvsh: signals: unable to set sigint handler"); // Begin evaluating commands - eval(&paths, prompt); + repl(&paths, prompt); } -- cgit v1.2.3