summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compose.rs2
-rw-r--r--src/main.rs2
-rw-r--r--src/poem.rs151
-rw-r--r--src/poem/anthology/alias.rs2
-rw-r--r--src/poem/anthology/source.rs4
-rw-r--r--src/poem/elements/rune.rs339
-rw-r--r--src/poem/elements/verse.rs285
-rw-r--r--src/poem/read.rs124
-rw-r--r--src/poem/read/parse.rs52
-rw-r--r--src/poem/recite.rs34
-rw-r--r--src/poem/recite/ps.rs134
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()?
- }
- };
-}