use core::fmt; use ctrlc; use std::fs; use std::io::{self, Write}; use std::path::Path; use std::process::{exit, Command, Stdio}; #[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 String, // Run the next command, even if this doesn't succeed } impl fmt::Display for Meter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let meter = match self { Meter::None => "", Meter::Pipe => "|", Meter::Daemon => "&", Meter::And => "&&", Meter::String => ";", }; write!(f, "{}", meter) } } #[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() } } impl fmt::Display for Verse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.verb(), self.clause().join(" ")) } } #[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() { // Check if user wants to exit the shell if verse.verb() == "exit" || verse.verb() == "quit" { exit(0); } if verse.verb() == "cd" { let path: String; if verse.clause().is_empty() { path = env!("HOME").to_string(); } else { path = verse.clause().first().unwrap().to_owned(); } match std::env::set_current_dir(&path) { Ok(_) => continue, Err(_) => { println!("cd: unable to change into {}", path); continue; } } } 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 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(); out = String::from_utf8(output.stdout).unwrap(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .stdin(Stdio::piped()) .spawn() .expect("dwvsh: error 1"); let stdin = child.stdin.as_mut().expect("dwvsh: error 8"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 9"); out.clear(); print!("[f] {}", child.id()); // let p = prompt.to_owned(); std::thread::spawn(move || { child.wait().unwrap(); println!("[f] +done {}", child.id()); io::stdout().flush().unwrap(); }); } Meter::String => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 5"); let 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 stdin = child.stdin.as_mut().expect("dwvsh: error 10"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 11"); out.clear(); if !child.wait().unwrap().success() { break; } } }; } else { match verse.meter { Meter::Pipe => { let 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(); // out = String::from_utf8(output.stdout).unwrap(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 4"); println!("[f] {}", child.id()); std::thread::spawn(move || { child.wait().unwrap(); print!("[f] +done {}\n", child.id()); io::stdout().flush().unwrap(); }); } Meter::String => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 5"); child.wait().unwrap(); } Meter::And | Meter::None => { let mut child = Command::new(verse.verb()) .args(verse.clause()) .spawn() .expect("dwvsh: error 5"); if !child.wait().unwrap().success() { break; } } }; } } true } } fn read(poetry: String) -> Option { let mut chars = poetry.chars(); let mut verses: Vec = Vec::new(); let mut stanza: Vec = Vec::new(); let mut word: Vec = Vec::new(); let mut prev: Option<&Verse> = None; loop { let char = chars.next(); let pipe = match prev { Some(prev) => match prev.meter { Meter::Pipe => true, Meter::Daemon | Meter::And | Meter::String | Meter::None => false, }, None => false, }; let metered = match prev { Some(prev) => match prev.meter { Meter::Pipe | Meter::Daemon | Meter::And | Meter::String => true, Meter::None => false, }, None => false, }; match char { Some(meter) if (meter == '|' || meter == '&' || meter == ';') && metered && stanza.is_empty() => { println!("dwvsh: parse error"); return None; } Some(meter) if meter == '|' => { if !word.is_empty() { stanza.push(word.iter().collect()); } verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Pipe, pipe)); stanza = Vec::new(); word.clear(); } Some(meter) if meter == '&' => { if !word.is_empty() { stanza.push(word.iter().collect()); } match chars.clone().peekable().peek() { Some(c) if c == &'&' => { chars.next(); verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::And, pipe)); } Some(_) => { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Daemon, pipe)); } None => { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Daemon, pipe)); } } stanza = Vec::new(); word.clear(); } Some(meter) if meter == ';' => { if !word.is_empty() { stanza.push(word.iter().collect()); } verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::String, pipe)); stanza = Vec::new(); word.clear(); } Some(char) if char == ' ' => { if !word.is_empty() { stanza.push(word.iter().collect()); word.clear(); } } Some(char) => { word.push(char); } None => { if !word.is_empty() { stanza.push(word.iter().collect()); } if !stanza.is_empty() { verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::None, pipe)); } break; } } prev = match verses.last() { Some(verse) => Some(verse), None => None, }; } Some(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()); // Skip parsing if there is no poetry if !poetry.is_empty() { // Parse a poem let poem = read(poetry); match poem { Some(poem) => { // poem.recite(paths, &mut bins, prompt); poem.recite(paths, &mut bins); } None => {} } } } } /// 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); }