use ctrlc; use notify::RecursiveMode; use notify::Watcher; use signals2::*; use std::fs; use std::io; use std::io::Write; use std::path::Path; use std::process::Command; use std::sync::Arc; use std::sync::RwLock; fn prefresh(paths: &Vec, bins: &mut Arc>>>, index: Option) { let mut bins = bins.write().unwrap(); let index = index.unwrap_or(-1); if index == -1 { for (i, path) in paths.iter().enumerate() { let files = fs::read_dir(path).expect("Unable to read files in your path"); bins.push(Vec::new()); for file in files { bins[i].push(file.unwrap().path().display().to_string()); } } } else { let index = index as usize; let files = fs::read_dir(paths[index].as_str()).expect("Unable to read files in your path"); bins[index].clear(); for file in files { bins[index].push(file.unwrap().path().display().to_string()); } } } fn eval(paths: Vec, prompt: &str) { // Setup search for our paths let mut bins = Arc::new(RwLock::new(Vec::new())); prefresh(&paths, &mut bins, None); // Handle file changes on paths let sig: Signal<(i32,)> = Signal::new(); let arcbins = Arc::clone(&bins); let p = paths.clone(); sig.connect(move |i| { let mut arcbins = arcbins.clone(); prefresh(&paths, &mut arcbins, Some(i)); }); let mut watcher = notify::recommended_watcher(move |res: Result| { match res { Ok(event) => { if event.kind.is_create() || event.kind.is_remove() { sig.emit(0); } } Err(_) => {} } }) .unwrap(); for path in p { watcher .watch(Path::new(path.as_str()), RecursiveMode::Recursive) .unwrap(); } // Main REPL 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("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 and arguments let mut split = input.split(' '); let cmd = match split.next() { Some(str) if str.trim().is_empty() => continue, Some(str) => 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) let mut cmd = String::from(cmd); if !Path::new(cmd.as_str()).exists() { cmd = match bins .read() .unwrap() .iter() .flatten() .map(|s| String::from(s)) .collect::>() .iter() .find(|b| b.split("/").last().unwrap() == cmd) { Some(cmd) => String::from(cmd), None => { println!("dwvsh: error: command not found..."); continue; } }; } // Run the command (and wait for it to finish) let mut child = match Command::new(cmd).args(args).spawn() { Ok(ch) => ch, Err(_) => { println!("Unable to fork"); continue; } }; child.wait().unwrap(); } } fn main() { // Define paths // TODO: Hardcoded path should only be the fallback let paths = vec![ "/bin".to_string(), "/sbin".to_string(), "/usr/bin".to_string(), "/usr/sbin".to_string(), "/usr/local/bin".to_string(), "/usr/local/sbin".to_string(), ]; // Set the prompt let prompt = "|> "; // Handle SIGINT ctrlc::set_handler(move || { print!("\n{}", prompt); io::stdout().flush().unwrap(); }) .expect("Unable to set handler"); // Begin evaluating commands eval(paths, prompt); }