diff options
author | Rory Dudley | 2024-02-22 23:12:24 -0700 |
---|---|---|
committer | Rory Dudley | 2024-02-22 23:12:24 -0700 |
commit | 536e250653e5c140a6d9e60f1cd652b79135e7a6 (patch) | |
tree | 6a6b212c913f52d559ed9ea8a9477bebb9b38a2d /src | |
parent | a14fdf8faa85628baf06399961eb1f9ab4c5f3eb (diff) | |
download | dwarvish-536e250653e5c140a6d9e60f1cd652b79135e7a6.tar.gz |
Reorganization and comments
Broke out the structs for a poem into their own file: src/recite.rs.
Also put the 'prefresh' function into it's own file: src/recite/path.rs.
Commented most of the parser code (including structs and helper methods
related to parsing (i.e. Verse, Stanza, Meter, Poem)). Renamed any
instance of the 'paths' variable to 'path'.
Notes
Notes:
The biggest task now is to cleanup Poem::recite. It has a ton of bogus
error messages, and (seemingly) redundant code.
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 441 | ||||
-rw-r--r-- | src/recite.rs | 628 | ||||
-rw-r--r-- | src/recite/path.rs | 42 |
3 files changed, 691 insertions, 420 deletions
diff --git a/src/main.rs b/src/main.rs index 3c016e9..db349d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,403 +1,9 @@ -use core::fmt; +mod recite; use ctrlc; -use std::fs; +use recite::path::prefresh; +use recite::Poem; 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<String>, -} - -impl Stanza { - fn new(stanza: Vec<String>) -> Stanza { - Stanza { - verb: stanza[0].clone(), - clause: stanza[1..].to_vec(), - } - } - - fn spellcheck(&self, bins: &Vec<String>) -> 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<String>) -> bool { - self.stanza.spellcheck(bins) - } - - fn verb(&self) -> String { - self.stanza.verb.clone() - } - - fn clause(&self) -> Vec<String> { - 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<Verse>, -} - -impl Poem { - fn new(verses: Vec<Verse>) -> Poem { - Poem { verses } - } - - fn recite(&self, paths: &Vec<&Path>, bins: &mut Vec<String>) -> 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<Poem> { - let mut chars = poetry.chars(); - let mut verses: Vec<Verse> = Vec::new(); - let mut stanza: Vec<String> = Vec::new(); - let mut word: Vec<char> = 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<String>` - 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<String> { - let mut bins: Vec<String> = 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 /// @@ -408,16 +14,16 @@ fn prefresh(paths: &Vec<&Path>) -> Vec<String> { /// # Examples /// ``` /// fn main() { -/// let paths = vec!["/bin"]; -/// let paths = paths.into_iter().map(Path::new).collect(); +/// let path = vec!["/bin"]; +/// let path = path.into_iter().map(Path::new).collect(); /// let prompt = "|> "; /// ... -/// repl(&paths, prompt); +/// repl(&path, prompt); /// } /// ``` -fn repl(paths: &Vec<&Path>, prompt: &str) { +fn repl(path: &Vec<&Path>, prompt: &str) { // Initial path refresh on startup - let mut bins: Vec<String> = prefresh(paths); + let mut bins: Vec<String> = prefresh(path); // Main shell loop loop { @@ -442,16 +48,17 @@ fn repl(paths: &Vec<&Path>, prompt: &str) { 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 => {} + if poetry.is_empty() { + continue; + } + + // Parse a poem + let poem = Poem::read(poetry); + match poem { + Some(poem) => { + poem.recite(path, &mut bins); } + None => {} } } } @@ -462,7 +69,7 @@ fn repl(paths: &Vec<&Path>, prompt: &str) { fn main() { // Define paths // TODO: Hardcoded path should only be the fallback - let paths = vec![ + let path = vec![ "/bin", "/sbin", "/usr/bin", @@ -470,7 +77,7 @@ fn main() { "/usr/local/bin", "/usr/local/sbin", ]; - let paths = paths.into_iter().map(Path::new).collect(); + let path = path.into_iter().map(Path::new).collect(); // Set the prompt let prompt = "|> "; @@ -482,12 +89,6 @@ fn main() { }) .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); + repl(&path, prompt); } diff --git a/src/recite.rs b/src/recite.rs new file mode 100644 index 0000000..4e1dc1f --- /dev/null +++ b/src/recite.rs @@ -0,0 +1,628 @@ +pub mod path; +use core::fmt; +use path::prefresh; +use std::io::{self, Write}; +use std::path::Path; +use std::process::{exit, Command, Stdio}; + +/// Describes the ending of a [Verse] +/// +/// The ending of a verse determines how the [Stanza] should be interpreted. +/// For instance, a [Stanza] that is piped needs to have it's `STDOUT` +/// captured (rather than printing out to the terminal), and subsequently sent +/// to the next [Verse] in the [Poem]. +/// +/// # Values +/// * `None` - A shell command with no additional actions +/// * `Couplet` - Pipe the output of this command into the next (`|`) +/// * `Quiet` - Fork the called process into the background (`&`) +/// * `And` - Run the next command only if this one succeeds (`&&`) +/// * `String` - String commands together on a single line (`;`) +#[derive(Debug)] +enum Meter { + None, // No meter + Couplet, // Pipe the output of this command into the next + Quiet, // 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 { + /// Determine how to print out a [Meter] + /// + /// Each [meter's][Meter] symbol corresponds to it's input. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let meter = match self { + Meter::None => "", + Meter::Couplet => "|", + Meter::Quiet => "&", + Meter::And => "&&", + Meter::String => ";", + }; + + write!(f, "{}", meter) + } +} + +/// Holds a program to be called +/// +/// This is simply the first word in a full command [String], dilineated via +/// whitespace. +type Verb = String; + +/// Holds arguments to a program +/// +/// This is a list of all the words that come after the [Verb], dilineated via +/// whitespace. +type Clause = Vec<String>; + +/// Holds the interpreted elements of a [Verse] +/// +/// Each [Stanza] has two parts, a [Verb] and a [Clause]. The [Verb] is the +/// program, or path to the program to call, while the [Clause] contains +/// arguments to pass to that program. +#[derive(Debug)] +struct Stanza { + verb: Verb, + clause: Clause, +} + +impl fmt::Display for Stanza { + /// Print out a [Stanza] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.verb, self.clause.join(" ")) + } +} + +impl Stanza { + /// Create a new [Stanza] + /// + /// Returns a new [Stanza] built from a `Vec<String>`. The first element of + /// the vector becomes the [Verb], while the remainder of the vector + /// becomes the [Clause]. + /// + /// # Arguments + /// `stanza: Vec<String>` - The full command split into individual strings + /// via whitespace + /// + /// # Examples + /// ``` + /// // Input: cargo build --release + /// let command = vec!["cargo", "build", "--release"] + /// .into_iter() + /// .map(String::from) + /// .collect<Vec<String>>(); + /// let stanza = Stanza::new(command); + /// println!("{}", stanza.verb); + /// println!("{:?}", stanza.clause); + /// + /// ``` + fn new(stanza: Vec<String>) -> Stanza { + Stanza { + verb: stanza[0].to_owned(), + clause: stanza[1..].to_vec(), + } + } + + /// Check if the [Verb] exists in the `$PATH` + /// + /// First checks if the [Verb] is a relative or full path. If it is, check + /// whether or not it exists. If it does exist, return true, otherwise see + /// if the [Verb] is cached in our list of binaries. Search is done in + /// $PATH order. + /// + /// # Examples + /// ``` + /// let bins = vec!["cargo", "ruby", "cat"] + /// .into_iter() + /// .map(String::from) + /// .collect<Vec<String>>(); + /// + /// let command_success = vec!["cargo", "build", "--release"] + /// .into_iter() + /// .map(String::from) + /// .collect<Vec<String>>(); + /// + /// let command_fail = vec!["make", "-j8"] + /// .into_iter() + /// .map(String::from) + /// .collect<Vec<String>>(); + /// + /// let stanza_success = Stanza::new(command_success); + /// let stanza_fail = Stanza::new(command_fail); + /// + /// stanza_success.spellcheck(bins) // -> true + /// stanza_fail.spellcheck(bins) // -> false + /// ``` + fn spellcheck(&self, bins: &Vec<String>) -> bool { + // An empty verb (i.e. the empty string) cannot be a program, so + // return false + // Thanks to the parsing in Poem::read, however, it's + // unlikely for this to happen + if self.verb.is_empty() { + return false; + } + + // Only search the $PATH if a full or relative path was not given, or + // if the path given does not exist + if !Path::new(self.verb.as_str()).exists() { + // Try to find a binary in our path with the same name as the verb + // Searches in $PATH order + match bins + .iter() + .find(|bin| bin.split('/').last().unwrap() == self.verb) + { + Some(_) => return true, + None => return false, + } + } + + // Return true if the full path or relative path exists + true + } +} + +/// Holds a [Stanza] and its [Meter] +/// +/// In addition to a [Stanza] and a [Meter], [verse's][Verse] also hold a bool +/// value called `couplet`, indicating that it needs to accept input on `STDIN` +/// from the previous [Verse]. +#[derive(Debug)] +struct Verse { + stanza: Stanza, + meter: Meter, + couplet: bool, +} + +impl fmt::Display for Verse { + /// Print out a [Verse] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{} {} {}", + self.verb(), + self.clause().join(" "), + self.meter + ) + } +} + +impl Verse { + /// Create a new [Verse] + /// + /// Returns a new [Verse] built from a [Stanza], a [Meter], and a `couplet` + /// indicator. See [Poem::read] for more details on how these are + /// constructed. + fn new(stanza: Stanza, meter: Meter, couplet: bool) -> Verse { + Verse { + stanza, + meter, + couplet, + } + } + + /// Alias to [Stanza::spellcheck] + fn spellcheck(&self, bins: &Vec<String>) -> bool { + self.stanza.spellcheck(bins) + } + + /// Alias to [stanza's][Stanza] `verb` + fn verb(&self) -> String { + self.stanza.verb.clone() + } + + /// Alias to [stanza's][Stanza] `clause` + fn clause(&self) -> Vec<String> { + self.stanza.clause.clone() + } +} + +/// An entire shell command parsed into [verse's][Verse] +/// +/// A [Poem] is the structure that contains a full shell command/program. It +/// may be composed of one or many [verse's][Verse]. +#[derive(Debug)] +pub struct Poem { + verses: Vec<Verse>, +} + +impl Poem { + /// Create a new [Poem] + /// + /// Returns a new [Poem] built from a list of [verse's][Verse]. + fn new(verses: Vec<Verse>) -> Poem { + Poem { verses } + } + + /// Recite a [Poem] (run the shell command(s)/program) + /// + /// This function attempts to call each [Verse] in the [Poem], in the order + /// that it was inputted/parsed. + /// + /// # Arguments + /// * `path` - A list of directories from the $PATH environment variable + /// Needed in case we need to refresh the $PATH + /// * `bins` - A list of binaries cached from the $PATH, used for searching + /// for a program that matches the verb in each [Verse] + /// + /// # Returns + /// * `true` - If the entire [Poem] was recited without fault + /// * `false` - If any [Verse] of the [Poem] was invalid + /// + /// # Examples + /// ``` + /// let poetry = "ps aux | grep dwvsh".to_string(); + /// let poem = Poem::read(poetry); + /// + /// match poem { + /// Some(poem) => { poem.recite(path, &mut bins); } + /// None => {} + /// } + /// ``` + pub fn recite(&self, path: &Vec<&Path>, bins: &mut Vec<String>) -> bool { + // Variable for storing the output of a piped verse + let mut out: String = String::new(); + + // Loop through each verse in the poem + for verse in self.verses.iter() { + // Check if user wants to exit the shell + if verse.verb() == "exit" || verse.verb() == "quit" { + exit(0); + } + + // Check if the user wants to change directories + 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; + } + } + } + + // Check if the verb exists + // If it doesn't exist, try refreshing the binary cache, and check + // again + // If it still doesn't exist, print an error + if !verse.spellcheck(bins) { + *bins = prefresh(path); + if !verse.spellcheck(bins) { + println!("dwvsh: {}: command not found", verse.verb()); + continue; + } + } + + // If the verse is a couplet, it means it needs the output of the + // previous verse on `STDIN` + if verse.couplet { + match verse.meter { + Meter::Couplet => { + 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(); + } + Meter::Quiet => { + 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()); + 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::Couplet => { + 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(); + } + Meter::Quiet => { + 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; + } + } + }; + } + } + + // If we've successfully exited the loop, then all verse's were + // properly recited + true + } + + /// Parse a [Poem] from a raw [String] input + /// + /// Takes a shell command/program and converts it to a machine-runnable + /// [Poem]. If there is a parse error, [Poem::read] may [Option]ally return + /// `None`. As of now, there is no support for multiline programs, unless + /// newlines (`\n`) were to be swapped out for semicolons (`;`) before + /// calling this function. See [Poem::recite] for how each [Verse] in a + /// [Poem] is called. + /// + /// # Examples + /// ``` + /// let poetry = "ps aux | grep dwvsh".to_string(); + /// let poem = Poem::read(poetry); + /// ``` + pub fn read(poetry: String) -> Option<Poem> { + // Need to loop through each char in the input string, since some + // characters aren't whitespace dilineated (`;`, `&`, etc.) + // + // Need to keep track of the previous verse, since it might haver + // a Meter of Couplet, meaning that we need to set couplet on the + // current verse + let mut chars = poetry.chars(); + let mut verses: Vec<Verse> = Vec::new(); // Accumulate verses + let mut stanza: Vec<String> = Vec::new(); // Stack for each stanza + let mut word: Vec<char> = Vec::new(); // Stack for each word + let mut prev: Option<&Verse> = None; // The previous verse + + // Parse from left to right + loop { + // Get the next character in the input string + let char = chars.next(); + + // Check if the previous verse is piping output to current + // TODO: Don't need to run this on each iteration of the loop, just when + // a stanza is pushed to a verse + let couplet = match prev { + Some(prev) => match prev.meter { + Meter::Couplet => true, + Meter::Quiet | Meter::And | Meter::String | Meter::None => false, + }, + None => false, + }; + + // Check if the previous verse was metered + // Need this to check for parse/input errors + let metered = match prev { + Some(prev) => match prev.meter { + Meter::Couplet | Meter::Quiet | Meter::And | Meter::String => true, + Meter::None => false, + }, + None => false, + }; + + // Do something depending on what the character is + match char { + // Print an error, and return None if a Meter was used without + // a Stanza before it + Some(meter) + if (meter == '|' || meter == '&' || meter == ';') + && metered + && stanza.is_empty() => + { + // TODO: Add more verbose error message + println!("dwvsh: parse error"); + return None; + } + + // The character represents the Couplet Meter + Some(meter) if meter == '|' => { + // If there are chars on the word stack, push that word + // onto the stanza + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + + // A meter indicates the end of a verse + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::Couplet, + couplet, + )); + + // Clear the stacks + stanza.clear(); + word.clear(); + } + + // The character represents the Quiet (or And) Meter + Some(meter) if meter == '&' => { + // If there are chars on the word stack, push that word + // onto the stanza + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + + // Need to peek at the next character, since '&' can mean + // Meter::Quiet, or '&&' can mean Meter::And + match chars.clone().peekable().peek() { + // Indicated Meter::And + Some(c) if c == &'&' => { + // Pop the next character from the input string + // (since we know what it is) + chars.next(); + + // A meter indicates the end of a verse + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::And, + couplet, + )); + } + + // Indicates Meter::Quiet + Some(_) => { + // A meter indicates the end of a verse + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::Quiet, + couplet, + )); + } + + // Indicated the end of the input + None => { + // The end of input also indicates the end of a + // verse + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::Quiet, + couplet, + )); + + // We can break out of the loop here, since it's + // the end of the raw input + break; + } + } + + // Clear the stacks + stanza.clear(); + word.clear(); + } + + // The character represents the String Meter + Some(meter) if meter == ';' => { + // If there are chars on the word stack, push that word + // onto the stanza + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + + // A meter indicates the end of a verse + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::String, + couplet, + )); + + stanza.clear(); + word.clear(); + } + + // The character is whitespace + Some(char) if char == ' ' || char == '\t' => { + if !word.is_empty() { + stanza.push(word.iter().collect()); + word.clear(); + } + } + + // The character is any other utf8 glyph + Some(char) => { + word.push(char); + } + + // Indicates the end of the list of characters + None => { + // Always push the last word onto the stanza + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + + // Only push the stanza into a verse if it contains + // any words + if !stanza.is_empty() { + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::None, + couplet, + )); + } + + // Break from the loop, since we are out of chars + break; + } + } + + // Set previous verse to the verse that was just pushed at the end + // of each loop + prev = match verses.last() { + Some(verse) => Some(verse), + None => None, + }; + } + + // Return the (parsed) poem + Some(Poem::new(verses)) + } +} diff --git a/src/recite/path.rs b/src/recite/path.rs new file mode 100644 index 0000000..28eb45b --- /dev/null +++ b/src/recite/path.rs @@ -0,0 +1,42 @@ +use std::fs; +use std::path::Path; + +/// 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<String>` - A new cache of all valid file paths in $PATH +/// +/// # Examples +/// ``` +/// let path = vec!["/bin"]; +/// let path = path.into_iter().map(Path::new).collect(); +/// let mut bins = prefresh(&path); +/// ... +/// // A situation occurs where the $PATH needs to be refreshed +/// bins = prefresh(&path) +/// ``` +pub fn prefresh(path: &Vec<&Path>) -> Vec<String> { + let mut bins: Vec<String> = Vec::new(); + + for p in path { + let files = fs::read_dir(p).expect( + format!( + "dwvsh: error: unable to read the contents of {}", + p.display().to_string() + ) + .as_str(), + ); + + for file in files { + bins.push(file.unwrap().path().display().to_string()); + } + } + + bins +} |