summaryrefslogtreecommitdiffstats
path: root/src/poem/elements/verse.rs
blob: e8576764aeae704bf4a3ce0edd267620cf1d56a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
    }
}