summaryrefslogtreecommitdiffstats
path: root/src/poem/elements
diff options
context:
space:
mode:
authorRory Dudley2024-05-19 18:50:06 -0600
committerRory Dudley2024-05-19 18:50:06 -0600
commit4b1b8061e79b42128df4f06fd1e439549bf9696b (patch)
tree80db43cf7295937751d61435fb4e60118b8a3ea9 /src/poem/elements
parent8756d3e7512c1416cc15a688c62b8f51f030b192 (diff)
downloaddwarvish-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.
Diffstat (limited to 'src/poem/elements')
-rw-r--r--src/poem/elements/rune.rs339
-rw-r--r--src/poem/elements/verse.rs285
2 files changed, 289 insertions, 335 deletions
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))
+ }
}