summaryrefslogtreecommitdiffstats
path: root/src/poem/elements
diff options
context:
space:
mode:
Diffstat (limited to 'src/poem/elements')
-rw-r--r--src/poem/elements/rune.rs363
-rw-r--r--src/poem/elements/stanza.rs10
-rw-r--r--src/poem/elements/verse.rs159
-rw-r--r--src/poem/elements/word.rs2
4 files changed, 534 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)
+ }
+}
diff --git a/src/poem/elements/stanza.rs b/src/poem/elements/stanza.rs
new file mode 100644
index 0000000..d58d080
--- /dev/null
+++ b/src/poem/elements/stanza.rs
@@ -0,0 +1,10 @@
+/// The actionable part of a [Verse][super::verse::Verse]
+///
+/// Each [Stanza] has two parts, a `verb()` and a `clause()`. The `verb()` is
+/// the program, or path to the program to call, while the `clause()` contains
+/// arguments to pass to that program.
+///
+/// The [Stanza] is just stored as a [Vec] of [String]s, where the verb is the
+/// first entry in the vector (i.e. `stanza[0]`) and the clause the the
+/// remainder of the vector (i.e. `stanza[1..]`).
+pub type Stanza = Vec<String>;
diff --git a/src/poem/elements/verse.rs b/src/poem/elements/verse.rs
new file mode 100644
index 0000000..e857676
--- /dev/null
+++ b/src/poem/elements/verse.rs
@@ -0,0 +1,159 @@
+use super::rune::Rune;
+use super::stanza::Stanza;
+use super::word::Word;
+use crate::poem::Poem;
+use std::path::Path;
+
+/// A [Stanza] and it's [meter](Rune)
+///
+/// In addition to a [Stanza] and a [meter](Rune), this also holds a [bool]
+/// value called `couplet`, indicating that it needs to accept input on `STDIN`
+/// from the previous [Verse].
+#[derive(Debug, Clone)]
+pub struct Verse {
+ pub stanza: Stanza,
+ pub couplet: bool,
+ pub io: Rune,
+ pub poems: Vec<Poem>,
+ pub meter: Rune,
+}
+
+impl Verse {
+ /// Create a new [Verse]
+ ///
+ /// Returns a new [Verse], with an empty [Stanza], a meter of [Rune::None],
+ /// and `couplet` set to `false`.
+ pub fn new() -> Self {
+ Verse {
+ stanza: Stanza::new(),
+ couplet: false,
+ io: Rune::None,
+ poems: Vec::new(),
+ meter: Rune::None,
+ }
+ }
+
+ /// Get the [Verse]'s verb
+ ///
+ /// Return the program to be forked
+ pub fn verb(&self) -> String {
+ self.stanza[0].clone()
+ }
+
+ /// Get the [Verse]'s clause
+ ///
+ /// Return program arguments, if they exist
+ pub fn clause(&self) -> Option<Vec<String>> {
+ match self.stanza.len() {
+ 0 => None,
+ 1 => None,
+ _ => Some(self.stanza[1..].to_vec()),
+ }
+ }
+
+ /// Alias to [Verse].stanza.push()
+ pub fn push(&mut self, word: String) {
+ self.stanza.push(word);
+ }
+
+ /// Alias to [Verse].stanza.is_empty()
+ pub fn is_empty(&self) -> bool {
+ self.stanza.is_empty()
+ }
+
+ /// Alias to [Verse].stanza.clear()
+ pub fn clear(&mut self) {
+ self.stanza.clear();
+ }
+
+ /// Check if the [Verse] contains any internal poems
+ pub fn poems(&self) -> bool {
+ if self.poems.len() > 0 {
+ return true;
+ }
+ false
+ }
+
+ /// Push a word to the [Verse]'s [Stanza]
+ ///
+ /// Push a word to the [Stanza] after performing a few extra checks, such
+ /// as whether or not the word is empty, or if the word should be
+ /// interpreted as an environment variable.
+ pub fn add(&mut self, word: &mut Word) {
+ if !word.is_empty() {
+ // Push the word, and clear the stack
+ self.push(word.iter().collect());
+ word.clear();
+ }
+ }
+
+ /// Split a [Verse] into two different [Verse]s
+ ///
+ /// This is useful for [Rune::Read], [Rune::Write], and [Rune::Addendum].
+ pub fn split(&mut self, c: &str) -> Vec<String> {
+ for (i, s) in self.stanza.iter().enumerate() {
+ if *s == c {
+ let split = self.stanza.split_off(i);
+ return split[1..].to_vec();
+ }
+ }
+ vec![]
+ }
+
+ /// Check if the `verb()` exists in the `$PATH`
+ ///
+ /// First checks if the `verb()` is a relative or full path. If it is,
+ /// check whether or not it exists. If it does exist, return true,
+ /// otherwise seeif the `verb()` is cached in our list of binaries. Search is
+ /// done in $PATH order.
+ ///
+ /// # Examples
+ /// ```
+ /// let bins = vec!["cargo", "ruby", "cat"]
+ /// .into_iter()
+ /// .map(String::from)
+ /// .collect<Vec<String>>();
+ ///
+ /// let command_success = vec!["cargo", "build", "--release"]
+ /// .into_iter()
+ /// .map(String::from)
+ /// .collect<Vec<String>>();
+ ///
+ /// let command_fail = vec!["make", "-j8"]
+ /// .into_iter()
+ /// .map(String::from)
+ /// .collect<Vec<String>>();
+ ///
+ /// let stanza_success = Stanza::new(command_success);
+ /// let stanza_fail = Stanza::new(command_fail);
+ ///
+ /// stanza_success.spellcheck(bins) // -> true
+ /// stanza_fail.spellcheck(bins) // -> false
+ /// ```
+ pub fn spellcheck(&self, bins: &Vec<String>) -> bool {
+ // An empty verb (i.e. the empty string) cannot be a program, so
+ // return false
+ // Thanks to the parsing in Poem::read, however, it's
+ // unlikely for this to happen
+ if self.verb().is_empty() {
+ return false;
+ }
+
+ // Only search the $PATH if a full or relative path was not given, or
+ // if the path given does not exist
+ if !Path::new(self.verb().as_str()).exists() {
+ // Try to find a binary in our path with the same name as the verb
+ // Searches in $PATH order
+ match bins
+ .iter()
+ .find(|bin| bin.split('/').last().unwrap() == self.verb())
+ {
+ Some(_) => return true,
+ None => return false,
+ }
+ }
+
+ // Return true if the full path or relative path exists
+ true
+ }
+}
diff --git a/src/poem/elements/word.rs b/src/poem/elements/word.rs
new file mode 100644
index 0000000..dd088e0
--- /dev/null
+++ b/src/poem/elements/word.rs
@@ -0,0 +1,2 @@
+/// A (typically) space dilineated piece of a [Stanza][super::stanza::Stanza]
+pub type Word = Vec<char>;