text_placeholder 0.5.1

A flexible text template engine
Documentation
enum State {
    Text,
    Placeholder,
}

#[derive(Clone, PartialEq, Debug)]
pub enum Token<'t> {
    Text(&'t str),
    Placeholder(&'t str),
}

pub struct TokenIterator<'t> {
    text: &'t str,
    state: State,
    start: &'t str,
    end: &'t str,
}

impl<'t> TokenIterator<'t> {
    pub fn new(text: &'t str, start: &'t str, end: &'t str) -> Self {
        Self {
            text,
            start,
            end,
            state: State::Text,
        }
    }

    fn parse_text(&mut self) -> Token<'t> {
        let token: Token;

        if let Some(placeholder_index) = self.text.find(self.start) {
            token = Token::Text(&self.text[..placeholder_index]);
            self.text = &self.text[placeholder_index..];
            self.state = State::Placeholder;
        } else {
            token = Token::Text(self.text);
            self.text = "";
        }

        token
    }

    fn parse_placeholder(&mut self) -> Token<'t> {
        let token: Token;
        self.state = State::Text;

        if let Some(placeholder_index) = self.text.find(self.end) {
            token = Token::Placeholder(
                self.text[self.start.len()..placeholder_index]
                    .trim_start_matches(' ')
                    .trim_end_matches(' '),
            );
            let new_position = placeholder_index + self.end.len();
            self.text = &self.text[new_position..];
        } else {
            token = Token::Text(self.text);
            self.text = "";
        }

        token
    }
}

impl<'t> Iterator for TokenIterator<'t> {
    type Item = Token<'t>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.text.is_empty() {
            return None;
        }

        match self.state {
            State::Text => Some(self.parse_text()),
            State::Placeholder => Some(self.parse_placeholder()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Token, TokenIterator};
    extern crate alloc;
    use alloc::vec::Vec;

    #[test]
    fn test_no_boundaries_present() {
        let tokens: Vec<Token> = TokenIterator::new("hello world", "[", "]").collect();
        assert_eq!(tokens, vec![Token::Text("hello world")]);
    }

    #[test]
    fn test_boundary_begging_of_line() {
        let tokens: Vec<Token> = TokenIterator::new("[placeholder] text", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text(""),
                Token::Placeholder("placeholder"),
                Token::Text(" text")
            ]
        );
    }

    #[test]
    fn test_boundary_middle_of_line() {
        let tokens: Vec<Token> = TokenIterator::new("text [placeholder] text", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text("text "),
                Token::Placeholder("placeholder"),
                Token::Text(" text")
            ]
        );
    }

    #[test]
    fn test_boundary_end_of_line() {
        let tokens: Vec<Token> = TokenIterator::new("text [placeholder]", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Placeholder("placeholder")]
        );
    }

    #[test]
    fn test_boundary_multiple_boundaries() {
        let tokens: Vec<Token> = TokenIterator::new(
            "[placeholder] text [placeholder] test [placeholder]",
            "[",
            "]",
        )
        .collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text(""),
                Token::Placeholder("placeholder"),
                Token::Text(" text "),
                Token::Placeholder("placeholder"),
                Token::Text(" test "),
                Token::Placeholder("placeholder")
            ]
        );
    }

    #[test]
    fn test_missing_boundary_start() {
        let tokens: Vec<Token> = TokenIterator::new("text placeholder]", "[", "]").collect();
        assert_eq!(tokens, vec![Token::Text("text placeholder]")]);
    }

    #[test]
    fn test_missing_boundary_end() {
        let tokens: Vec<Token> = TokenIterator::new("text [placeholder", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Text("[placeholder")]
        );
    }

    #[test]
    fn test_boundary_and_missing_boundaries() {
        let tokens: Vec<Token> =
            TokenIterator::new("text [placeholder] [placeholder", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text("text "),
                Token::Placeholder("placeholder"),
                Token::Text(" "),
                Token::Text("[placeholder")
            ]
        );
    }

    #[test]
    fn test_multiple_chars_boundary_start_of_line() {
        let tokens: Vec<Token> = TokenIterator::new("{{placeholder}} text", "{{", "}}").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text(""),
                Token::Placeholder("placeholder"),
                Token::Text(" text")
            ]
        );
    }

    #[test]
    fn test_multiple_chars_boundary_middle_of_line() {
        let tokens: Vec<Token> =
            TokenIterator::new("text {{placeholder}} text", "{{", "}}").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text("text "),
                Token::Placeholder("placeholder"),
                Token::Text(" text")
            ]
        );
    }

    #[test]
    fn test_multiple_chars_boundary_end_of_line() {
        let tokens: Vec<Token> = TokenIterator::new("text {{placeholder}}", "{{", "}}").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Placeholder("placeholder")]
        );
    }

    #[test]
    fn test_multiple_chars_boundary_multiple_boundaries() {
        let tokens: Vec<Token> = TokenIterator::new(
            "{{placeholder}} text {{placeholder}} test {{placeholder}}",
            "{{",
            "}}",
        )
        .collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text(""),
                Token::Placeholder("placeholder"),
                Token::Text(" text "),
                Token::Placeholder("placeholder"),
                Token::Text(" test "),
                Token::Placeholder("placeholder")
            ]
        );
    }

    #[test]
    fn test_multiple_chars_missing_boundary_start() {
        let tokens: Vec<Token> = TokenIterator::new("text placeholder}}", "{{", "}}").collect();
        assert_eq!(tokens, vec![Token::Text("text placeholder}}")]);
    }

    #[test]
    fn test_multiple_chars_missing_boundary_end() {
        let tokens: Vec<Token> = TokenIterator::new("text {{placeholder", "{{", "}}").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Text("{{placeholder")]
        );
    }

    #[test]
    fn test_multiple_chars_boundary_and_missing_boundaries() {
        let tokens: Vec<Token> =
            TokenIterator::new("text {{placeholder}} {{placeholder", "{{", "}}").collect();
        assert_eq!(
            tokens,
            vec![
                Token::Text("text "),
                Token::Placeholder("placeholder"),
                Token::Text(" "),
                Token::Text("{{placeholder")
            ]
        );
    }

    #[test]
    fn test_trim_placeholdler_prefix_space() {
        let tokens: Vec<Token> = TokenIterator::new("text [ placeholder]", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Placeholder("placeholder")]
        );
    }

    #[test]
    fn test_trim_placeholdler_suffix_space() {
        let tokens: Vec<Token> = TokenIterator::new("text [placeholder ]", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Placeholder("placeholder")]
        );
    }

    #[test]
    fn test_trim_placeholdler_presuffix_space() {
        let tokens: Vec<Token> = TokenIterator::new("text [ placeholder ]", "[", "]").collect();
        assert_eq!(
            tokens,
            vec![Token::Text("text "), Token::Placeholder("placeholder")]
        );
    }
}