diff options
author | Rory Dudley | 2024-06-04 16:25:32 -0600 |
---|---|---|
committer | Rory Dudley | 2024-06-04 16:25:32 -0600 |
commit | c4cd1e2c165c4f34ebf67fa9350f8732b2aeca13 (patch) | |
tree | a8dd06c7563c205eb4710f620cf89d89ae17d98b | |
parent | dedadcfd30516c40692fe495a6ad10aea7c050de (diff) | |
download | dwarvish-c4cd1e2c165c4f34ebf67fa9350f8732b2aeca13.tar.gz |
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.
Notes
Notes:
There is some cleanup that needs to happen on this patch. For one, the
spellcheck function is no longer being used, so there is a generic OS
error if the program cannot be found in the $PATH. Also,
anthology::lookup gets called twice, which shouldn't need to happen.
-rw-r--r-- | src/compose/environment.rs | 1 | ||||
-rw-r--r-- | src/poem/anthology.rs | 142 | ||||
-rw-r--r-- | src/poem/anthology/alias.rs | 56 | ||||
-rw-r--r-- | src/poem/anthology/cd.rs | 53 | ||||
-rw-r--r-- | src/poem/anthology/exit.rs | 3 | ||||
-rw-r--r-- | src/poem/anthology/export.rs | 47 | ||||
-rw-r--r-- | src/poem/anthology/source.rs | 98 | ||||
-rw-r--r-- | src/poem/anthology/which.rs | 44 | ||||
-rw-r--r-- | src/poem/elements/verse.rs | 242 | ||||
-rw-r--r-- | src/poem/elements/verse/logic.rs | 219 | ||||
-rw-r--r-- | src/poem/read.rs | 2 | ||||
-rw-r--r-- | src/poem/recite.rs | 57 |
12 files changed, 650 insertions, 314 deletions
diff --git a/src/compose/environment.rs b/src/compose/environment.rs index ea6d070..18d59c4 100644 --- a/src/compose/environment.rs +++ b/src/compose/environment.rs @@ -24,6 +24,7 @@ use std::collections::HashMap; /// () /// } /// ``` +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Environment { pub aliases: HashMap<String, String>, pub bins: Vec<String>, diff --git a/src/poem/anthology.rs b/src/poem/anthology.rs index 8ff70f9..a35eafb 100644 --- a/src/poem/anthology.rs +++ b/src/poem/anthology.rs @@ -5,7 +5,8 @@ mod export; mod source; mod which; use crate::compose::Environment; -use crate::poem::Verse; +use std::io; +use std::process::{Output, Stdio}; /// A static list of all the built-in commands static INDEX: [&str; 8] = [ @@ -44,17 +45,132 @@ pub fn lookup(verb: &str) -> Option<usize> { /// ... /// } /// ``` -pub fn incant(verse: &Verse, out: &mut Vec<u8>, index: usize, env: &mut Environment) -> i32 { - let verb = INDEX[index]; - match verb { - "alias" => alias::incant(verse, out, &mut env.aliases), - "cd" => cd::incant(verse), - "exit" => exit::incant(), - "export" => export::incant(verse), - "source" => source::incant(verse, out, env), - "unalias" => alias::unincant(verse, &mut env.aliases), - "unset" => export::unincant(verse), - "which" => which::incant(verse, out, env), - _ => unreachable!(), +// pub fn incant(verse: &Verse, out: &mut Vec<u8>, index: usize, env: &mut Environment) -> i32 { +// // let verb = INDEX[index]; +// // match verb { +// // "alias" => alias::incant(verse, out, &mut env.aliases), +// // "cd" => cd::incant(verse), +// // "exit" => exit::incant(), +// // "export" => export::incant(verse), +// // "source" => source::incant(verse, out, env), +// // "unalias" => alias::unincant(verse, &mut env.aliases), +// // "unset" => export::unincant(verse), +// // "which" => which::incant(verse, out, env), +// // _ => unreachable!(), +// // } +// 0 +// } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AnthologyStdin { + data: Vec<u8>, +} + +impl AnthologyStdin { + pub fn new() -> Self { + AnthologyStdin { data: Vec::new() } + } + + pub fn as_mut(&mut self) -> Option<&mut Self> { + Some(self) + } + + pub fn write_all(&mut self, data: &[u8]) -> Result<(), io::Error> { + self.data.append(&mut data.to_vec()); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct Anthology { + verb: String, + clause: Option<Vec<String>>, + uin: bool, + uout: bool, + uerr: bool, + pub stdin: AnthologyStdin, + output: Option<Output>, +} + +impl Anthology { + /// Create a new instance of a built-in command + /// + /// Sets up a built-in command with default values. + /// + /// # Examples + /// ``` + /// let mut command = Anthology::new("alias"); + /// ``` + pub fn new(verb: String) -> Self { + Anthology { + verb, + clause: None, + uin: false, + uout: false, + uerr: false, + stdin: AnthologyStdin::new(), + output: None, + } + } + + /// Setup arguments to the built-in command + /// + /// Sets the 'clause' field of the [Anthology] struct, which are arguments + /// to be passed to the built-in command. + pub fn args(&mut self, clause: Vec<String>) { + self.clause = match clause.is_empty() { + true => None, + false => Some(clause.clone()), + }; + } + + /// Read to STDIN + /// + /// None of the built-in commands will currently read from STDIN for any + /// reason, so this is just a dummy function. + pub fn stdin(&mut self, _stdin: Stdio) { + self.uin = true; + } + + /// Capture STDOUT + pub fn stdout(&mut self, _stdout: Stdio) { + self.uout = true; + } + + /// Capture STDERR + pub fn stderr(&mut self, _stderr: Stdio) { + self.uerr = true; + } + + pub fn process_group(&mut self, _id: usize) {} + pub fn id(&mut self) -> i32 { + 0 + } + + pub fn spawn(&mut self, env: &mut Environment) -> Result<Self, io::Error> { + let index = lookup(self.verb.as_str()).unwrap(); + let verb = INDEX[index]; + + // Incant the built-in and set the output + self.output = Some(match verb { + "alias" => alias::incant(&self.clause, self.uout, &mut env.aliases), + "cd" => cd::incant(&self.clause, self.uerr), + "exit" => exit::incant(), + "export" => export::incant(&self.clause, self.uout), + "source" => source::incant(&self.clause, self.uout, self.uerr, env), + "unalias" => alias::unincant(&self.clause, self.uerr, &mut env.aliases), + "unset" => export::unincant(&self.clause, self.uerr), + "which" => which::incant(&self.clause, self.uout, self.uerr, env), + _ => unreachable!(), + }); + + Ok(self.clone()) + } + + pub fn wait_with_output(&self) -> Result<Output, io::Error> { + match &self.output { + Some(output) => Ok(output.clone()), + None => Err(io::Error::new(io::ErrorKind::Other, "not spawned")), + } } } diff --git a/src/poem/anthology/alias.rs b/src/poem/anthology/alias.rs index 45ee7f2..0746b59 100644 --- a/src/poem/anthology/alias.rs +++ b/src/poem/anthology/alias.rs @@ -1,6 +1,7 @@ -use crate::poem::Verse; use std::collections::BTreeMap; use std::collections::HashMap; +use std::os::unix::process::ExitStatusExt; +use std::process::{ExitStatus, Output}; /// alias /// @@ -11,8 +12,15 @@ use std::collections::HashMap; /// ```sh /// alias vim=nvim /// ``` -pub fn incant(verse: &Verse, out: &mut Vec<u8>, aliases: &mut HashMap<String, String>) -> i32 { - match verse.clause() { +pub fn incant( + clause: &Option<Vec<String>>, + uout: bool, + aliases: &mut HashMap<String, String>, +) -> Output { + let status = 0; + let mut out: Vec<u8> = Vec::new(); + let err: Vec<u8> = Vec::new(); + match clause { Some(clause) => { for stanza in clause { let (key, val) = match stanza.split_once("=") { @@ -24,7 +32,7 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, aliases: &mut HashMap<String, St } None => { let mut lines = Vec::new(); - let sorted: BTreeMap<_, _> = aliases.into_iter().collect(); + let sorted: BTreeMap<_, _> = aliases.iter().collect(); for (key, val) in sorted { let line = if key.contains(' ') && val.contains(' ') { format!("'{}'='{}'", key, val) @@ -40,14 +48,19 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, aliases: &mut HashMap<String, St lines.push(line); } - if verse.couplet > 0 { - *out = format!("{}\n", lines.join("\n")).as_bytes().to_vec(); + if uout { + out.append(&mut format!("{}\n", lines.join("\n")).as_bytes().to_vec()); } else { println!("{}", lines.join("\n")); } } } - 0 + + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + } } /// unalias @@ -59,17 +72,34 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, aliases: &mut HashMap<String, St /// ```sh /// unalias vim /// ``` -pub fn unincant(verse: &Verse, aliases: &mut HashMap<String, String>) -> i32 { - match verse.clause() { +pub fn unincant( + clause: &Option<Vec<String>>, + uerr: bool, + aliases: &mut HashMap<String, String>, +) -> Output { + let out: Vec<u8> = Vec::new(); + let mut err: Vec<u8> = Vec::new(); + + let status = match clause { Some(clause) => { for stanza in clause { - aliases.remove(&stanza); + aliases.remove(stanza); } + 0 } None => { - eprintln!("unalias: not enough arguments"); - return 1; + if uerr { + err.append(&mut "unalias: not enough arguments".as_bytes().to_vec()); + } else { + eprintln!("unalias: not enough arguments"); + } + 1 } + }; + + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, } - 0 } diff --git a/src/poem/anthology/cd.rs b/src/poem/anthology/cd.rs index 5b39359..bdf04f6 100644 --- a/src/poem/anthology/cd.rs +++ b/src/poem/anthology/cd.rs @@ -1,5 +1,6 @@ -use crate::poem::Verse; use std::env; +use std::os::unix::process::ExitStatusExt; +use std::process::{ExitStatus, Output}; /// cd /// @@ -12,27 +13,57 @@ use std::env; /// ```sh /// cd ~/.config # Change into /home/<user>/.config /// ``` -pub fn incant(verse: &Verse) -> i32 { - let path = match verse.clause() { +pub fn incant(clause: &Option<Vec<String>>, uerr: bool) -> Output { + let status; + let out: Vec<u8> = Vec::new(); + let mut err: Vec<u8> = Vec::new(); + let path = match clause { Some(path) => path[0].to_string(), None => match env::var("HOME") { Ok(val) => val, Err(_) => { - eprintln!("cd: unknown home, staying in pwd"); - return 1; + status = 1; + if uerr { + err.append(&mut "cd: unknown home, staying in pwd\n".as_bytes().to_vec()); + } else { + eprintln!("cd: unknown home, staying in pwd"); + } + return Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + }; } }, }; - match std::env::set_current_dir(&path) { + status = match std::env::set_current_dir(&path) { Ok(_) => 0, Err(e) => { - eprintln!( - "cd: unable to change into {}: {}", - path, - e.to_string().to_lowercase() - ); + if uerr { + err.append( + &mut format!( + "cd: unable to change into {}: {}\n", + path, + e.to_string().to_lowercase() + ) + .as_bytes() + .to_vec(), + ); + } else { + eprintln!( + "cd: unable to change into {}: {}", + path, + e.to_string().to_lowercase() + ); + } 1 } + }; + + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, } } diff --git a/src/poem/anthology/exit.rs b/src/poem/anthology/exit.rs index 46fe13c..6bbaa33 100644 --- a/src/poem/anthology/exit.rs +++ b/src/poem/anthology/exit.rs @@ -1,4 +1,5 @@ use std::process::exit; +use std::process::Output; /// exit /// @@ -7,6 +8,6 @@ use std::process::exit; /// # Aliases /// * exit /// * quit -pub fn incant() -> i32 { +pub fn incant() -> Output { exit(0); } diff --git a/src/poem/anthology/export.rs b/src/poem/anthology/export.rs index aa5a894..35a24ae 100644 --- a/src/poem/anthology/export.rs +++ b/src/poem/anthology/export.rs @@ -1,6 +1,7 @@ -use crate::poem::Verse; use std::collections::BTreeMap; use std::env; +use std::os::unix::process::ExitStatusExt; +use std::process::{ExitStatus, Output}; /// export /// @@ -15,8 +16,11 @@ use std::env; /// ```sh /// export FOO=BAR /// ``` -pub fn incant(verse: &Verse) -> i32 { - match verse.clause() { +pub fn incant(clause: &Option<Vec<String>>, uout: bool) -> Output { + let status = 0; + let mut out: Vec<u8> = Vec::new(); + let err: Vec<u8> = Vec::new(); + match clause { Some(clause) => { for stanza in clause { let (key, val) = match stanza.split_once("=") { @@ -27,14 +31,25 @@ pub fn incant(verse: &Verse) -> i32 { } } None => { + let mut lines = Vec::new(); let sorted: BTreeMap<_, _> = env::vars().into_iter().collect(); for (key, val) in sorted { - println!("{}={}", key, val); + lines.push(format!("{}={}", key, val)); + } + + if uout { + out.append(&mut format!("{}\n", lines.join("\n")).as_bytes().to_vec()); + } else { + println!("{}", lines.join("\n")); } } } - 0 + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + } } /// unset @@ -47,17 +62,29 @@ pub fn incant(verse: &Verse) -> i32 { /// ```sh /// unset FOO /// ``` -pub fn unincant(verse: &Verse) -> i32 { - match verse.clause() { +pub fn unincant(clause: &Option<Vec<String>>, uerr: bool) -> Output { + let mut status = 0; + let out: Vec<u8> = Vec::new(); + let mut err: Vec<u8> = Vec::new(); + match clause { Some(clause) => { for stanza in clause { env::remove_var(stanza); } } None => { - eprintln!("unset: not enough arguments"); - return 1; + status = 1; + if uerr { + err.append(&mut "unset: not enough arguments\n".as_bytes().to_vec()); + } else { + eprintln!("unset: not enough arguments"); + } } } - 0 + + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + } } diff --git a/src/poem/anthology/source.rs b/src/poem/anthology/source.rs index 3c81110..0ed759c 100644 --- a/src/poem/anthology/source.rs +++ b/src/poem/anthology/source.rs @@ -1,7 +1,8 @@ use crate::compose::Environment; -use crate::poem::Verse; use crate::poem::{read::Readable, recite::Reciteable, Poem}; use std::fs; +use std::os::unix::process::ExitStatusExt; +use std::process::{ExitStatus, Output}; /// source /// @@ -12,12 +13,29 @@ use std::fs; /// ```sh /// source ~/.dwvshrc /// ``` -pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &mut Environment) -> i32 { - let files = match verse.clause() { +pub fn incant( + clause: &Option<Vec<String>>, + uout: bool, + uerr: bool, + env: &mut Environment, +) -> Output { + let mut status = 0; + let mut out: Vec<u8> = Vec::new(); + let mut err: Vec<u8> = Vec::new(); + let files = match clause { Some(clause) => clause, None => { - eprintln!("source: not enough arguments"); - return 1; + status = 1; + if uerr { + err.append(&mut "source: not enough arguments\n".as_bytes().to_vec()); + } else { + eprintln!("source: not enough arguments"); + } + return Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + }; } }; @@ -25,31 +43,77 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &mut Environment) -> i32 { let poetry = match fs::read_to_string(&file) { Ok(poetry) => poetry, Err(e) => { - eprintln!( - "source: could not load {}: {}", - file, - e.to_string().to_lowercase() - ); - return 127; + status = 127; + if uerr { + err.append( + &mut format!( + "source: could not load {}: {}\n", + file, + e.to_string().to_lowercase() + ) + .as_bytes() + .to_vec(), + ); + } else { + eprintln!( + "source: could not load {}: {}", + file, + e.to_string().to_lowercase() + ); + } + return Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + }; } }; let poem = match Poem::read(poetry, env) { Ok(poem) => poem, Err(e) => { - eprintln!("dwvsh: {}", e.to_string().to_lowercase()); + if uerr { + err.append( + &mut format!("dwvsh: {}", e.to_string().to_lowercase()) + .as_bytes() + .to_vec(), + ); + } else { + eprintln!("dwvsh: {}", e.to_string().to_lowercase()); + } continue; } }; - *out = match poem.recite(env) { - Ok(out) => out, + status = match poem.recite(env) { + Ok(mut sout) => { + if uout { + out.append(&mut sout); + } else { + if !sout.is_empty() { + println!("{}", String::from_utf8_lossy(&sout)); + } + } + 0 + } Err(e) => { - eprintln!("dwvsh: {}", e.to_string().to_lowercase()); - continue; + if uerr { + err.append( + &mut format!("dwvsh: {}", e.to_string().to_lowercase()) + .as_bytes() + .to_vec(), + ); + } else { + eprintln!("dwvsh: {}", e.to_string().to_lowercase()); + } + 1 } }; } - 0 + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + } } diff --git a/src/poem/anthology/which.rs b/src/poem/anthology/which.rs index 515b52e..6fe709c 100644 --- a/src/poem/anthology/which.rs +++ b/src/poem/anthology/which.rs @@ -1,16 +1,20 @@ use crate::compose::Environment; use crate::poem::Verse; +use std::os::unix::process::ExitStatusExt; +use std::process::{ExitStatus, Output}; -pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &Environment) -> i32 { +pub fn incant(clause: &Option<Vec<String>>, uout: bool, uerr: bool, env: &Environment) -> Output { let mut status = 0; - match verse.clause() { + let mut out: Vec<u8> = Vec::new(); + let mut err: Vec<u8> = Vec::new(); + match clause { Some(clause) => { let mut output: String; for word in clause { // Check if it's an alias - if env.aliases.contains_key(&word) { - output = format!("{}: aliased to {}\n", word, env.aliases.get(&word).unwrap()); - if verse.couplet > 0 { + if env.aliases.contains_key(word) { + output = format!("{}: aliased to {}\n", word, env.aliases.get(word).unwrap()); + if uout { out.append(&mut output.as_bytes().to_vec()); } else { print!("{}", output); @@ -22,7 +26,7 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &Environment) -> i32 { match super::lookup(&word) { Some(_) => { output = format!("{}: shell built-in command\n", word); - if verse.couplet > 0 { + if uout { out.append(&mut output.as_bytes().to_vec()); } else { print!("{}", output); @@ -38,7 +42,7 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &Environment) -> i32 { match verb.spellcheck(&env.bins) { Some(i) => { output = format!("{}\n", env.bins[i]); - if verse.couplet > 0 { + if uout { out.append(&mut output.as_bytes().to_vec()); } else { print!("{}", output); @@ -47,15 +51,33 @@ pub fn incant(verse: &Verse, out: &mut Vec<u8>, env: &Environment) -> i32 { None => { output = format!("{} not found\n", word); status = 1; - eprint!("{}", output); + if uerr { + err.append(&mut output.as_bytes().to_vec()); + } else { + eprint!("{}", output); + } } } } } None => { - eprintln!("which: not enough arguments"); - return 1; + status = 1; + if uerr { + err.append(&mut "which: not enough arguments".as_bytes().to_vec()); + } else { + eprintln!("which: not enough arguments"); + } + return Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + }; } } - status + + Output { + status: ExitStatus::from_raw(status), + stdout: out, + stderr: err, + } } 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<T> { + fn fork(&mut self, env: &mut Environment) -> Result<T, io::Error>; +} + +impl Forkable<Child> for Command { + fn fork(&mut self, _env: &mut Environment) -> Result<Child, io::Error> { + self.spawn() + } +} + +impl Forkable<Anthology> for Anthology { + fn fork(&mut self, env: &mut Environment) -> Result<Anthology, io::Error> { + self.spawn(env) + } +} + impl Verse { /// Create a new [Verse] /// @@ -234,6 +255,8 @@ impl Verse { &mut self, out: &mut Vec<u8>, pids: &mut Arc<Mutex<Vec<i32>>>, + env: &mut Environment, + anthology: Option<usize>, ) -> Result<i32, io::Error> { // 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<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); - } + // 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<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) + .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)) + }}; +} diff --git a/src/poem/read.rs b/src/poem/read.rs index c6596ad..0418738 100644 --- a/src/poem/read.rs +++ b/src/poem/read.rs @@ -119,6 +119,8 @@ impl Appendable for Poem { lv.couplet = verse.couplet; } lv.io = verse.io.clone(); + lv.op = verse.op.clone(); + lv.ep = verse.ep.clone(); lv.poems = verse.poems.clone(); lv.meter = verse.meter; if verse.clause().is_some() { diff --git a/src/poem/recite.rs b/src/poem/recite.rs index 037906b..b33cc87 100644 --- a/src/poem/recite.rs +++ b/src/poem/recite.rs @@ -1,6 +1,5 @@ use super::Poem; use crate::compose::Environment; -use crate::path; use crate::poem::anthology; use crate::poem::elements::rune::Rune; use crate::poem::elements::stanza::Stanza; @@ -143,33 +142,35 @@ impl Reciteable for Poem { }; // Incant the verse if it's a built-in - let status = if index.is_some() { - anthology::incant(&verse, &mut out, index.unwrap(), env) - } else { - // Checking for environment variables and running internal - // poems may mean that the verb is empty now, so check it once - // more - // If it is empty, just continue to the next verse - if !verse.verb().is_empty() { - // Check if the verb exists on the PATH - // If it doesn't exist, try refreshing the binary cache, and check - // again - // If it still doesn't exist, print an error - if !verse.spellcheck(&env.bins).is_some() { - env.bins = path::refresh(); - if !verse.spellcheck(&env.bins).is_some() { - eprintln!("dwvsh: {}: command not found", verse.verb()); - - if verse.meter != Rune::And { - continue; - } - } - } - } else { - continue; - } - verse.incant(&mut out, &mut pids)? - }; + let status = + verse.incant(&mut out, &mut pids, env, anthology::lookup(&verse.verb()))?; + // let status = if index.is_some() { + // anthology::incant(&verse, &mut out, index.unwrap(), env) + // } else { + // // Checking for environment variables and running internal + // // poems may mean that the verb is empty now, so check it once + // // more + // // If it is empty, just continue to the next verse + // if !verse.verb().is_empty() { + // // Check if the verb exists on the PATH + // // If it doesn't exist, try refreshing the binary cache, and check + // // again + // // If it still doesn't exist, print an error + // if !verse.spellcheck(&env.bins).is_some() { + // env.bins = path::refresh(); + // if !verse.spellcheck(&env.bins).is_some() { + // eprintln!("dwvsh: {}: command not found", verse.verb()); + // + // if verse.meter != Rune::And { + // continue; + // } + // } + // } + // } else { + // continue; + // } + // verse.incant(&mut out, &mut pids, anthology::lookup(&verse.verb()))? + // }; // Break from the loop if the meter is not [Rune::Continue], and // if the status is not 0 |