use ctrlc; use std::fs; use std::io::{self, Write}; use std::path::Path; use std::process::Command; /// 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(); print!("{}", prompt); io::stdout().flush().unwrap(); // Wait for user input let mut input = String::new(); let bytes = io::stdin() .read_line(&mut input) .expect("dwvsh: error: unable to evaluate the input string"); // Check if we've reached EOF (i.e. ) if bytes == 0 { println!(""); break; } // Trim the input let input = input.trim(); // Check if user wants to exit the shell if input == "exit" || input == "quit" { break; } // Parse command let mut split = input.split(' '); let mut cmd = match split.next() { Some(str) if str.trim().is_empty() => continue, Some(str) => String::from(str.trim()), None => continue, }; // Parse arguments let mut args = vec![]; loop { let next = split.next(); match next { Some(str) => args.push(str), None => break, } } // Check if user wants to change directories if cmd == "cd" { let path = match args.first() { Some(str) => str, None => env!("HOME"), }; match std::env::set_current_dir(path) { Ok(_) => continue, Err(_) => { println!("cd: unable to change into {}", path); continue; } } } // Check if the file exists, if given a pull or relative path // TODO: Check if file at the path is executable (i.e. +x) if !Path::new(cmd.as_str()).exists() { let b = bins.clone(); // Check if the command exists in $PATH if a full or relative path // was not given, or if the path does not exist // // 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 = 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) let mut child = match Command::new(&cmd).args(args).spawn() { Ok(ch) => ch, Err(err) => { match err.kind() { // 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; } }; child.wait().unwrap(); } } /// Shell entry /// /// Shell setup and entry fn main() { // Define paths // TODO: Hardcoded path should only be the fallback let paths = vec![ "/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin", ]; let paths = paths.into_iter().map(Path::new).collect(); // Set the prompt let prompt = "|> "; // Handle signals ctrlc::set_handler(move || { print!("\n{}", prompt); io::stdout().flush().unwrap(); }) .expect("dwvsh: signals: unable to set sigint handler"); // Begin evaluating commands repl(&paths, prompt); }