Skip to content

Instantly share code, notes, and snippets.

@yawaramin
Last active July 30, 2019 00:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yawaramin/44ba9677830bb5a0ec40 to your computer and use it in GitHub Desktop.
Save yawaramin/44ba9677830bb5a0ec40 to your computer and use it in GitHub Desktop.
Parse text tags (Rust)
/// 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
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment