grab/parsers/
text.rs

1use super::{
2    nom::{self, Finish},
3    EKind, InputError, InputType, NomError, Parser, Weight,
4};
5
6use std::fmt;
7
8pub type TextParser = for<'a, 'b> fn(&'a str, &'b str) -> nom::IResult<&'a str, String>;
9
10/// Construct for treating the given input to parse as a readable input source. By default, this
11/// parser will consume any valid utf8 strings and return it as an input source. Consequently, this
12/// parser by default has the lowest possible priority so it will always be the last parser run.
13#[derive(Clone, Default)]
14pub struct Text {
15    marker: Option<String>,
16    parser: Option<TextParser>,
17    weight: Option<u8>,
18}
19
20impl Text {
21    /// The default weighting for [Text]. Note that is the lowest weight possible, as by default
22    /// this parser will never fail for UTF8 input.
23    pub const DEFAULT_WEIGHT: u8 = 255;
24    /// Default marker for [Text]
25    pub const DEFAULT_MARKER: &'static str = "";
26    /// Default parser implementation for [Text]
27    pub const DEFAULT_PARSER: TextParser = default_text_parser;
28
29    /// Instantiate a new Text parser with sensible defaults
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Convenience function for modifying the semantics of
35    /// this parser
36    ///
37    /// Example:
38    ///
39    /// ```
40    /// use grab::parsers::Text;
41    ///
42    /// // Require the text to be prefaced with '###'
43    /// let Text = Text::new().with(|this| this.marker("###"));
44    /// ```
45    pub fn with<F>(self, f: F) -> Self
46    where
47        F: FnMut(&mut Self) -> &mut Self,
48    {
49        let mut this = self;
50        let mut actions = f;
51
52        actions(&mut this);
53
54        this
55    }
56
57    /// Modify the marker string for triggering this Text parser.
58    /// This marker is passed to the parser function as the second &str
59    /// argument.
60    pub fn marker(&mut self, marker: impl AsRef<str>) -> &mut Self {
61        self.marker = Some(marker.as_ref().to_string());
62
63        self
64    }
65
66    /// Replace the parser for this File with a different one. Expects a
67    /// _function_ (not closure) with the following arguments + return:
68    ///
69    /// fn my_parser<'a, 'b>(input: &'a str, marker: &'b str) -> crate::nom::IResult<&'a str, String>
70    /// {
71    ///     /* ... */
72    /// }
73    pub fn parser(&mut self, parser: TextParser) -> &mut Self {
74        self.parser = Some(parser);
75
76        self
77    }
78
79    /// Set this parser's weight. Lower numbers will be ran before greater.
80    pub fn weight(&mut self, weight: u8) -> &mut Self {
81        self.weight = Some(weight);
82
83        self
84    }
85
86    fn get_marker(&self) -> &str {
87        self.marker.as_deref().unwrap_or(Self::DEFAULT_MARKER)
88    }
89
90    fn get_weight(&self) -> u8 {
91        self.weight.unwrap_or(Self::DEFAULT_WEIGHT)
92    }
93
94    fn parse<'a>(&self, input: &'a str) -> Result<String, NomError<&'a str>> {
95        let marker = self.get_marker();
96
97        let (_, text) = self
98            .parser
99            .map(|p| p(input, marker))
100            .unwrap_or_else(|| Self::DEFAULT_PARSER(input, marker))
101            .finish()?;
102
103        Ok(text)
104    }
105
106    fn new_error(&self, _p_error: NomError<&str>) -> InputError {
107        InputError::new(EKind::TEXT)
108    }
109}
110
111impl Parser for Text {
112    fn parse_str(&self, s: &str) -> Result<InputType, InputError> {
113        self.parse(s)
114            .map(InputType::UTF8)
115            .map_err(|e| self.new_error(e))
116    }
117}
118
119impl Weight for Text {
120    fn weight(&self) -> u8 {
121        self.get_weight()
122    }
123}
124
125impl fmt::Debug for Text {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.debug_struct("Text")
128            .field("marker", &self.get_marker())
129            .field(
130                "parser",
131                &self
132                    .parser
133                    .map_or("Default TextParser", |_| "Custom TextParser"),
134            )
135            .finish()
136    }
137}
138
139/// Default text parser, if the given marker is empty (i.e "") it returns
140/// the entire input unmodified, otherwise it will return everything after
141/// the given marker
142pub fn default_text_parser<'a, 'b>(
143    input: &'a str,
144    marker: &'b str,
145) -> nom::IResult<&'a str, String> {
146    // If the marker is empty (the default) we just return everything
147    if marker.is_empty() {
148        Ok(("", input.to_string()))
149    } else {
150        nom::context("TEXT", nom::tag(marker))(input).map(|(path, _)| ("", String::from(path)))
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    const INPUT: &'static str = "some arbitrary text";
159
160    #[test]
161    fn defaults_success() {
162        let input = INPUT;
163        let output = String::from(input);
164
165        let parser = Text::new();
166
167        let result = parser.parse_str(input);
168
169        assert_eq!(result, Ok(InputType::UTF8(output)))
170    }
171
172    #[test]
173    fn c_marker_success() {
174        let mkr = "!!";
175
176        let input = "!!valid text";
177        let output = String::from("valid text");
178
179        let parser = Text::new().with(|this| this.marker(mkr));
180
181        let result = parser.parse_str(input);
182
183        assert_eq!(result, Ok(InputType::UTF8(output)))
184    }
185
186    #[test]
187    fn c_marker_failure() {
188        let mkr = "!!";
189
190        let input = "no marker";
191
192        let parser = Text::new().with(|this| this.marker(mkr));
193
194        let result = parser.parse_str(input);
195
196        assert_eq!(result, Err(EKind::TEXT.into()))
197    }
198
199    #[test]
200    fn c_parser_success() {
201        let input = INPUT;
202        let output = String::from(input);
203
204        let parser = Text::new().with(|this| this.parser(test_custom_parser));
205
206        let result = parser.parse_str(input);
207
208        assert_eq!(result, Ok(InputType::UTF8(output)))
209    }
210
211    #[test]
212    fn c_parser_failure() {
213        let input = "";
214
215        let parser = Text::new().with(|this| this.parser(test_custom_parser));
216
217        let result = parser.parse_str(input);
218
219        assert_eq!(result, Err(EKind::TEXT.into()))
220    }
221
222    fn test_custom_parser<'a, 'b>(
223        input: &'a str,
224        marker: &'b str,
225    ) -> nom::IResult<&'a str, String> {
226        use ::nom::error::{make_error, ErrorKind};
227        if input.is_empty() {
228            Err(::nom::Err::Error(make_error(input, ErrorKind::NonEmpty)))
229        } else {
230            nom::context("TEXT", nom::tag(marker))(input).map(|(path, _)| ("", String::from(path)))
231        }
232    }
233}