summaryrefslogtreecommitdiffstats
path: root/src/poem/elements/rune.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/poem/elements/rune.rs')
-rw-r--r--src/poem/elements/rune.rs363
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)
+ }
+}