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 (`/`) /// * `Remark` - Indicates a single line comment (`#`) /// * `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 Remark, // A comment 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::Remark => "#", 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 { 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 { 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>>` - 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>>, ) -> Result { 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 { Rune::incant_none(verse, out) } /// Alias to [Rune::incant_none] pub fn incant_continue(verse: &Verse, out: &mut String) -> Result { 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>>, ) -> Result { // 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_ 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>>, ) -> Result { // Split the verse from the paths let paths = verse.split("<"); // Alias incant_ // 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>>, ) -> Result { // Split the verse from the paths let paths = verse.split("<"); // Alias incant_ // 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) } /// Same as incant_quiet, except capture STDOUT into `out` pub fn incant_quiet_io( verse: &Verse, out: &mut String, pids: &mut Arc>>, ) -> Result { 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) } }