summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-06-04 16:25:32 -0600
committerRory Dudley2024-06-04 16:25:32 -0600
commitc4cd1e2c165c4f34ebf67fa9350f8732b2aeca13 (patch)
treea8dd06c7563c205eb4710f620cf89d89ae17d98b
parentdedadcfd30516c40692fe495a6ad10aea7c050de (diff)
downloaddwarvish-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.rs1
-rw-r--r--src/poem/anthology.rs142
-rw-r--r--src/poem/anthology/alias.rs56
-rw-r--r--src/poem/anthology/cd.rs53
-rw-r--r--src/poem/anthology/exit.rs3
-rw-r--r--src/poem/anthology/export.rs47
-rw-r--r--src/poem/anthology/source.rs98
-rw-r--r--src/poem/anthology/which.rs44
-rw-r--r--src/poem/elements/verse.rs242
-rw-r--r--src/poem/elements/verse/logic.rs219
-rw-r--r--src/poem/read.rs2
-rw-r--r--src/poem/recite.rs57
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