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, 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> { 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 { 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>(); /// /// let command_success = vec!["cargo", "build", "--release"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// let command_fail = vec!["make", "-j8"] /// .into_iter() /// .map(String::from) /// .collect>(); /// /// 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) -> 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 } }