snippets_rs/
lib.rs

1//! This specific implementation of the snippet parser does not read all strings into memory
2//! immediately. Rather, it reads lines into memory as needed.
3
4use std::fmt::{Debug, Display, Formatter};
5use std::fs::File;
6use std::io::{BufRead, BufReader, Lines};
7
8#[derive(Debug, Clone)]
9pub struct SnippetError<'a> {
10    message: &'a str
11}
12
13impl<'a> Display for SnippetError<'a> {
14    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
15        write!(f, "{}", self.message)
16    }
17}
18
19impl<'a> std::error::Error for SnippetError<'a> {}
20
21/// Parses a snippet file, or creates a new struct representing a snippet file.
22#[derive(Debug)]
23pub struct SnippetParser<'a> {
24    path: Option<&'a str>,
25    iter_reader: Option<Lines<BufReader<File>>>,
26    snippets: Option<Vec<Snippet>>,
27    snippet_index: usize
28}
29
30// New
31impl<'a> SnippetParser<'a> {
32    /// Creates a new struct representing a snippet file.
33    pub fn new() -> Self {
34        Self { path: None, iter_reader: None, snippets: None, snippet_index: 0 }
35    }
36    
37    /// Reads a snippet file into this struct
38    pub fn read(path: &'a str) -> std::io::Result<Self> {
39        let file = File::open(path);
40        if file.is_err() {
41            return Err(file.err().unwrap());
42        }
43        let reader = BufReader::new(file.unwrap());
44        Ok(Self { path: Some(path), iter_reader: Some(reader.lines()), snippets: None, snippet_index: 0 })
45    }
46    
47    /// Creates a new struct representing a snippet file containing the given snippets
48    pub fn from_snippets(snips: Vec<Snippet>) -> Self {
49        Self { path: None, iter_reader: None, snippets: Some(snips), snippet_index: 0 }
50    }
51}
52
53impl<'a> SnippetParser<'a> {
54    /// Adds a [snippet](crate::Snippet) to this SnippetParser.
55    pub fn add_snippet(&mut self, snip: Snippet) {
56        if let Some(snippets) = &mut self.snippets {
57            snippets.push(snip);
58        } else {
59            self.snippets = Some(vec![snip]);
60        }
61    }
62    
63    /// Gets all snippets from this `SnippetParser`. This means snippets defined by the file at the
64    /// given `path` and files added using the `add_snippet` method or `from_snippets` method.
65    pub fn get_snippets(&self) -> std::io::Result<Vec<Snippet>> {
66        return if self.path.is_some() {
67            let file = File::open(self.path.unwrap());
68            if file.is_err() {
69                return Err(file.err().unwrap());
70            }
71            let reader = BufReader::new(file.unwrap());
72            let copy_of_self = Self {
73                path: Some(self.path.unwrap()),
74                iter_reader: Some(reader.lines()),
75                snippets: self.snippets.clone(),
76                snippet_index: 0
77            };
78            let file_snippets: Vec<Snippet> = copy_of_self.into_iter().map(|snippet| snippet.clone()).collect();
79            Ok(file_snippets)
80        } else {
81            if let Some(snippets) = &self.snippets {
82                Ok(snippets.clone())
83            } else {
84                Ok(Vec::new())
85            }
86        }
87    }
88    
89    /// Returns the snippet matching the given title.
90    ///
91    /// # Errors
92    /// Returns an err if the file specified by the path could not be read. Ok otherwise. If there
93    /// was no path specified, then this will always return Ok.
94    ///
95    /// # Optional
96    /// Return `Some(&Snippet)` if the snippet with the specified title could be found, None otherwise
97    pub fn get_snippet(&self, title: &str) -> std::io::Result<Option<Snippet>> {
98        let snippets = self.get_snippets();
99        if let Ok(snippets) = snippets {
100            let found_snippet: Option<&Snippet> = snippets.iter().find_map(|snippet| {
101               if &snippet.title == title {
102                   Some(snippet)
103               } else {
104                   None
105               }
106            });
107            return if let Some(found_snippet) = found_snippet {
108                Ok(Some(found_snippet.clone()))
109            } else {
110                Ok(None)
111            }
112        } else {
113            Err(snippets.err().unwrap())
114        }
115    }
116}
117
118impl Iterator for SnippetParser<'_> {
119    type Item = Snippet;
120    
121    fn next(&mut self) -> Option<Self::Item> {
122        if self.iter_reader.is_some() {
123            return if let Some(snippet) = &self.read_next_snippet() {
124                // println!("There are more snippets to read from file: {}", snippet);
125                Some(snippet.clone())
126            } else {
127                // read next from snippets
128                self.read_next_from_snippets()
129            }
130        } else {
131            // Read next from snippets
132            self.read_next_from_snippets()
133        }
134    }
135}
136
137#[cfg(test)]
138#[test]
139fn read_next_snippet_test() {
140    let mut parser = SnippetParser::read("./tests/snippets/snippet_test.snip").unwrap();
141    let first_snip = "\
142Are we human?
143Or are we dancer?\
144";
145    let second_snip = "\
146This is my church.
147This is where I heal my hurts.";
148    
149    let third_snip = "\
150Never gonna give you up
151Never gonna let you down
152Never gonna run around and desert you
153
154Never gonna make you cry
155Never gonna say goodbye
156Never gonna tell a lie and hurt you
157\
158";
159    let first_read_snip = parser.read_next_snippet().unwrap();
160    let second_read_snip = parser.read_next_snippet().unwrap();
161    let third_read_snip = parser.read_next_snippet().unwrap();
162    
163    assert_eq!(first_snip, first_read_snip.s);
164    assert_eq!(second_snip, second_read_snip.s);
165    assert_eq!(third_snip, third_read_snip.s);
166    assert_eq!("snippet1", first_read_snip.title);
167    assert_eq!("snippet2", second_read_snip.title);
168    assert_eq!("snippet3 with space", third_read_snip.title);
169    assert_eq!(None, parser.read_next_snippet());
170}
171
172#[cfg(test)]
173#[test]
174fn read_next_snippet_test_with_adding_snippet() {
175    // Should not read added snippets
176    
177    let mut parser = SnippetParser::read("./tests/snippets/snippet_test.snip").unwrap();
178    let first_snip = "\
179Are we human?
180Or are we dancer?\
181";
182    let second_snip = "\
183This is my church.
184This is where I heal my hurts.";
185    
186    let third_snip = "\
187Never gonna give you up
188Never gonna let you down
189Never gonna run around and desert you
190
191Never gonna make you cry
192Never gonna say goodbye
193Never gonna tell a lie and hurt you
194\
195";
196    
197    let fourth_snip = "\
198Are you on the square?
199Are you on the hammer?
200Are you ready to stand right here right now
201Before the devil?
202\
203";
204    let fourth_snippet = Snippet::new("Square Hammer".to_string(), fourth_snip.to_string());
205    
206    parser.add_snippet(fourth_snippet);
207    println!("{:?}", parser);
208    let first_read_snip = parser.read_next_snippet().unwrap();
209    let second_read_snip = parser.read_next_snippet().unwrap();
210    let third_read_snip = parser.read_next_snippet().unwrap();
211    
212    let fourth_read_snip = parser.read_next_snippet();
213    
214    assert_eq!(first_snip, first_read_snip.s);
215    assert_eq!(second_snip, second_read_snip.s);
216    assert_eq!(third_snip, third_read_snip.s);
217    assert_eq!("snippet1", first_read_snip.title);
218    assert_eq!("snippet2", second_read_snip.title);
219    assert_eq!("snippet3 with space", third_read_snip.title);
220    // assert_eq!("Square Hammer", fourth_read_snip.title);
221    // assert_eq!(fourth_snip, fourth_read_snip.s);
222    assert_eq!(None, fourth_read_snip);
223    assert_eq!(None, parser.read_next_snippet());
224}
225
226// next
227impl<'a> SnippetParser<'a> {
228    /// Reads the next snippet from the file. This is like a `next` method, but only for
229    /// snippets in the file.
230    fn read_next_snippet(&mut self) -> Option<Snippet> {
231        let mut title: String = String::new();
232        let mut started = false;
233        let mut lines: Vec<String> = Vec::new();
234        // Read lines from file into `lines`
235        loop {
236            if let Some(_lines) = &mut self.iter_reader {
237                let line = _lines.next();
238                if let Some(line) = line {
239                    if line.is_err() {
240                        return None;
241                    }
242                    if started == false {
243                        if line.as_ref().unwrap().contains("--") {
244                            // Found title
245                            let _title = line.unwrap().replace("--", "");
246                            title = _title.trim().to_string();
247                            started = true;
248                        }
249                    } else {
250                        // Search for ending
251                        if line.as_ref().unwrap().contains("-- end --") {
252                            break; // end
253                        } else {
254                            // Line from string
255                            lines.push(line.unwrap());
256                        }
257                    }
258                } else {
259                    return None;
260                }
261            } else {
262                return None;
263            }
264        }
265        
266        let len_of_lines = lines.len();
267        let s: String = lines
268            .into_iter()
269            .enumerate()
270            .flat_map(|(index, line)| {
271            let mut line = line;
272            if index != len_of_lines - 1 {
273                line.push_str("\n");
274            }
275            line.chars().collect::<Vec<char>>()
276        }).collect();
277        Some(Snippet::new(title,  s))
278    }
279    
280    /// Reads the next snippet from the `snippets` field.
281    fn read_next_from_snippets(&mut self) -> Option<Snippet> {
282        if let Some(snippets) = &self.snippets {
283            let snippet = snippets.get(self.snippet_index);
284            self.snippet_index += 1;
285            if let Some(snippet) = snippet {
286                Some(snippet.clone())
287            } else {
288                None
289            }
290        } else {
291            None
292        }
293    }
294}
295
296impl ToString for SnippetParser<'_> {
297    fn to_string(&self) -> String {
298        let mut s = String::new();
299        for snip in self.get_snippets().unwrap() {
300            s.push_str(snip.to_string().as_str());
301            s.push_str("\n");
302        }
303        
304        s
305    }
306}
307
308#[derive(Clone, PartialEq, Debug)]
309/// Represents a snippet, with a `title` and a `string`
310pub struct Snippet {
311    title: String,
312    s: String
313}
314
315// impl Display for Snippet {
316//     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
317//         write!(f, "-- {} --\n{}\n-- end --", self.title, self.s)
318//     }
319// }
320
321impl ToString for Snippet {
322    fn to_string(&self) -> String {
323        String::from(
324            format!("-- {} --\n{}\n-- end --", self.title, self.s)
325        )
326    }
327}
328
329impl Snippet {
330    /// Creates a new snippet from a title and a string
331    pub fn new(title: String, s: String) -> Snippet {
332        Snippet { title, s }
333    }
334    
335    /// Appends a string to the snippet
336    pub fn append(&mut self, s: &str) {
337        self.s += s;
338    }
339    
340    /// Gets the string from the snippet
341    pub fn get_string(&self) -> &str {
342        &self.s
343    }
344}