summaryrefslogtreecommitdiffstats
path: root/src/poem
diff options
context:
space:
mode:
Diffstat (limited to 'src/poem')
-rw-r--r--src/poem/elements.rs4
-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
-rw-r--r--src/poem/read.rs261
-rw-r--r--src/poem/read/parse.rs68
-rw-r--r--src/poem/recite.rs222
-rw-r--r--src/poem/recite/ps.rs134
9 files changed, 1223 insertions, 0 deletions
diff --git a/src/poem/elements.rs b/src/poem/elements.rs
new file mode 100644
index 0000000..6cb4c7c
--- /dev/null
+++ b/src/poem/elements.rs
@@ -0,0 +1,4 @@
+pub mod rune;
+pub mod stanza;
+pub mod verse;
+pub mod word;
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>;
diff --git a/src/poem/read.rs b/src/poem/read.rs
new file mode 100644
index 0000000..01ddfc3
--- /dev/null
+++ b/src/poem/read.rs
@@ -0,0 +1,261 @@
+use super::{
+ elements::{rune::Rune, verse::Verse, word::Word},
+ Poem,
+};
+use core::fmt;
+mod parse;
+use crate::{next, poem, string};
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Mishap {
+ ParseMishap(usize, usize, char),
+ IOMishap(usize, usize, char),
+ PartialMishap(usize, usize, char),
+}
+
+impl fmt::Display for Mishap {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let message = match self {
+ Mishap::ParseMishap(j, i, c) => {
+ format!("parse error on line {} pos {} near '{}'", j, i, c)
+ }
+ Mishap::IOMishap(j, i, c) => {
+ format!(
+ "must provide file for io operation on line {} pos {} near '{}'",
+ j, i, c
+ )
+ }
+ Mishap::PartialMishap(j, i, c) => {
+ format!(
+ "partial string or action on line {} pos {} near '{}'",
+ j, i, c
+ )
+ }
+ };
+
+ write!(f, "{}", message)
+ }
+}
+
+/// A [Poem] can add more [Verse]s to itself
+trait Appendable {
+ type Type;
+ fn add(&mut self, verse: &mut Self::Type, meter: Rune, last: Rune);
+}
+
+impl Appendable for Poem {
+ type Type = Verse;
+
+ /// Push a [Verse] to the [Poem]
+ ///
+ /// Push a [Verse] to the [Poem] after checking that the [Verse] is not
+ /// empty. Also sets the meter of the [Verse].
+ fn add(&mut self, verse: &mut Self::Type, last: Rune, meter: Rune) {
+ if !verse.is_empty() {
+ verse.meter = meter;
+ if last == Rune::Couplet || meter == Rune::Couplet {
+ verse.couplet = true;
+ }
+ self.push(verse.clone());
+ verse.clear();
+ }
+ }
+}
+
+/// A [Poem] can parse poetry
+pub trait Readable {
+ fn read(poetry: String) -> Result<Poem, Mishap>;
+}
+
+impl Readable for Poem {
+ /// Parse a [Poem] from a raw [String] input
+ ///
+ /// Takes a shell command/program or file and converts it to a
+ /// machine-runnable [Poem]. If there is a parse error, [Poem::read] may
+ /// return a [Mishap]. See [Poem::recite][super::recite] for how each
+ /// [Verse] in a [Poem] is called.
+ fn read(poetry: String) -> Result<Poem, Mishap> {
+ // Get all the characters in the input string as an iterator
+ let mut chars = poetry.chars().into_iter();
+
+ // Create a stack to store words
+ let mut word: Word = Word::new();
+
+ // Create a stack to store the current verse
+ let mut verse: Verse = Verse::new();
+
+ // Create a vector to return
+ let mut poem: Self = Poem::new();
+
+ // Keep track of the last rune
+ let mut last = Rune::None;
+
+ // Keep track of the line
+ let mut j = 0;
+
+ // Keep track of the column
+ let mut i = 0;
+
+ // Loop through every char in the iterator
+ loop {
+ // Get the next character, and unwrap it
+ let c = chars.next();
+ let c = match c {
+ Some(c) => c,
+ None => {
+ // Check for IO parse errors
+ if last == Rune::Read || last == Rune::Write || last == Rune::Addendum {
+ return Err(Mishap::IOMishap(j, i, ' '));
+ }
+
+ // If c is none, it indicates the end of a poem, so wrap up and
+ // then break from the loop
+ verse.add(&mut word);
+
+ // Throw an error if the verse is empty
+ if verse.is_empty() && (last == Rune::Couplet || last == Rune::And) {
+ return Err(Mishap::ParseMishap(j, i, ' '));
+ }
+
+ // Push the verse and break
+ poem.add(&mut verse, last, Rune::None);
+ break;
+ }
+ };
+
+ // Determine the meter based on the character
+ let rune = match c {
+ ' ' => Rune::Pause,
+ '/' => Rune::Path,
+ '\'' | '"' => Rune::String,
+ '`' => Rune::Poem,
+ '<' => {
+ verse.couplet = true;
+ Rune::Read
+ }
+ '>' => next!(chars, i, Rune::Write, Rune::Addendum, '>'),
+ '|' => Rune::Couplet,
+ '&' => next!(chars, i, Rune::Quiet, Rune::And, '&'),
+ ';' => Rune::Continue,
+ '\n' => {
+ j += 1;
+ i = 0;
+ Rune::Continue
+ }
+ '~' => Rune::Home,
+ _ => Rune::Else,
+ };
+
+ // Some error checking, based on the last character
+ match rune {
+ Rune::Couplet
+ | Rune::Quiet
+ | Rune::And
+ | Rune::Read
+ | Rune::Write
+ | Rune::Addendum => {
+ if (last == Rune::Couplet
+ || last == Rune::Quiet
+ || last == Rune::And
+ || last == Rune::Read
+ || last == Rune::Write
+ || last == Rune::Addendum)
+ || verse.is_empty()
+ {
+ return Err(Mishap::ParseMishap(j, i, c));
+ }
+ }
+
+ Rune::Continue => {
+ if last == Rune::Read || last == Rune::Write || last == Rune::Addendum {
+ return Err(Mishap::ParseMishap(j, i, c));
+ }
+ }
+
+ _ => {
+ if (last == Rune::Read || last == Rune::Write || last == Rune::Addendum)
+ && rune == Rune::None
+ && rune == Rune::Read
+ && rune == Rune::Write
+ && rune == Rune::Addendum
+ && rune == Rune::Couplet
+ && rune == Rune::Quiet
+ && rune == Rune::And
+ && rune == Rune::Continue
+ {
+ return Err(Mishap::IOMishap(j, i, c));
+ }
+ }
+ };
+
+ // Do some action, based on the rune
+ match rune {
+ // Indicates the end of a word (space dilineated)
+ Rune::Pause => {
+ verse.add(&mut word);
+ }
+
+ // Indicates a string (' or ")
+ Rune::String => {
+ string!(chars, j, i, c, word);
+ }
+
+ // Indicates a sub-poem
+ Rune::Poem => {
+ poem!(chars, j, i, c, verse, word);
+ // let sp = Poem::read(word.iter().collect());
+ // let sp = match sp {
+ // Ok(sp) => sp,
+ // Err(e) => return Err(e),
+ // };
+ // verse.poems.push(sp);
+ // word.push('\x0b');
+ // verse.push(format!("\x0b{}", verse.poems.len() - 1));
+ // word.clear();
+ }
+
+ // Indicates a file operation (<, >, or >>)
+ Rune::Read | Rune::Write | Rune::Addendum => {
+ verse.add(&mut word);
+ word.push('<');
+ verse.add(&mut word);
+ verse.io = rune;
+ }
+
+ // These meters indicate the end of a verse
+ Rune::Couplet | Rune::Quiet | Rune::And | Rune::Continue => {
+ verse.add(&mut word);
+ poem.add(&mut verse, last, rune);
+ }
+
+ // Interpret ~ as $HOME
+ Rune::Home => {
+ let mut chars = env!("HOME").chars().collect();
+ word.append(&mut chars);
+ }
+
+ // Any other char i.e. Meter::Else
+ _ => {
+ word.push(c);
+ }
+ }
+
+ // Set the last meter
+ if rune != Rune::Pause {
+ last = rune;
+ }
+ // last = match poem.last() {
+ // Some(last) => last.meter,
+ // None => Rune::None,
+ // };
+
+ // Increment i, but don't drift over newlines
+ if c != '\n' {
+ i += 1;
+ }
+ }
+
+ // Return the poem
+ Ok(poem)
+ }
+}
diff --git a/src/poem/read/parse.rs b/src/poem/read/parse.rs
new file mode 100644
index 0000000..c4d59e6
--- /dev/null
+++ b/src/poem/read/parse.rs
@@ -0,0 +1,68 @@
+/// Look ahead one character in the input
+///
+/// May need to look ahead one character in the input string to determine the
+/// proper rune. For instance `&`, vs `&&`.
+#[macro_export]
+macro_rules! next {
+ ($chars:expr, $i:expr, $otherwise:expr, $rune:expr, $ahead:expr) => {
+ match $chars.clone().peekable().peek() {
+ Some(c) if *c == $ahead => {
+ $chars.next();
+ $i += 1;
+ $rune
+ }
+ Some(_) => $otherwise,
+ None => $otherwise,
+ }
+ };
+}
+
+/// Keep pushing to the [Word][super::super::elements::word::Word] stack
+///
+/// If a [Rune::String][super::super::elements::rune::Rune] character is found,
+/// stop interpreting special characters, and push all characters to the
+/// [Word][super::super::elements::word::Word] stack, until the corresponding
+/// [Rune::String][super::super::elements::rune::Rune] character is found.
+#[macro_export]
+macro_rules! string {
+ ($chars:expr, $j:expr, $i:expr, $c:expr, $word:expr) => {
+ let token = $c;
+ loop {
+ match $chars.next() {
+ None => return Err(Mishap::PartialMishap($j, $i, $c)),
+ Some(c) if c == token => break,
+ Some(c) => {
+ $word.push(c);
+ $i += 1;
+ }
+ }
+ }
+ continue;
+ };
+}
+
+/// Same as the [string!] macro, but don't `continue`
+#[macro_export]
+macro_rules! poem {
+ ($chars:expr, $j:expr, $i:expr, $c:expr, $verse:expr, $word:expr) => {
+ let token = $c;
+ let mut poetry = Word::new();
+ loop {
+ match $chars.next() {
+ None => return Err(Mishap::PartialMishap($j, $i, $c)),
+ Some(c) if c == token => break,
+ Some(c) => {
+ poetry.push(c);
+ $i += 1;
+ }
+ }
+ }
+ let sp = Poem::read(poetry.iter().collect());
+ let sp = match sp {
+ Ok(sp) => sp,
+ Err(e) => return Err(e),
+ };
+ $verse.poems.push(sp);
+ $word.push('\x0b');
+ };
+}
diff --git a/src/poem/recite.rs b/src/poem/recite.rs
new file mode 100644
index 0000000..ba739c2
--- /dev/null
+++ b/src/poem/recite.rs
@@ -0,0 +1,222 @@
+mod ps;
+use super::Poem;
+use crate::path::prefresh;
+use crate::poem::elements::rune::Rune;
+use std::env;
+use std::process::exit;
+use std::{
+ io,
+ path::Path,
+ sync::{Arc, Mutex},
+};
+
+pub trait Reciteable {
+ fn recite(
+ &self,
+ path: &Vec<&Path>,
+ bins: &mut Vec<String>,
+ stdout: Option<bool>,
+ ) -> Result<String, io::Error>;
+}
+
+impl Reciteable for Poem {
+ fn recite(
+ &self,
+ path: &Vec<&Path>,
+ bins: &mut Vec<String>,
+ stdout: Option<bool>,
+ ) -> Result<String, io::Error> {
+ // Should we print to stdout or always capture it
+ let stdout = stdout.unwrap_or(true);
+
+ // Variable for storing the output of a piped verse
+ let mut out: String = String::new();
+
+ // Keep track of pids for background processes
+ let mut pids: Arc<Mutex<Vec<i32>>> = Arc::new(Mutex::new(Vec::new()));
+
+ // Loop through each verse in the poem
+ for verse in self.iter() {
+ // Verse may need to be mutable
+ let mut verse = verse.clone();
+
+ // Check for environment variables
+ for word in verse.stanza.iter_mut() {
+ while word.contains("$") {
+ let mut name = vec![];
+ let mut idx = word.chars().position(|c| c == '$').unwrap() + 1;
+ let bytes = word.as_bytes();
+ while idx < word.len() {
+ let c = bytes[idx] as char;
+ if !c.is_alphanumeric() {
+ break;
+ }
+ name.push(c);
+ idx += 1;
+ }
+ let name: String = format!("${}", name.iter().collect::<String>());
+ let envar = name[1..].to_string();
+ let envar = match env::var(envar) {
+ Ok(envar) => envar.to_string(),
+ Err(_) => "".to_string(),
+ };
+ *word = word.replace(name.as_str(), envar.as_str());
+ }
+ }
+
+ // Run interal poems
+ let v = verse.clone();
+ let mut new_stanza = None;
+ if verse.poems() {
+ // Collect all the words that have vertical tabs
+ let mut wordp_indicies = vec![];
+ let wordps = verse
+ .stanza
+ .iter_mut()
+ .enumerate()
+ .filter(|(_, w)| w.contains("\x0b"))
+ .map(|(i, w)| {
+ wordp_indicies.push(i + 1);
+ w
+ })
+ .collect::<Vec<&mut String>>();
+
+ // Loop through each word and replace with the output of the poem
+ let mut poems = verse.poems.iter();
+ let mut j = 0;
+ for wordp in wordps {
+ let times = wordp
+ .chars()
+ .filter(|c| c == &'\x0b')
+ .collect::<String>()
+ .len();
+ for _ in 0..times {
+ let poem = match poems.next() {
+ Some(poem) => poem,
+ None => break, // TODO: Return an error
+ };
+ let out = poem.recite(path, bins, Some(false))?;
+ if out.contains("\n") {
+ let mut out = out.split("\n");
+ let next = out.next().unwrap_or("").trim();
+ *wordp = wordp.replacen("\x0b", next, 1).to_string();
+ let (_, right) = v.stanza.split_at(wordp_indicies[j]);
+ let mut left = vec![];
+ let mut right = right.to_vec();
+ loop {
+ let next = match out.next() {
+ Some(next) => next,
+ None => break,
+ }
+ .to_string();
+ left.push(next);
+ }
+ left.append(&mut right);
+ new_stanza = Some(left.clone());
+ } else {
+ *wordp = wordp.replacen("\x0b", out.as_str(), 1).to_string();
+ }
+ }
+ j += 1;
+ }
+
+ // // Get indices of words in the verse that begin with a vertical tab
+ // let mut indicies = vec![];
+ // for (i, word) in verse.stanza.iter().enumerate() {
+ // if word.starts_with("\x0b") {
+ // indicies.push(i);
+ // }
+ // }
+
+ // // Try to recite the internal poem, and update the parent poem
+ // for (i, poem) in verse.poems.iter().enumerate() {
+ // let out = poem.recite(path, bins, Some(false))?;
+ // verse.stanza[indicies[i]] = out;
+ // }
+ }
+
+ match new_stanza {
+ Some(stanza) => {
+ let mut stanza = stanza.clone();
+ verse.stanza.append(&mut stanza);
+ }
+ None => {}
+ }
+
+ // Check if the user wants to exit the shell
+ if verse.verb() == "exit" || verse.verb() == "quit" {
+ exit(0);
+ }
+
+ // Check if the user wants to change directories
+ if verse.verb() == "cd" {
+ let path = match verse.clause() {
+ Some(path) => path[0].to_string(),
+ None => env!("HOME").to_string(),
+ };
+
+ match std::env::set_current_dir(&path) {
+ Ok(_) => continue,
+ Err(e) => {
+ eprintln!(
+ "cd: unable to change into {}: {}",
+ path,
+ e.to_string().to_lowercase()
+ );
+ continue;
+ }
+ }
+ }
+
+ // Check if the verb exists
+ // 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(bins) {
+ *bins = prefresh(path);
+ if !verse.spellcheck(bins) {
+ eprintln!("dwvsh: {}: command not found", verse.verb());
+
+ if verse.meter != Rune::And {
+ continue;
+ }
+ }
+ }
+
+ // Incant the verse, based on its meter
+ let status = if stdout {
+ match verse.io {
+ Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?,
+ Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?,
+ Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?,
+ _ => match verse.meter {
+ Rune::None => Rune::incant_none(&verse, &mut out)?,
+ Rune::Couplet => Rune::incant_couplet(&verse, &mut out)?,
+ Rune::Quiet => Rune::incant_quiet(&verse, &mut out, &mut pids)?,
+ Rune::And => Rune::incant_and(&verse, &mut out)?,
+ Rune::Continue => Rune::incant_continue(&verse, &mut out)?,
+ _ => unreachable!(),
+ },
+ }
+ } else {
+ match verse.io {
+ Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?,
+ Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?,
+ Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?,
+ _ => Rune::incant_couplet(&verse, &mut out)?,
+ }
+ };
+
+ // Break from the loop if the meter is not [Rune::Continue], and
+ // if the status is not 0
+ // For [Rune::Quiet], [Rune::incant_quiet] will always return 0
+ if verse.meter != Rune::Continue && status != 0 {
+ break;
+ }
+ }
+
+ // If we've successfully exited the loop, then all verses were properly
+ // recited
+ Ok(out.trim().to_string())
+ }
+}
diff --git a/src/poem/recite/ps.rs b/src/poem/recite/ps.rs
new file mode 100644
index 0000000..61ed66d
--- /dev/null
+++ b/src/poem/recite/ps.rs
@@ -0,0 +1,134 @@
+/// Fork into a process from a Verse
+///
+/// Figures out whether or not the given Verse is a couplet. If it is, fork
+/// into a process, and pipe the contents of out `out` into STDIN. If not, then
+/// simply fork into the process.
+///
+/// # Arguments
+/// * `$verse: &Verse` - The verse to fork into
+/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse
+#[macro_export]
+macro_rules! task {
+ ($verse:expr, $out:expr) => {
+ if $verse.couplet {
+ let mut child = Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdin(Stdio::piped())
+ .spawn()?;
+
+ let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?;
+ stdin.write_all(&$out.as_bytes())?;
+ $out.clear();
+
+ child
+ } else {
+ Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .spawn()?
+ }
+ };
+}
+
+/// Fork into a process from a Verse, and capture STDOUT
+///
+/// Figures out whether or not the given Verse is a couplet. If it is, fork
+/// into a process, and pipe the contents of out `out` into STDIN. If not, then
+/// simply fork into the process. Additionally, this function will capture
+/// STDOUT of the process specified by the Verse, and store it in `out`.
+///
+/// # Arguments
+/// * `$verse: &Verse` - The verse to fork into
+/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse
+#[macro_export]
+macro_rules! ctask {
+ ($verse:expr, $out:expr) => {
+ if $verse.couplet {
+ let mut child = Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?;
+
+ let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?;
+ stdin.write_all(&$out.as_bytes())?;
+ $out.clear();
+
+ child
+ } else {
+ Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdout(Stdio::piped())
+ .spawn()?
+ }
+ };
+}
+
+/// Fork into a background process from a Verse
+///
+/// Figures out whether or not the given Verse is a couplet. If it is, fork
+/// into a backgournd process, and pipe the contents of out `out` into STDIN.
+/// If not, then simply fork into the background process.
+///
+/// # Arguments
+/// * `$verse: &Verse` - The verse to fork into
+/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse
+#[macro_export]
+macro_rules! btask {
+ ($verse:expr, $out:expr) => {
+ if $verse.couplet {
+ let mut child = Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdin(Stdio::piped())
+ .process_group(0)
+ .spawn()?;
+
+ let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?;
+ stdin.write_all(&$out.as_bytes())?;
+ $out.clear();
+
+ child
+ } else {
+ Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .process_group(0)
+ .spawn()?
+ }
+ };
+}
+
+/// Fork into a background process from a Verse, and capture STDOUT
+///
+/// Figures out whether or not the given Verse is a couplet. If it is, fork
+/// into a backgournd process, and pipe the contents of out `out` into STDIN.
+/// If not, then simply fork into the background process. This captures the
+/// output of STDOUT, in order to redirect it to a file when the program
+/// finishes running.
+///
+/// # Arguments
+/// * `$verse: &Verse` - The verse to fork into
+/// * `$out: &mut String` - If the $verse is a couplet, the contents of STDOUT from the last verse
+#[macro_export]
+macro_rules! iobtask {
+ ($verse:expr, $out:expr) => {
+ if $verse.couplet {
+ let mut child = Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .process_group(0)
+ .spawn()?;
+
+ let stdin = child.stdin.as_mut().ok_or(io::ErrorKind::BrokenPipe)?;
+ stdin.write_all(&$out.as_bytes())?;
+ $out.clear();
+
+ child
+ } else {
+ Command::new($verse.verb())
+ .args($verse.clause().unwrap_or(vec![]))
+ .stdout(Stdio::piped())
+ .process_group(0)
+ .spawn()?
+ }
+ };
+}