summaryrefslogtreecommitdiffstats
path: root/src/poem/elements/verse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/poem/elements/verse.rs')
-rw-r--r--src/poem/elements/verse.rs159
1 files changed, 159 insertions, 0 deletions
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
+ }
+}