From c4cd1e2c165c4f34ebf67fa9350f8732b2aeca13 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Tue, 4 Jun 2024 16:25:32 -0600 Subject: Updated the way built-in commands are called/used Previously, built-in commands were fairly primitive, merely outputting STDOUT and STDERR with the print! macros. However, we need them to behave like normal programs, that is: - Acknowledge their verse's meter (forking, piping, etc.), - Ability to capture STDOUT and STDERR (>, 2>), - and Affect the currently running environment. For these reasons, the anthology was reworked, and now contains the Anthology struct, which mimics both std::process::{Child, Command}. The AnthologyStdin helper struct was also created, for built-ins to take input on STDIN, though no built-in is currently using it. Each built-ins' incant functions were updated to return a std::process::Output. It contains output from STDOUT, output from STDERR, and the exit code of the "process". A fix was also implemented for aliases, where the STDOUT and STDERR vectors were not being copied to the newly constructed verse. --- src/poem/elements/verse.rs | 242 ++++++--------------------------------- src/poem/elements/verse/logic.rs | 219 +++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 210 deletions(-) create mode 100644 src/poem/elements/verse/logic.rs (limited to 'src/poem/elements') diff --git a/src/poem/elements/verse.rs b/src/poem/elements/verse.rs index a1ae65f..42d7c6a 100644 --- a/src/poem/elements/verse.rs +++ b/src/poem/elements/verse.rs @@ -1,13 +1,18 @@ use super::rune::Rune; use super::stanza::Stanza; use super::word::Word; +use crate::poem::anthology::Anthology; use crate::poem::Poem; +mod logic; +use crate::compose::Environment; +use crate::incant; use libc::{waitpid, WNOHANG}; +use std::fmt::Debug; 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::process::{Child, Command, Output, Stdio}; use std::sync::{Arc, Mutex}; /// A [Stanza] and it's [meter](Rune) @@ -27,6 +32,22 @@ pub struct Verse { pub meter: Rune, } +pub trait Forkable { + fn fork(&mut self, env: &mut Environment) -> Result; +} + +impl Forkable for Command { + fn fork(&mut self, _env: &mut Environment) -> Result { + self.spawn() + } +} + +impl Forkable for Anthology { + fn fork(&mut self, env: &mut Environment) -> Result { + self.spawn(env) + } +} + impl Verse { /// Create a new [Verse] /// @@ -234,6 +255,8 @@ impl Verse { &mut self, out: &mut Vec, pids: &mut Arc>>, + env: &mut Environment, + anthology: Option, ) -> Result { // Read files into 'out' if Rune::Read is present in the verse's IO if self.io.contains(&Rune::Read) { @@ -250,217 +273,16 @@ impl Verse { } } - // 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 = 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); - } + // Build and run the command + match anthology { + Some(_) => { + let mut command = Anthology::new(self.verb()); + incant!(self.verb(), command, out, pids, env, self) } - 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); - } + None => { + let mut command = Command::new(self.verb()); + incant!(self.verb(), command, out, pids, env, self) } - 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)); - } - - err.clear(); - if self.meter != Rune::Couplet { - out.clear(); - } - - Ok(output.status.code().unwrap_or(0)) } } diff --git a/src/poem/elements/verse/logic.rs b/src/poem/elements/verse/logic.rs new file mode 100644 index 0000000..c1d3d62 --- /dev/null +++ b/src/poem/elements/verse/logic.rs @@ -0,0 +1,219 @@ +#[macro_export] +macro_rules! incant { + ($verb:expr, $command:expr, $out:expr, $pids:expr, $env:expr, $self:expr) => {{ + $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.fork($env)?; + + // 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 = 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) + .truncate(true) + .write(true) + .open(&$self.op[oi - 1])?, + ), + None, + ) + } + Rune::Write2 => { + ei += 1; + ( + None, + Some( + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&$self.ep[ei - 1])?, + ), + ) + } + Rune::WriteAll => { + oi += 1; + ei += 1; + ( + Some( + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&$self.op[oi - 1])?, + ), + Some( + OpenOptions::new() + .create(true) + .truncate(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)); + } + + err.clear(); + if $self.meter != Rune::Couplet { + $out.clear(); + } + + Ok(output.status.code().unwrap_or(0)) + }}; +} -- cgit v1.2.3