summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-02-22 23:12:24 -0700
committerRory Dudley2024-02-22 23:12:24 -0700
commit536e250653e5c140a6d9e60f1cd652b79135e7a6 (patch)
tree6a6b212c913f52d559ed9ea8a9477bebb9b38a2d
parenta14fdf8faa85628baf06399961eb1f9ab4c5f3eb (diff)
downloaddwarvish-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.
-rw-r--r--src/main.rs441
-rw-r--r--src/recite.rs628
-rw-r--r--src/recite/path.rs42
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
+}