/// Spawn a program /// /// This macro provides a common interface for running system programs, /// as well as built-in shell commands. This is possible, since the /// shell's homebrew facilities for running built-ins mimic the /// interface of [std::process::Command]. See /// [crate::poem::anthology::Anthology] for the implementation details. /// /// First, the macro sets the arguments for the command. Next, it /// determines the whether or not text should be piped in/out of the /// command. The process is spawned, then it determines what to do based /// on the [crate::poem::elements::verse::Verse]'s meter (wait, fork to /// background, or capture STDOUT). Finally, the macro performs any IO /// functions (i.e. writing/appending STDOUT or STDERR to a file). #[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()); } // Capture stdout if (f)orce (c)apture is set if $env.fc { $command.stdout(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); } if $env.fc { $out.append(&mut output.stdout); } } 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 && $env.fc != true { $out.clear(); } Ok(output.status.code().unwrap_or(0)) }}; }