lunar-lib 0.8.0

Common utilities for lunar applications
Documentation
use crate::formatter::lexer::lex_str;

#[derive(Debug, Clone, Copy)]
pub enum ErrorKind {
    /// A bad closure was discovered, like an unmatched `{` or `}`
    BadClosure(&'static str),

    /// Something was defined twice, like `{content<prefix<prefix_again}`
    DoubleDefinition(&'static str),
}

#[derive(Debug, thiserror::Error)]
#[error("{kind:?}")]
pub struct TemplateError {
    kind: ErrorKind,
}

impl TemplateError {
    pub fn new(kind: ErrorKind) -> Self {
        Self { kind }
    }

    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

mod block;
mod condition;
mod lexer;
mod tag;

mod template;
pub use template::{Template, TemplateOwned};

mod format_table;
pub use format_table::*;

mod taggable;
pub use taggable::*;

trait Render {
    fn render(&self, format_table: &FormatTable) -> String;
}

/// Returns the format [`Arguments`] for the input format string
///
/// There is not yet any documentation for how the format string works, that will come soon in the repository wiki
pub fn parse_template<'a>(format_string: &'a str) -> Result<Template<'a>, TemplateError> {
    let lexes = lex_str(format_string);
    Template::from_lex(lexes)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn template_equality() {
        let template = Template::try_from("Hello, world!").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_arguments_to_string_equality() {
        const FORMAT_STR: &str = r#"{$tag_block} ${block_tag} {{{nested_block}}} \{escaped_block\} \$escaped_tag {$weird\ tag} {fully_conditioned_block@condition<prefix>postfix?fallback}"#;

        let template = parse_template(FORMAT_STR).unwrap();
        assert_eq!(template.to_string(), FORMAT_STR)
    }

    #[test]
    fn template_simple_tag() {
        let table = FormatTable::from([("tag", "Hello, world!")]);
        let template = Template::try_from("$tag").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_block_tag() {
        let table = FormatTable::from([("tag", "Hello, world!")]);
        let template = Template::try_from("${tag}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_double_tag() {
        let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
        let template = Template::try_from("$foo$bar").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "foobar");
    }

    #[test]
    fn template_tag_ends_at_spaces() {
        let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
        let template = Template::try_from("$foo $bar").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "foo bar");
    }

    #[test]
    fn template_recursive_block_tag() {
        let table = FormatTable::from([("foo", "bar"), ("bar", "Hello, world!")]);
        let template = Template::try_from("${$foo}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_conditional_block() {
        let template = Template::try_from("{Hello, world!@$invalid}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_inverted_conditional_block() {
        let template = Template::try_from("{Hello, world!@!$invalid}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_double_invert() {
        let template = Template::try_from("{Hello, world!@!!$invalid}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_double_definition_fails() {
        let result = Template::try_from("{content<prefix>suffix?fallback<prefix_again}");

        match result {
            Err(err) if matches!(err.kind(), ErrorKind::DoubleDefinition(_)) => (),
            _ => panic!("Unexpected result: {result:?}"),
        };
    }

    #[test]
    fn template_prefix_and_suffix() {
        let template = Template::try_from("{content<prefix > suffix}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "prefix content suffix");
    }

    #[test]
    fn template_no_content_no_prefix_and_suffix() {
        let template = Template::try_from("{$empty<prefix > suffix}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_fallback() {
        let template = Template::try_from("{$invalid?fallback}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "fallback");
    }

    #[test]
    fn template_fallback_uses_prefix_and_suffix() {
        let template = Template::try_from("{$empty<prefix > suffix?fallback}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "prefix fallback suffix");
    }

    #[test]
    fn template_fallback_condition() {
        let template = Template::try_from("{$invalid?fallback@$invalid}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_or_condition() {
        let table = FormatTable::from([("a", "foo")]);
        let template = Template::try_from("{Hello, world!@$invalid||$a}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_and_condition() {
        let table = FormatTable::from([("a", "foo"), ("b", "bar")]);
        let template = Template::try_from("{Hello, world!@$a&&$b}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn template_nand_condition() {
        let table = FormatTable::from([("a", "foo")]);
        let template = Template::try_from("{Hello, world!@$a!&$invalid}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_nor_condition() {
        let template = Template::try_from("{Hello, world!@$invalid!|$invalid}").unwrap();
        let result = FormatTable::new().render(&template);
        assert_eq!(result, "");
    }

    #[test]
    fn template_verify_conditional_left_to_right() {
        let table = FormatTable::from([("a", "foo")]);
        let template = Template::try_from("{Hello, world!@$invalid&&$invalid||$a&&$a}").unwrap();
        let result = table.render(&template);
        assert_eq!(result, "Hello, world!");
    }
}