|
/// Parsed fragment of a document, containing borrowed slices of text |
|
/// from the original document itself. |
|
#[derive(Debug, PartialEq)] |
|
pub enum DocPart<'a> { |
|
/// Placeholder markup containing variable or expression that can be |
|
/// interpolated with values. |
|
Tag(&'a str, Vec<&'a str>), |
|
|
|
/// Plain text. |
|
Run(&'a str), |
|
} |
|
|
|
const TAG_START: &str = "{{"; |
|
const TAG_END: &str = "}}"; |
|
const TAG_EXPR_START: &str = "{%"; |
|
const TAG_EXPR_END: &str = "%}"; |
|
|
|
/// Parses a string into a series of either 'tags' (markup) or 'runs' |
|
/// (plaintext). The tags can be processed further. |
|
pub fn parse_text<'a>(text: &'a str) -> Vec<DocPart<'a>> { |
|
let slice_end = text.len(); |
|
let mut slice_from = 0; |
|
let mut slice_to = 0; |
|
let mut cur_word; |
|
let mut split_at; |
|
let mut parts = Vec::new(); |
|
|
|
// The base case (empty string) is taken care of here. |
|
while slice_to < slice_end { |
|
slice_to += 1; |
|
cur_word = &text[slice_from..slice_to]; |
|
|
|
if starts_tag(cur_word) && ends_tag(cur_word) { |
|
parts.push(word_to_tag(cur_word)); |
|
slice_from = slice_to; |
|
// A run has just finished and a new tag is about to start: |
|
} else if (cur_word.ends_with(TAG_START) && cur_word != TAG_START) |
|
|| (cur_word.ends_with(TAG_EXPR_START) && cur_word != TAG_EXPR_START) |
|
{ |
|
split_at = slice_to - slice_from - 2; |
|
|
|
/* Create the run out of all but the last two chars of the |
|
current word. */ |
|
parts.push(DocPart::Run(&cur_word[..split_at])); |
|
slice_from += split_at; |
|
} |
|
} |
|
|
|
/* Can now only be a Run. If it had been a Tag we would've handled it |
|
above. */ |
|
if slice_from < slice_to { |
|
parts.push(DocPart::Run(&text[slice_from..slice_to])); |
|
} |
|
|
|
parts |
|
} |
|
|
|
#[inline] |
|
fn starts_tag(word: &str) -> bool { |
|
word.starts_with(TAG_START) || word.starts_with(TAG_EXPR_START) |
|
} |
|
|
|
#[inline] |
|
fn ends_tag(word: &str) -> bool { |
|
(word.starts_with(TAG_START) && word.ends_with(TAG_END)) |
|
|| (word.starts_with(TAG_EXPR_START) && word.ends_with(TAG_EXPR_END)) |
|
} |
|
|
|
fn word_to_tag<'a>(word: &'a str) -> DocPart<'a> { |
|
let tag_chars: &[_] = &['{', '}', '%', ' ']; |
|
let words: Vec<&str> = word.trim_matches(tag_chars).split(' ').collect(); |
|
|
|
DocPart::Tag(words[0], words[1..].to_vec()) |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
#[test] |
|
fn test_parse_text() { |
|
use super::parse_text; |
|
use super::DocPart::*; |
|
|
|
let result = vec![ |
|
Run("Hello, "), |
|
Tag("World", vec![]), |
|
Run("! "), |
|
Tag("if", vec!["a", "b"]), |
|
Run(". Meh. "), |
|
Run("{{derp}"), |
|
]; |
|
|
|
assert_eq!( |
|
parse_text("Hello, {{World}}! {% if a b %}. Meh. {{derp}"), |
|
result |
|
); |
|
} |
|
} |