diff options
author | Rory Dudley | 2024-05-19 18:50:06 -0600 |
---|---|---|
committer | Rory Dudley | 2024-05-19 18:50:06 -0600 |
commit | 4b1b8061e79b42128df4f06fd1e439549bf9696b (patch) | |
tree | 80db43cf7295937751d61435fb4e60118b8a3ea9 | |
parent | 8756d3e7512c1416cc15a688c62b8f51f030b192 (diff) | |
download | dwarvish-4b1b8061e79b42128df4f06fd1e439549bf9696b.tar.gz |
Handle STDERR, in addition to STDOUT
This patch overhauls the reading and reciting of verses, such that the
redirection of STDERR (in addition to STDOUT, which was already a
feature), is now possible.
Removed the 'stdout' argument from recite(), since it is no longer
needed with how incantations function.
A verse's couplet indicator is now a u8, instead of a bool, with certain
values corresponding to types of couplets, for instance:
ls | grep Ca | lolcat
^ ^ ^
| | 2: right side of a couplet
| 3: both sides of a couplet
1: left side of a couplet
Incantions are no longer hanlded in rune.rs, and the task macros have
been removed. Now, a verse incants itself, matching on its own meter to
determine how to handle the next verse.
The following runes were added to help with handling STDERR:
Write2 -> 2>
WriteAll -> &>
Addendum2 -> 2>>
AddendumAll -> &>>
The 'io' field in verse was changed from an Option<Rune>, to an array of
Runes, since a single verse might have multiple IO operations.
The following fields were added to Verse, to assist with handling
STDERR:
ip -> List of filenames to read into STDIN
op -> List of filenames to send STDOUT to
ep -> List of filenames to send STDERR to
Keep track of channels when reading a poem. Channels are relating to IO
operations. If channel is None, words get pushed to the verse's primary
stanza (i.e. the verb or the clause). If a channel is selected, words
are pushed to one of the aforementioned new fields in Verse.
Read -> ip
Write/Addedum -> op
Write2/Addedum2 -> ep
WriteAll/AddendumAll -> op and ep
Notes
Notes:
This commit also added tests for the new Runes.
-rw-r--r-- | src/compose.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/poem.rs | 151 | ||||
-rw-r--r-- | src/poem/anthology/alias.rs | 2 | ||||
-rw-r--r-- | src/poem/anthology/source.rs | 4 | ||||
-rw-r--r-- | src/poem/elements/rune.rs | 339 | ||||
-rw-r--r-- | src/poem/elements/verse.rs | 285 | ||||
-rw-r--r-- | src/poem/read.rs | 124 | ||||
-rw-r--r-- | src/poem/read/parse.rs | 52 | ||||
-rw-r--r-- | src/poem/recite.rs | 34 | ||||
-rw-r--r-- | src/poem/recite/ps.rs | 134 |
11 files changed, 566 insertions, 563 deletions
diff --git a/src/compose.rs b/src/compose.rs index f59b719..46f04a7 100644 --- a/src/compose.rs +++ b/src/compose.rs @@ -65,7 +65,7 @@ fn rrr(path: PathBuf, env: &mut Environment) { } }; - match poem.recite(env, None) { + match poem.recite(env) { Ok(_) => {} Err(e) => { eprintln!("dwvsh: {}", e.to_string().to_lowercase()); diff --git a/src/main.rs b/src/main.rs index 3e0a2e8..821dde2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,7 @@ fn repl(away: &mut Arc<Mutex<bool>>, env: &mut Environment) { }; // Recite the poem - match poem.recite(env, None) { + match poem.recite(env) { Ok(_) => {} Err(e) => eprintln!("dwvsh: {}", e.to_string().to_lowercase()), } diff --git a/src/poem.rs b/src/poem.rs index a063a14..db57e55 100644 --- a/src/poem.rs +++ b/src/poem.rs @@ -67,10 +67,17 @@ mod tests { assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); - assert_eq!(verse.io, Rune::Read); + assert!(verse.io.contains(&Rune::Read)); assert_eq!( verse.stanza, - vec!["lolcat", "<", "src/main.rs"] + vec!["lolcat"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.ip, + vec!["src/main.rs"] .iter() .map(|s| s.to_string()) .collect::<Vec<String>>() @@ -86,10 +93,76 @@ mod tests { assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); - assert_eq!(verse.io, Rune::Write); + assert!(verse.io.contains(&Rune::Write)); assert_eq!( verse.stanza, - vec!["cat", "src/main.rs", "<", "/dev/null"] + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.op, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + } + + #[test] + fn it_parses_a_verse_with_the_write2_rune() { + let poem = Poem::read( + "cat src/main.rs 2> /dev/null".to_string(), + &Environment::new(), + ); + assert!(poem.is_ok()); + let mut verses = poem.unwrap().into_iter(); + let verse = verses.next().unwrap(); + assert!(verse.io.contains(&Rune::Write2)); + assert_eq!( + verse.stanza, + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.ep, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + } + + #[test] + fn it_parses_a_verse_with_the_write_all_rune() { + let poem = Poem::read( + "cat src/main.rs &> /dev/null".to_string(), + &Environment::new(), + ); + assert!(poem.is_ok()); + let mut verses = poem.unwrap().into_iter(); + let verse = verses.next().unwrap(); + assert!(verse.io.contains(&Rune::WriteAll)); + assert_eq!( + verse.stanza, + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.op, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.ep, + vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::<Vec<String>>() @@ -105,10 +178,76 @@ mod tests { assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); - assert_eq!(verse.io, Rune::Addendum); + assert!(verse.io.contains(&Rune::Addendum)); + assert_eq!( + verse.stanza, + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.op, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + } + + #[test] + fn it_parses_a_verse_with_the_addendum2_rune() { + let poem = Poem::read( + "cat src/main.rs 2>> /dev/null".to_string(), + &Environment::new(), + ); + assert!(poem.is_ok()); + let mut verses = poem.unwrap().into_iter(); + let verse = verses.next().unwrap(); + assert!(verse.io.contains(&Rune::Addendum2)); + assert_eq!( + verse.stanza, + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.ep, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + } + + #[test] + fn it_parses_a_verse_with_the_addendum_all_rune() { + let poem = Poem::read( + "cat src/main.rs &>> /dev/null".to_string(), + &Environment::new(), + ); + assert!(poem.is_ok()); + let mut verses = poem.unwrap().into_iter(); + let verse = verses.next().unwrap(); + assert!(verse.io.contains(&Rune::AddendumAll)); assert_eq!( verse.stanza, - vec!["cat", "src/main.rs", "<", "/dev/null"] + vec!["cat", "src/main.rs"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.op, + vec!["/dev/null"] + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + ); + assert_eq!( + verse.ep, + vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::<Vec<String>>() diff --git a/src/poem/anthology/alias.rs b/src/poem/anthology/alias.rs index 511bd3a..45ee7f2 100644 --- a/src/poem/anthology/alias.rs +++ b/src/poem/anthology/alias.rs @@ -40,7 +40,7 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, aliases: &mut HashMap<String, St lines.push(line); } - if verse.couplet { + if verse.couplet > 0 { *out = format!("{}\n", lines.join("\n")).as_bytes().to_vec(); } else { println!("{}", lines.join("\n")); diff --git a/src/poem/anthology/source.rs b/src/poem/anthology/source.rs index 43d6204..3c81110 100644 --- a/src/poem/anthology/source.rs +++ b/src/poem/anthology/source.rs @@ -42,9 +42,7 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &mut Environment) -> i32 { } }; - let stdout = if verse.couplet { Some(true) } else { None }; - - *out = match poem.recite(env, stdout) { + *out = match poem.recite(env) { Ok(out) => out, Err(e) => { eprintln!("dwvsh: {}", e.to_string().to_lowercase()); diff --git a/src/poem/elements/rune.rs b/src/poem/elements/rune.rs index 2307519..1322ca5 100644 --- a/src/poem/elements/rune.rs +++ b/src/poem/elements/rune.rs @@ -1,14 +1,4 @@ -use super::verse::Verse; -use crate::iobtask; -use crate::{btask, ctask, task}; use core::fmt; -use libc::waitpid; -use libc::WNOHANG; -use std::fs::OpenOptions; -use std::io::{self, Read, Write}; -use std::os::unix::process::CommandExt; -use std::process::{Command, Stdio}; -use std::sync::{Arc, Mutex}; /// Describes one or two characters from the input /// @@ -40,21 +30,25 @@ use std::sync::{Arc, Mutex}; /// * `Else` - Any other character #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Rune { - None, // No meter (the end of a poem) - Pause, // A space - Path, // A forward slash - Remark, // A comment - String, // Interpret the following as one large [Word] - Poem, // Run a sub-poem before the main one - Read, // Read files into STDIN - Write, // Send STDOUT to a file - Addendum, // Append STDOUT to a file - 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 - Continue, // Run the next command, even if this doesn't succeed - Home, // Interpret '~' as $HOME - Else, // Any other character + None, // No meter (the end of a poem) + Pause, // A space + Path, // A forward slash + Remark, // A comment + String, // Interpret the following as one large [Word] + Poem, // Run a sub-poem before the main one + Read, // Read files into STDIN + Write, // Send STDOUT to a file + Write2, // Send STDERR to a file + WriteAll, // Send STDOUT and STDERR to a file + Addendum, // Append STDOUT to a file + Addendum2, // Append STDERR to a file + AddendumAll, // Append STDOUT and STDERR to a file + 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 + Continue, // Run the next command, even if this doesn't succeed + Home, // Interpret '~' as $HOME + Else, // Any other character } impl fmt::Display for Rune { @@ -71,7 +65,11 @@ impl fmt::Display for Rune { Rune::Poem => "`", Rune::Read => "<", Rune::Write => ">", + Rune::Write2 => "2>", + Rune::WriteAll => "&>", Rune::Addendum => ">>", + Rune::Addendum2 => "2>>", + Rune::AddendumAll => "&>>", Rune::Couplet => "|", Rune::Quiet => "&", Rune::And => "&&", @@ -83,294 +81,3 @@ impl fmt::Display for Rune { write!(f, "{}", rune) } } - -impl Rune { - /// Recite a verse with [Rune::None] - /// - /// Call this function on a [Verse] with a meter of type [Rune::None]. - /// This forks into a child process, calls the `verb()` (i.e. program) - /// that was specified in the [Verse], then waits for that program to - /// complete. If the last [Verse] piped its contents into `out`, it will - /// be piped into the STDIN of this [Verse]. If all Rust code is called - /// successfully, return the exit code of the process. Otherwise, return a - /// [std::io::Error]. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `out: &mut String` - A string that may have output from the last command - pub fn incant_none(verse: &Verse, out: &mut Vec<u8>) -> Result<i32, io::Error> { - let child = task!(verse, out); - - let output = child.wait_with_output()?; - - if !output.status.success() { - return Ok(output.status.code().unwrap_or(-1)); - } - - Ok(output.status.code().unwrap_or(0)) - } - - /// Recite a verse with [Rune::Couplet] - /// - /// Call this function on a [Verse] with a meter of type [Rune::Couplet]. - /// This forks into a child process, calls the `verb` (i.e. program) - /// that was specified in the [Verse], then waits for that program to - /// complete. If the last [Verse] piped its contents into `out`, it will - /// be piped into the STDIN of this [Verse]. Then, the contents of this - /// processes' STDOUT are stored in `out`. If all Rust code is called - /// successfully, return the exit code of the process. Otherwise, return a - /// [std::io::Error]. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `out: &mut String` - A string that may have output from the last command - pub fn incant_couplet(verse: &Verse, out: &mut Vec<u8>) -> Result<i32, io::Error> { - let child = ctask!(verse, out); - - let mut output = child.wait_with_output()?; - - if !output.status.success() { - return Ok(output.status.code().unwrap_or(-1)); - } - - out.append(&mut output.stdout); - - Ok(output.status.code().unwrap_or(0)) - } - - /// Recite a verse with [Rune::Quiet] - /// - /// Call this function on a [Verse] with a meter of type [Rune::Quiet]. - /// This forks a child process into the background. It then registers a - /// `SIGCHLD` handler, making sure to do so for each PID in the `pids` - /// Vec. If the last [Verse] piped its contents into `out`, it will be - /// piped into the STDIN of this [Verse]. If all Rust code is called - /// successfully, return the exit code of the process. Otherwise, return a - /// [std::io::Error]. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `out: &mut String` - A string that may have output from the last command - /// * `pids: Arc<Mutex<Vec<i32>>>` - A vector that stores the PIDs of all background processes that belong to the shell - pub fn incant_quiet( - verse: &Verse, - out: &mut Vec<u8>, - pids: &mut Arc<Mutex<Vec<i32>>>, - ) -> Result<i32, io::Error> { - let child = btask!(verse, out); - println!("[&] {}", child.id()); - - pids.lock().unwrap().push(child.id() as i32); - let stanza = verse.stanza.join(" ").to_string(); - let pids = Arc::clone(pids); - - unsafe { - signal_hook::low_level::register(signal_hook::consts::SIGCHLD, move || { - for pid in pids.lock().unwrap().iter() { - let mut pid = *pid; - let mut status: i32 = 0; - pid = waitpid(pid, &mut status, WNOHANG); - if pid > 0 { - print!("\n[&] + done {}", stanza); - io::stdout().flush().unwrap(); - } - } - }) - .unwrap(); - } - - Ok(0) - } - - /// Alias to [Rune::incant_none] - pub fn incant_and(verse: &Verse, out: &mut Vec<u8>) -> Result<i32, io::Error> { - Rune::incant_none(verse, out) - } - - /// Alias to [Rune::incant_none] - pub fn incant_continue(verse: &Verse, out: &mut Vec<u8>) -> Result<i32, io::Error> { - Rune::incant_none(verse, out) - } - - /// Recite a verse with [Rune::Read] - /// - /// Call this function on a [Verse] with a meter of type [Rune::Read]. - /// This reads the specified files into `out`, then makes a call to - /// [Rune::incant_none] with all the contents of `out`. Anything piped to - /// this command will appear in `out` first, and any subsequent files will - /// be appended. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `paths: &Verse` - The next verse (i.e. the file paths) - /// * `out: &mut String` - A string that may have output from the last command, - /// and that will be used to store the contents of the - /// file paths in `next` - pub fn incant_read( - verse: &mut Verse, - out: &mut Vec<u8>, - pids: &mut Arc<Mutex<Vec<i32>>>, - ) -> Result<i32, io::Error> { - // Split the verse from the paths - let paths = verse.split("<"); - - // Read all file specified in the next verse into 'out', since there - // may also be piped output from the last command - for path in paths.iter() { - let mut file = OpenOptions::new().read(true).open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - out.append(&mut contents.as_bytes().to_vec()); - } - - // Alias incant_<meter> - match verse.meter { - Rune::None => Rune::incant_none(&verse, out), - Rune::Couplet => Rune::incant_couplet(&verse, out), - Rune::Quiet => Rune::incant_quiet(&verse, out, pids), - Rune::And => Rune::incant_and(&verse, out), - Rune::Continue => Rune::incant_continue(&verse, out), - _ => unreachable!(), - } - } - - /// Recite a verse with [Rune::Write] - /// - /// Call this function on a [Verse] with a meter of type [Rune::Write]. - /// This writes the output of the verse into the specified files, after - /// making a call to [Rune::incant_couplet]. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `paths: &Verse` - The next verse (i.e. the file paths) - /// * `out: &mut String` - A string that may have output from the last command, - /// and that will be used to store the contents of the - /// file paths in `next` - pub fn incant_write( - verse: &mut Verse, - out: &mut Vec<u8>, - pids: &mut Arc<Mutex<Vec<i32>>>, - ) -> Result<i32, io::Error> { - // Split the verse from the paths - let mut paths = Arc::new(Mutex::new(verse.split("<"))); - - // Alias incant_<meter> - // let status = Rune::incant_couplet(&verse, out)?; - let status = match verse.meter { - Rune::None => Rune::incant_couplet(&verse, out)?, - Rune::Couplet => Rune::incant_couplet(&verse, out)?, - Rune::Quiet => Rune::incant_quiet_io(&verse, out, pids, &mut paths)?, - Rune::And => Rune::incant_couplet(&verse, out)?, - Rune::Continue => Rune::incant_couplet(&verse, out)?, - _ => unreachable!(), - }; - - // Write output to each file specified in the next verse - for path in paths.lock().unwrap().iter() { - let mut file = OpenOptions::new().create(true).write(true).open(path)?; - file.write(out)?; - } - - // Clear out - out.clear(); - - // Return the exit status - Ok(status) - } - - /// Recite a verse with [Rune::Addendum] - /// - /// Same as [Rune::Write], except it appends to the file(s) specified, - /// instead of overwriting them. - /// - /// # Arguments - /// * `verse: &Verse` - The verse to recite - /// * `paths: &Verse` - The next verse (i.e. the file paths) - /// * `out: &mut String` - A string that may have output from the last command, - /// and that will be used to store the contents of the - /// file paths in `next` - pub fn incant_addendum( - verse: &mut Verse, - out: &mut Vec<u8>, - pids: &mut Arc<Mutex<Vec<i32>>>, - ) -> Result<i32, io::Error> { - // Split the verse from the paths - let mut paths = Arc::new(Mutex::new(verse.split("<"))); - - // Alias incant_<meter> - // let status = Rune::incant_couplet(&verse, out)?; - let status = match verse.meter { - Rune::None => Rune::incant_couplet(&verse, out)?, - Rune::Couplet => Rune::incant_couplet(&verse, out)?, - Rune::Quiet => Rune::incant_quiet_io(&verse, out, pids, &mut paths)?, - Rune::And => Rune::incant_couplet(&verse, out)?, - Rune::Continue => Rune::incant_couplet(&verse, out)?, - _ => unreachable!(), - }; - - // Write output to each file specified in the next verse - for path in paths.lock().unwrap().iter() { - let mut file = OpenOptions::new().create(true).append(true).open(path)?; - file.write(out)?; - } - - // Clear out - out.clear(); - - // Return the exit status - Ok(status) - } - - /// Same as incant_quiet, except capture STDOUT into `out` - pub fn incant_quiet_io( - verse: &Verse, - out: &mut Vec<u8>, - pids: &mut Arc<Mutex<Vec<i32>>>, - paths: &mut Arc<Mutex<Vec<String>>>, - ) -> Result<i32, io::Error> { - let child = Arc::new(Mutex::new(iobtask!(verse, out))); - println!("[&] {}", child.lock().unwrap().id()); - - pids.lock().unwrap().push(child.lock().unwrap().id() as i32); - let stanza = verse.stanza.join(" ").to_string(); - let pids = Arc::clone(pids); - let paths = Arc::clone(paths); - let io = Arc::new(verse.io); - - unsafe { - signal_hook::low_level::register(signal_hook::consts::SIGCHLD, move || { - for pid in pids.lock().unwrap().iter() { - let mut pid = *pid; - let mut status: i32 = 0; - pid = waitpid(pid, &mut status, WNOHANG); - if pid > 0 { - print!("\n[&] + done {}", stanza); - let mut bytes: Vec<u8> = Vec::new(); - let mut child = child.lock().unwrap(); - child - .stdout - .as_mut() - .unwrap() - .read_to_end(&mut bytes) - .unwrap(); - for path in paths.lock().unwrap().iter() { - let file = if io == Rune::Write.into() { - OpenOptions::new().create(true).write(true).open(path) - } else if io == Rune::Addendum.into() { - OpenOptions::new().create(true).append(true).open(path) - } else { - unreachable!(); - }; - - let _ = file.unwrap().write(&bytes); - } - io::stdout().flush().unwrap(); - } - } - }) - .unwrap(); - } - - Ok(0) - } -} diff --git a/src/poem/elements/verse.rs b/src/poem/elements/verse.rs index e857676..307ebc8 100644 --- a/src/poem/elements/verse.rs +++ b/src/poem/elements/verse.rs @@ -2,7 +2,13 @@ use super::rune::Rune; use super::stanza::Stanza; use super::word::Word; use crate::poem::Poem; +use libc::{waitpid, WNOHANG}; +use std::fs::OpenOptions; +use std::io::{self, Read, Write}; +use std::os::unix::process::CommandExt; use std::path::Path; +use std::process::{Command, Output, Stdio}; +use std::sync::{Arc, Mutex}; /// A [Stanza] and it's [meter](Rune) /// @@ -12,8 +18,11 @@ use std::path::Path; #[derive(Debug, Clone)] pub struct Verse { pub stanza: Stanza, - pub couplet: bool, - pub io: Rune, + pub couplet: u8, + pub io: Vec<Rune>, + pub ip: Stanza, + pub op: Stanza, + pub ep: Stanza, pub poems: Vec<Poem>, pub meter: Rune, } @@ -26,8 +35,11 @@ impl Verse { pub fn new() -> Self { Verse { stanza: Stanza::new(), - couplet: false, - io: Rune::None, + couplet: 0, + io: Vec::new(), + ip: Stanza::new(), + op: Stanza::new(), + ep: Stanza::new(), poems: Vec::new(), meter: Rune::None, } @@ -64,6 +76,8 @@ impl Verse { /// Alias to [Verse].stanza.clear() pub fn clear(&mut self) { self.stanza.clear(); + self.io.clear(); + self.poems.clear(); } /// Check if the [Verse] contains any internal poems @@ -79,25 +93,26 @@ impl Verse { /// Push a word to the [Stanza] after performing a few extra checks, such /// as whether or not the word is empty, or if the word should be /// interpreted as an environment variable. - pub fn add(&mut self, word: &mut Word) { - if !word.is_empty() { - // Push the word, and clear the stack - self.push(word.iter().collect()); - word.clear(); + pub fn add(&mut self, word: &mut Word, channel: Option<Rune>) { + // Do nothing if the stack is empty + if word.is_empty() { + return; } - } - /// Split a [Verse] into two different [Verse]s - /// - /// This is useful for [Rune::Read], [Rune::Write], and [Rune::Addendum]. - pub fn split(&mut self, c: &str) -> Vec<String> { - for (i, s) in self.stanza.iter().enumerate() { - if *s == c { - let split = self.stanza.split_off(i); - return split[1..].to_vec(); + // Push the word + match channel { + Some(Rune::Read) => self.ip.push(word.iter().collect()), + Some(Rune::Write) | Some(Rune::Addendum) => self.op.push(word.iter().collect()), + Some(Rune::Write2) | Some(Rune::Addendum2) => self.ep.push(word.iter().collect()), + Some(Rune::WriteAll) | Some(Rune::AddendumAll) => { + self.op.push(word.iter().collect()); + self.ep.push(word.iter().collect()); } + Some(_) | None => self.push(word.iter().collect()), } - vec![] + + // Clear the stack + word.clear(); } /// Check if the `verb()` exists in the `$PATH` @@ -156,4 +171,236 @@ impl Verse { // Return true if the full path or relative path exists true } + + pub fn incant( + &mut self, + out: &mut Vec<u8>, + pids: &mut Arc<Mutex<Vec<i32>>>, + ) -> Result<i32, io::Error> { + // Read files into 'out' if Rune::Read is present in the verse's IO + if self.io.contains(&Rune::Read) { + // Enable piping on stdin + self.couplet += 2; + + // Read all files specified after '<' into 'out', since there may + // also be piped output from the last command + for path in self.ip.iter() { + let mut file = OpenOptions::new().read(true).open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + out.append(&mut contents.as_bytes().to_vec()); + } + } + + // Build the command + let mut command = Command::new(self.verb()); + command.args(self.clause().unwrap_or(vec![])); + + // Determine couplet status + if self.couplet == 1 { + // Verse is the left half of a couplet + command.stdout(Stdio::piped()); + } else if self.couplet == 2 { + // Verse is the right half of a couplet + command.stdin(Stdio::piped()); + } else if self.couplet == 3 { + // Verse is taking in and piping out output + command.stdout(Stdio::piped()); + command.stdin(Stdio::piped()); + } + + // Setup for other IO + if self.io.contains(&Rune::Write) || self.io.contains(&Rune::Addendum) { + command.stdout(Stdio::piped()); + } + if self.io.contains(&Rune::Write2) || self.io.contains(&Rune::Addendum2) { + command.stderr(Stdio::piped()); + } + if self.io.contains(&Rune::WriteAll) || self.io.contains(&Rune::AddendumAll) { + command.stdout(Stdio::piped()); + command.stderr(Stdio::piped()); + } + + // Detach the process group, if in the [Rune::Quiet] meter + if self.meter == Rune::Quiet { + command.process_group(0); + } + + // Spawn the process + let mut child = command.spawn()?; + + // Pipe in command, if we're the right side of a couplet + if self.couplet > 1 { + let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?; + stdin.write_all(&out)?; + out.clear(); + } + + // Determine what to do based on the meter + let mut output: Output; + let mut err: Vec<u8> = Vec::new(); + match self.meter { + Rune::None | Rune::And | Rune::Continue => { + output = child.wait_with_output()?; + if self.io.contains(&Rune::Write) || self.io.contains(&Rune::Addendum) { + out.append(&mut output.stdout); + } + if self.io.contains(&Rune::Write2) || self.io.contains(&Rune::Addendum2) { + err.append(&mut output.stderr); + } + if self.io.contains(&Rune::WriteAll) || self.io.contains(&Rune::AddendumAll) { + out.append(&mut output.stdout); + err.append(&mut output.stderr); + } + } + Rune::Couplet => { + output = child.wait_with_output()?; + out.append(&mut output.stdout); + if self.io.contains(&Rune::Write2) || self.io.contains(&Rune::Addendum2) { + err.append(&mut output.stderr); + } + if self.io.contains(&Rune::WriteAll) || self.io.contains(&Rune::AddendumAll) { + err.append(&mut output.stderr); + } + } + Rune::Quiet => { + println!("[&] {}", child.id()); + + pids.lock().unwrap().push(child.id() as i32); + let stanza = self.stanza.join(" ").to_string(); + let pids = Arc::clone(pids); + + unsafe { + signal_hook::low_level::register(signal_hook::consts::SIGCHLD, move || { + for pid in pids.lock().unwrap().iter() { + let mut pid = *pid; + let mut status: i32 = 0; + pid = waitpid(pid, &mut status, WNOHANG); + if pid > 0 { + print!("\n[&] + done {}", stanza); + io::stdout().flush().unwrap(); + } + } + }) + .unwrap(); + } + + return Ok(0); + } + _ => unreachable!(), + } + + // Perform IO operations + let mut oi = 0; + let mut ei = 0; + self.io.retain(|rune| *rune != Rune::Read); + for io in self.io.iter() { + let (f, f2) = match *io { + Rune::Write => { + oi += 1; + ( + Some( + OpenOptions::new() + .create(true) + .write(true) + .open(&self.op[oi - 1])?, + ), + None, + ) + } + Rune::Write2 => { + ei += 1; + ( + None, + Some( + OpenOptions::new() + .create(true) + .write(true) + .open(&self.ep[ei - 1])?, + ), + ) + } + Rune::WriteAll => { + oi += 1; + ei += 1; + ( + Some( + OpenOptions::new() + .create(true) + .write(true) + .open(&self.op[oi - 1])?, + ), + Some( + OpenOptions::new() + .create(true) + .write(true) + .open(&self.ep[ei - 1])?, + ), + ) + } + Rune::Addendum => { + oi += 1; + ( + Some( + OpenOptions::new() + .create(true) + .append(true) + .open(&self.op[oi - 1])?, + ), + None, + ) + } + Rune::Addendum2 => { + ei += 1; + ( + None, + Some( + OpenOptions::new() + .create(true) + .append(true) + .open(&self.ep[ei - 1])?, + ), + ) + } + Rune::AddendumAll => { + oi += 1; + ei += 1; + ( + Some( + OpenOptions::new() + .create(true) + .append(true) + .open(&self.op[oi - 1])?, + ), + Some( + OpenOptions::new() + .create(true) + .append(true) + .open(&self.ep[ei - 1])?, + ), + ) + } + _ => unreachable!(), + }; + + match f { + Some(mut file) => file.write(out)?, + None => 0, + }; + + match f2 { + Some(mut file) => file.write(&err)?, + None => 0, + }; + } + + if !output.status.success() { + return Ok(output.status.code().unwrap_or(-1)); + } + + out.clear(); + err.clear(); + + Ok(output.status.code().unwrap_or(0)) + } } diff --git a/src/poem/read.rs b/src/poem/read.rs index 35ca2e1..da13f76 100644 --- a/src/poem/read.rs +++ b/src/poem/read.rs @@ -42,13 +42,8 @@ impl fmt::Display for Mishap { /// A [Poem] can add more [Verse]s to itself trait Appendable { type Type; - fn add( - &mut self, - verse: &mut Self::Type, - meter: Rune, - last: Rune, - env: &Environment, - ) -> Result<(), Mishap>; + fn add(&mut self, verse: &mut Self::Type, meter: Rune, env: &Environment) + -> Result<(), Mishap>; } impl Appendable for Poem { @@ -61,7 +56,6 @@ impl Appendable for Poem { fn add( &mut self, verse: &mut Self::Type, - last: Rune, meter: Rune, env: &Environment, ) -> Result<(), Mishap> { @@ -69,10 +63,20 @@ impl Appendable for Poem { return Ok(()); } + // Get meter of the last verse + let last = match self.last() { + Some(last) => last.meter, + None => Rune::Else, + }; + // Check the meter verse.meter = meter; - if last == Rune::Couplet || meter == Rune::Couplet { - verse.couplet = true; + if last == Rune::Couplet && meter == Rune::Couplet { + verse.couplet = 3; + } else if last == Rune::Couplet { + verse.couplet = 2; + } else if meter == Rune::Couplet { + verse.couplet = 1; } // Check for aliases @@ -88,10 +92,10 @@ impl Appendable for Poem { }; // The last verse inherits the traits from the original - if verse.couplet { + if verse.couplet > 0 { lv.couplet = verse.couplet; } - lv.io = verse.io; + lv.io = verse.io.clone(); lv.poems = verse.poems.clone(); lv.meter = verse.meter; if verse.clause().is_some() { @@ -147,6 +151,9 @@ impl Readable for Poem { // Keep track of the last rune let mut last = Rune::None; + // Keep track of the channel + let mut channel: Option<Rune> = None; + // Keep track of the line let mut j = 0; @@ -167,7 +174,7 @@ impl Readable for Poem { // If c is none, it indicates the end of a poem, so wrap up and // then break from the loop - verse.add(&mut word); + verse.add(&mut word, channel); // Throw an error if the verse is empty if verse.is_empty() && (last == Rune::Couplet || last == Rune::And) { @@ -175,7 +182,7 @@ impl Readable for Poem { } // Push the verse and break - poem.add(&mut verse, last, Rune::None, env)?; + poem.add(&mut verse, Rune::None, env)?; // append!(poem, last, Rune::None, verse, env); break; } @@ -188,13 +195,31 @@ impl Readable for Poem { '#' => Rune::Remark, '\'' | '"' => Rune::String, '`' => Rune::Poem, - '<' => { - verse.couplet = true; - Rune::Read - } - '>' => next(&mut chars, &mut i, Rune::Write, vec![('>', Rune::Addendum)]), + '<' => Rune::Read, + '>' => next(&mut chars, &mut i, Rune::Write, vec![(">", Rune::Addendum)]), + '1' => next( + &mut chars, + &mut i, + Rune::Else, + vec![(">", Rune::Write), (">>", Rune::Addendum)], + ), + '2' => next( + &mut chars, + &mut i, + Rune::Else, + vec![(">", Rune::Write2), (">>", Rune::Addendum2)], + ), '|' => Rune::Couplet, - '&' => next(&mut chars, &mut i, Rune::Quiet, vec![('&', Rune::And)]), + '&' => next( + &mut chars, + &mut i, + Rune::Quiet, + vec![ + ("&", Rune::And), + (">", Rune::WriteAll), + (">>", Rune::AddendumAll), + ], + ), ';' => Rune::Continue, '\n' => { j += 1; @@ -212,13 +237,21 @@ impl Readable for Poem { | Rune::And | Rune::Read | Rune::Write - | Rune::Addendum => { + | Rune::Write2 + | Rune::WriteAll + | Rune::Addendum + | Rune::Addendum2 + | Rune::AddendumAll => { if (last == Rune::Couplet || last == Rune::Quiet || last == Rune::And || last == Rune::Read || last == Rune::Write - || last == Rune::Addendum) + || last == Rune::Write2 + || last == Rune::WriteAll + || last == Rune::Addendum + || last == Rune::Addendum2 + || last == Rune::AddendumAll) || verse.is_empty() { return Err(Mishap::ParseMishap(j, i, c)); @@ -226,17 +259,34 @@ impl Readable for Poem { } Rune::Continue => { - if last == Rune::Read || last == Rune::Write || last == Rune::Addendum { + if last == Rune::Read + || last == Rune::Write + || last == Rune::Write2 + || last == Rune::WriteAll + || last == Rune::Addendum + || last == Rune::Addendum2 + || last == Rune::AddendumAll + { return Err(Mishap::ParseMishap(j, i, c)); } } _ => { - if (last == Rune::Read || last == Rune::Write || last == Rune::Addendum) + if (last == Rune::Read + || last == Rune::Write + || last == Rune::Write2 + || last == Rune::WriteAll + || last == Rune::Addendum + || last == Rune::Addendum2 + || last == Rune::AddendumAll) && rune == Rune::None && rune == Rune::Read && rune == Rune::Write + && rune == Rune::Write2 + && rune == Rune::WriteAll && rune == Rune::Addendum + && rune == Rune::Addendum2 + && rune == Rune::AddendumAll && rune == Rune::Couplet && rune == Rune::Quiet && rune == Rune::And @@ -251,7 +301,7 @@ impl Readable for Poem { match rune { // Indicates the end of a word (space dilineated) Rune::Pause => { - verse.add(&mut word); + verse.add(&mut word, channel); } Rune::Remark => { @@ -261,7 +311,7 @@ impl Readable for Poem { // Indicates a string (' or ") Rune::String => { string!(chars, j, i, c, word); - verse.add(&mut word); + verse.add(&mut word, channel); } // Indicates a sub-poem @@ -270,17 +320,23 @@ impl Readable for Poem { } // Indicates a file operation (<, >, or >>) - Rune::Read | Rune::Write | Rune::Addendum => { - verse.add(&mut word); - word.push('<'); - verse.add(&mut word); - verse.io = rune; + Rune::Read + | Rune::Write + | Rune::Write2 + | Rune::WriteAll + | Rune::Addendum + | Rune::Addendum2 + | Rune::AddendumAll => { + verse.add(&mut word, channel); + channel = Some(rune); + verse.io.push(rune); } // These meters indicate the end of a verse Rune::Couplet | Rune::Quiet | Rune::And | Rune::Continue => { - verse.add(&mut word); - poem.add(&mut verse, last, rune, env)?; + channel = None; + verse.add(&mut word, channel); + poem.add(&mut verse, rune, env)?; // append!(poem, last, rune, verse, env); } @@ -290,7 +346,7 @@ impl Readable for Poem { word.append(&mut chars); } - // Any other char i.e. Meter::Else + // Any other char i.e. Rune::Else _ => { word.push(c); } diff --git a/src/poem/read/parse.rs b/src/poem/read/parse.rs index 27c4728..fc1979f 100644 --- a/src/poem/read/parse.rs +++ b/src/poem/read/parse.rs @@ -15,29 +15,45 @@ use std::str::Chars; /// /// # Examples: /// ``` -/// next(&mut chars, &mut i, Rune::Write, vec![('>', Rune::Addendum)]) -/// next(&mut chars, &mut i, Rune::Quiet, vec![('&', Rune::And)]) +/// next(&mut chars, &mut i, Rune::Write, vec![(">", Rune::Addendum)]) +/// next(&mut chars, &mut i, Rune::Quiet, vec![("&", Rune::And)]) /// ``` -pub fn next(chars: &mut Chars, i: &mut usize, otherwise: Rune, ahead: Vec<(char, Rune)>) -> Rune { - // Try to get the next character in the poem - let next = match chars.peekable().peek() { - Some(c) => *c, - None => { - return otherwise; - } - }; +pub fn next(chars: &mut Chars, i: &mut usize, otherwise: Rune, ahead: Vec<(&str, Rune)>) -> Rune { + // Initialize rune (the return value) with the default + let mut rune = otherwise; + + // We need to peek the iterator + let mut peekable = chars.clone().peekable(); + + // Help keep track of matched characters + let mut j = 0; - // Check if that next character matches any characters in ahead - for (c, rune) in ahead.iter() { - if next == *c { - chars.next(); - *i += 1; - return *rune; + // For each tuple pair (string, rune)... + for (s, r) in ahead.iter() { + // For each character in in the string, starting at length j... + for c in s[j..].chars().into_iter() { + // If the next char matches... + match peekable.peek() { + Some(next) if next == &c => { + // Increment counters + chars.next(); + peekable.next(); + *i += 1; + j += 1; + + // Only update the rune if j equals the length of the string + if j == s.len() { + rune = *r; + } + } + Some(_) => {} + None => {} + } } } - // If it doesn't match, return the default - otherwise + // Return whatever the rune was determined to be + rune } /// Keep pushing to the [Word][super::super::elements::word::Word] stack diff --git a/src/poem/recite.rs b/src/poem/recite.rs index 60b7857..8b2fd1d 100644 --- a/src/poem/recite.rs +++ b/src/poem/recite.rs @@ -1,4 +1,3 @@ -mod ps; use super::Poem; use crate::compose::Environment; use crate::path; @@ -12,14 +11,11 @@ use std::{ }; pub trait Reciteable { - fn recite(&self, env: &mut Environment, stdout: Option<bool>) -> Result<Vec<u8>, io::Error>; + fn recite(&self, env: &mut Environment) -> Result<Vec<u8>, io::Error>; } impl Reciteable for Poem { - fn recite(&self, env: &mut Environment, stdout: Option<bool>) -> Result<Vec<u8>, io::Error> { - // Should we print to stdout or always capture it - let stdout = stdout.unwrap_or(true); - + fn recite(&self, env: &mut Environment) -> Result<Vec<u8>, io::Error> { // Variable for storing the output of a piped verse let mut out: Vec<u8> = Vec::new(); @@ -94,7 +90,7 @@ impl Reciteable for Poem { Some(poem) => poem, None => break, // TODO: Return an error }; - let mut out = poem.recite(env, Some(false))?; + let mut out = poem.recite(env)?; match out.last() { Some(last) => { if *last == b'\n' { @@ -165,29 +161,7 @@ impl Reciteable for Poem { continue; } - // Incant the verse, based on its meter - if stdout { - match verse.io { - Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, - Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, - Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, - _ => match verse.meter { - Rune::None => Rune::incant_none(&verse, &mut out)?, - Rune::Couplet => Rune::incant_couplet(&verse, &mut out)?, - Rune::Quiet => Rune::incant_quiet(&verse, &mut out, &mut pids)?, - Rune::And => Rune::incant_and(&verse, &mut out)?, - Rune::Continue => Rune::incant_continue(&verse, &mut out)?, - _ => unreachable!(), - }, - } - } else { - match verse.io { - Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, - Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, - Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, - _ => Rune::incant_couplet(&verse, &mut out)?, - } - } + verse.incant(&mut out, &mut pids)? }; // Break from the loop if the meter is not [Rune::Continue], and diff --git a/src/poem/recite/ps.rs b/src/poem/recite/ps.rs deleted file mode 100644 index 5700ae8..0000000 --- a/src/poem/recite/ps.rs +++ /dev/null @@ -1,134 +0,0 @@ -/// Fork into a process from a Verse -/// -/// Figures out whether or not the given Verse is a couplet. If it is, fork -/// into a process, and pipe the contents of out `out` into STDIN. If not, then -/// simply fork into the process. -/// -/// # Arguments -/// * `$verse: &Verse` - The verse to fork into -/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse -#[macro_export] -macro_rules! task { - ($verse:expr, $out:expr) => { - if $verse.couplet { - let mut child = Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdin(Stdio::piped()) - .spawn()?; - - let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?; - stdin.write_all(&$out)?; - $out.clear(); - - child - } else { - Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .spawn()? - } - }; -} - -/// Fork into a process from a Verse, and capture STDOUT -/// -/// Figures out whether or not the given Verse is a couplet. If it is, fork -/// into a process, and pipe the contents of out `out` into STDIN. If not, then -/// simply fork into the process. Additionally, this function will capture -/// STDOUT of the process specified by the Verse, and store it in `out`. -/// -/// # Arguments -/// * `$verse: &Verse` - The verse to fork into -/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse -#[macro_export] -macro_rules! ctask { - ($verse:expr, $out:expr) => { - if $verse.couplet { - let mut child = Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?; - stdin.write_all(&$out)?; - $out.clear(); - - child - } else { - Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdout(Stdio::piped()) - .spawn()? - } - }; -} - -/// Fork into a background process from a Verse -/// -/// Figures out whether or not the given Verse is a couplet. If it is, fork -/// into a backgournd process, and pipe the contents of out `out` into STDIN. -/// If not, then simply fork into the background process. -/// -/// # Arguments -/// * `$verse: &Verse` - The verse to fork into -/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse -#[macro_export] -macro_rules! btask { - ($verse:expr, $out:expr) => { - if $verse.couplet { - let mut child = Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdin(Stdio::piped()) - .process_group(0) - .spawn()?; - - let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?; - stdin.write_all(&$out)?; - $out.clear(); - - child - } else { - Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .process_group(0) - .spawn()? - } - }; -} - -/// Fork into a background process from a Verse, and capture STDOUT -/// -/// Figures out whether or not the given Verse is a couplet. If it is, fork -/// into a backgournd process, and pipe the contents of out `out` into STDIN. -/// If not, then simply fork into the background process. This captures the -/// output of STDOUT, in order to redirect it to a file when the program -/// finishes running. -/// -/// # Arguments -/// * `$verse: &Verse` - The verse to fork into -/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse -#[macro_export] -macro_rules! iobtask { - ($verse:expr, $out:expr) => { - if $verse.couplet { - let mut child = Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .process_group(0) - .spawn()?; - - let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?; - stdin.write_all(&$out)?; - $out.clear(); - - child - } else { - Command::new($verse.verb()) - .args($verse.clause().unwrap_or(vec![])) - .stdout(Stdio::piped()) - .process_group(0) - .spawn()? - } - }; -} |