diff options
Diffstat (limited to 'src/poem/elements/rune.rs')
-rw-r--r-- | src/poem/elements/rune.rs | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/poem/elements/rune.rs b/src/poem/elements/rune.rs new file mode 100644 index 0000000..fc2b27a --- /dev/null +++ b/src/poem/elements/rune.rs @@ -0,0 +1,363 @@ +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 +/// +/// [Rune]s are a way to mark special characters from the input string (i.e. +/// poetry). Some [Rune]s are special--as they denote the end of a [Verse]-- +/// and are refered to as a Meter. For instance, `Addendum`, `Couplet`, +/// `Quiet`, and `And`, are all meters. Meters also determine how the +/// [Stanza][super::stanza::Stanza] should be interpreted. For instance, a +/// [Stanza][super::stanza::Stanza] that is piped needs to have +/// its `STDOUT` captured (rather than printing out to the terminal), and +/// subsequently sent to the next [Verse] in the [Poem][super::super::Poem]. +/// +/// # Values +/// * `None` - A shell command with no additional actions (the end of a poem) +/// * `Pause` - The space character, to dilineate words (` `) +/// * `Path` - The forward slash character, to dilineate paths (`/`) +/// * `Env` - Indicates an environment variable (`$`) +/// * `String` - Interpret all character as one large +/// [Word][super::word::Word] (`'` or `"`) +/// * `Poem` - A subcommand to run first (`\``) +/// * `Read` - Read files into STDIN (`<`) +/// * `Write` - Write STDOUT to a file (`>`) +/// * `Addendum` - Append STDOUT to a file (`>>`) +/// * `Couplet` - Pipe the output of this command into the next (`|`) +/// * `Quiet` - Fork the called process into the background (`&`) +/// * `And` - Run the next command only if this one succeeds (`&&`) +/// * `Continue` - String commands together on a single line (`;`) +/// * `Home` - Interpret `~` as `$HOME` +/// * `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 + 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 +} + +impl fmt::Display for Rune { + /// Determine how to print out a [Rune] + /// + /// Each [Rune]'s symbol corresponds to its input. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let rune = match self { + Rune::None => "", + Rune::Pause => " ", + Rune::Path => "/", + Rune::String => "\"", + Rune::Poem => "`", + Rune::Read => "<", + Rune::Write => ">", + Rune::Addendum => ">>", + Rune::Couplet => "|", + Rune::Quiet => "&", + Rune::And => "&&", + Rune::Continue => ";", + Rune::Home => "~", + Rune::Else => "_", + }; + + write!(f, "{}", rune) + } +} + +impl Rune { + // /// Check if a character is a special [Rune] + // pub fn special(rune: char) -> bool { + // match rune { + // ' ' | '/' | '$' | '\'' | '"' | '`' | '<' | '>' | '|' | '&' | ';' | '~' => true, + // _ => false, + // } + // } + + /// 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 String) -> 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 String) -> Result<i32, io::Error> { + let child = ctask!(verse, out); + + let output = child.wait_with_output()?; + + if !output.status.success() { + return Ok(output.status.code().unwrap_or(-1)); + } + + out.push_str( + String::from_utf8_lossy(&output.stdout) + .into_owned() + .as_str(), + ); + + 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 String, + 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 String) -> Result<i32, io::Error> { + Rune::incant_none(verse, out) + } + + /// Alias to [Rune::incant_none] + pub fn incant_continue(verse: &Verse, out: &mut String) -> 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 String, + 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.push_str(contents.as_str()); + } + + // 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 String, + pids: &mut Arc<Mutex<Vec<i32>>>, + ) -> Result<i32, io::Error> { + // Split the verse from the paths + let paths = verse.split("<"); + + // Alias incant_<meter> + // let status = Rune::incant_couplet(&verse, out)?; + let status = match verse.meter { + Rune::None => Rune::incant_none(&verse, out)?, + Rune::Couplet => Rune::incant_couplet(&verse, out)?, + Rune::Quiet => Rune::incant_quiet_io(&verse, out, pids)?, + Rune::And => Rune::incant_and(&verse, out)?, + Rune::Continue => Rune::incant_continue(&verse, out)?, + _ => unreachable!(), + }; + + // Write output to each file specified in the next verse + for path in paths.iter() { + let mut file = OpenOptions::new().create(true).write(true).open(path)?; + file.write(out.as_bytes())?; + } + + // 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 String, + pids: &mut Arc<Mutex<Vec<i32>>>, + ) -> Result<i32, io::Error> { + // Split the verse from the paths + let paths = verse.split("<"); + + // Alias incant_<meter> + // let status = Rune::incant_couplet(&verse, out)?; + let status = match verse.meter { + Rune::None => Rune::incant_none(&verse, out)?, + Rune::Couplet => Rune::incant_couplet(&verse, out)?, + Rune::Quiet => Rune::incant_quiet_io(&verse, out, pids)?, + Rune::And => Rune::incant_and(&verse, out)?, + Rune::Continue => Rune::incant_continue(&verse, out)?, + _ => unreachable!(), + }; + + // Write output to each file specified in the next verse + for path in paths.iter() { + let mut file = OpenOptions::new().create(true).append(true).open(path)?; + file.write(out.as_bytes())?; + } + + // Clear out + out.clear(); + + // Return the exit status + Ok(status) + } + + pub fn incant_quiet_io( + verse: &Verse, + out: &mut String, + pids: &mut Arc<Mutex<Vec<i32>>>, + ) -> Result<i32, io::Error> { + let child = iobtask!(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) + } +} |