use ctrlc; use std::fs; use std::io::{self, Write}; use std::path::Path; use std::process::{Command, Stdio}; // #[derive(Debug)] // enum Meter { // Verb, // A command (and possible some arguments) // Pipe, // Pipe the output of this command into the next // Daemon, // Fork the command into the background // And, // Run the next command only if this succeeds // } // impl fmt::Display for Meter { // fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // let meter = match self { // Meter::Verb => "verb", // Meter::Pipe => "pipe", // Meter::Daemon => "daemon", // Meter::And => "and", // }; // write!(f, "{}", meter) // } // } // #[derive(Debug)] // struct Stanza { // verb: Option, // clause: Option>, // } // impl Stanza { // fn new(line: String) -> Stanza { // let mut split = line.split(' '); // let cmd = match split.next() { // Some(verb) if verb.trim().is_empty() => None, // Some(verb) => Some(String::from(verb.trim())), // None => None, // }; // let mut args: Option>; // match cmd { // Some(_) => { // args = Some(Vec::new()); // loop { // let next = split.next(); // match next { // Some(clause) => args.unwrap().push(clause.to_string()), // None => break, // } // } // } // None => args = None, // } // Stanza { // verb: cmd, // clause: args, // } // } // } // #[derive(Debug)] // struct Verse { // meter: Meter, // stanza: Option, // } // #[derive(Debug)] // struct Poem { // verses: Vec, // } // impl Poem { // fn new() -> Poem { // Poem { verses: Vec::new() } // } // fn recite(&self) { // unimplemented!(); // } // } #[derive(Debug)] enum Meter { None, // No meter Pipe, // Pipe the output of this command into the next Daemon, // Fork the command into the background And, // Run the next command only if this succeeds } #[derive(Debug)] struct Stanza { verb: String, clause: Vec, } impl Stanza { fn new(stanza: Vec) -> Stanza { Stanza { verb: stanza[0].clone(), clause: stanza[1..].to_vec(), } } fn spellcheck(&self, bins: &Vec) -> bool { if self.verb.is_empty() { return false; } if !Path::new(self.verb.as_str()).exists() { match bins .iter() .find(|bin| bin.split('/').last().unwrap() == self.verb) { Some(_) => return true, None => return false, } } true } } #[derive(Debug)] struct Verse { stanza: Stanza, meter: Meter, stdin: bool, } impl Verse { fn new(stanza: Stanza, meter: Meter, stdin: bool) -> Verse { Verse { stanza, meter, stdin, } } fn spellcheck(&self, bins: &Vec) -> bool { self.stanza.spellcheck(bins) } fn verb(&self) -> String { self.stanza.verb.clone() } fn clause(&self) -> Vec { self.stanza.clause.clone() } } #[derive(Debug)] struct Poem { verses: Vec, } impl Poem { fn new(verses: Vec) -> Poem { Poem { verses } } fn recite(&self, paths: &Vec<&Path>, bins: &mut Vec) -> bool { // println!("{:#?}", self); let mut out: String = String::new(); for verse in self.verses.iter() { // let verse = match self.verses.iter().next() { // Some(verse) => verse, // None => break, // }; if !verse.spellcheck(bins) { *bins = prefresh(paths); if !verse.spellcheck(bins) { println!("dwvsh: {}: command not found", verse.verb()); continue; } } if verse.stdin { match verse.meter { Meter::Pipe => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("dwvsh: error 0"); let mut stdin = child.stdin.as_mut().expect("dwvsh: error 6"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 7"); out.clear(); let output = child.wait_with_output().unwrap(); out = String::from_utf8_lossy(&output.stdout).to_string(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .stdin(Stdio::piped()) .spawn() .expect("dwvsh: error 1"); let mut stdin = child.stdin.as_mut().expect("dwvsh: error 8"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 9"); out.clear(); child.wait().unwrap(); } Meter::And | Meter::None => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .stdin(Stdio::piped()) .spawn() .expect("dwvsh: error 2"); let mut stdin = child.stdin.as_mut().expect("dwvsh: error 10"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 11"); out.clear(); child.wait().unwrap(); } }; } else { match verse.meter { Meter::Pipe => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .stdout(Stdio::piped()) .spawn() .expect("dwvsh: error 3"); let output = child.wait_with_output().unwrap(); out = String::from_utf8_lossy(&output.stdout).to_string(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 4"); child.wait().unwrap(); } Meter::And | Meter::None => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 5"); child.wait().unwrap(); } }; } } true } } fn read(poetry: String) -> Poem { let mut words = poetry.split(' '); let mut verses: Vec = Vec::new(); let mut stanza: Vec = Vec::new(); let mut prev: Option<&Verse> = None; loop { let word = words.next(); let stdin = match prev { Some(prev) => match prev.meter { Meter::Pipe => true, Meter::Daemon | Meter::And | Meter::None => false, }, None => false, }; match word { Some(verb) if verb == "|" => { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Pipe, stdin)); stanza = Vec::new(); } Some(verb) if verb == "&" => { verses.push(Verse::new( Stanza::new(stanza.clone()), Meter::Daemon, stdin, )); stanza = Vec::new(); } Some(verb) if verb == "&&" => { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::And, stdin)); stanza = Vec::new(); } Some(verb) => { stanza.push(verb.trim().to_string()); } None => { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::None, stdin)); break; } } prev = match verses.last() { Some(verse) => Some(verse), None => None, }; } Poem::new(verses) } /// 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 poetry = String::new(); let bytes = io::stdin() .read_line(&mut poetry) .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 poetry = String::from(poetry.trim()); // Check if user wants to exit the shell if poetry == "exit" || poetry == "quit" { break; } // Parse a poem let poem = read(poetry); poem.recite(paths, &mut bins); // // Parse command // let mut split = poetry.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.split("/").last().unwrap() // ) // } // // 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.split("/").last().unwrap() // ); // } // // 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"); // let poem = read("eza -la".to_string()); // for line in poem.verses.iter().zip(poem.meters) { // let (verse, meter) = line; // println!("{}: {}", meter, verse); // } // Begin evaluating commands repl(&paths, prompt); }