Skip to main content

lunar_lib/
formatter.rs

1use crate::formatter::lexer::lex_str;
2
3#[derive(Debug, Clone, Copy)]
4pub enum ErrorKind {
5    /// A bad closure was discovered, like an unmatched `{` or `}`
6    BadClosure(&'static str),
7
8    /// Something was defined twice, like `{content<prefix<prefix_again}`
9    DoubleDefinition(&'static str),
10}
11
12#[derive(Debug, thiserror::Error)]
13#[error("{kind:?}")]
14pub struct TemplateError {
15    kind: ErrorKind,
16}
17
18impl TemplateError {
19    #[must_use] 
20    pub fn new(kind: ErrorKind) -> Self {
21        Self { kind }
22    }
23
24    #[must_use] 
25    pub fn kind(&self) -> ErrorKind {
26        self.kind
27    }
28}
29
30mod block;
31mod condition;
32mod lexer;
33mod tag;
34
35mod template;
36pub use template::{Template, TemplateOwned};
37
38mod format_table;
39pub use format_table::*;
40
41mod taggable;
42pub use taggable::*;
43
44trait Render {
45    fn render(&self, format_table: &FormatTable) -> String;
46}
47
48/// Returns the format [`Arguments`] for the input format string
49///
50/// There is not yet any documentation for how the format string works, that will come soon in the repository wiki
51pub fn parse_template(format_string: &str) -> Result<Template<'_>, TemplateError> {
52    let lexes = lex_str(format_string);
53    Template::from_lex(lexes)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn template_equality() {
62        let template = Template::try_from("Hello, world!").unwrap();
63        let result = FormatTable::new().render(&template);
64        assert_eq!(result, "Hello, world!");
65    }
66
67    #[test]
68    fn template_arguments_to_string_equality() {
69        const FORMAT_STR: &str = r"{$tag_block} ${block_tag} {{{nested_block}}} \{escaped_block\} \$escaped_tag {$weird\ tag} {fully_conditioned_block@condition<prefix>postfix?fallback}";
70
71        let template = parse_template(FORMAT_STR).unwrap();
72        assert_eq!(template.to_string(), FORMAT_STR);
73    }
74
75    #[test]
76    fn template_simple_tag() {
77        let table = FormatTable::from([("tag", "Hello, world!")]);
78        let template = Template::try_from("$tag").unwrap();
79        let result = table.render(&template);
80        assert_eq!(result, "Hello, world!");
81    }
82
83    #[test]
84    fn template_block_tag() {
85        let table = FormatTable::from([("tag", "Hello, world!")]);
86        let template = Template::try_from("${tag}").unwrap();
87        let result = table.render(&template);
88        assert_eq!(result, "Hello, world!");
89    }
90
91    #[test]
92    fn template_double_tag() {
93        let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
94        let template = Template::try_from("$foo$bar").unwrap();
95        let result = table.render(&template);
96        assert_eq!(result, "foobar");
97    }
98
99    #[test]
100    fn template_tag_ends_at_spaces() {
101        let table = FormatTable::from([("foo", "foo"), ("bar", "bar")]);
102        let template = Template::try_from("$foo $bar").unwrap();
103        let result = table.render(&template);
104        assert_eq!(result, "foo bar");
105    }
106
107    #[test]
108    fn template_recursive_block_tag() {
109        let table = FormatTable::from([("foo", "bar"), ("bar", "Hello, world!")]);
110        let template = Template::try_from("${$foo}").unwrap();
111        let result = table.render(&template);
112        assert_eq!(result, "Hello, world!");
113    }
114
115    #[test]
116    fn template_conditional_block() {
117        let template = Template::try_from("{Hello, world!@$invalid}").unwrap();
118        let result = FormatTable::new().render(&template);
119        assert_eq!(result, "");
120    }
121
122    #[test]
123    fn template_inverted_conditional_block() {
124        let template = Template::try_from("{Hello, world!@!$invalid}").unwrap();
125        let result = FormatTable::new().render(&template);
126        assert_eq!(result, "Hello, world!");
127    }
128
129    #[test]
130    fn template_double_invert() {
131        let template = Template::try_from("{Hello, world!@!!$invalid}").unwrap();
132        let result = FormatTable::new().render(&template);
133        assert_eq!(result, "");
134    }
135
136    #[test]
137    fn template_double_definition_fails() {
138        let result = Template::try_from("{content<prefix>suffix?fallback<prefix_again}");
139
140        match result {
141            Err(err) if matches!(err.kind(), ErrorKind::DoubleDefinition(_)) => (),
142            _ => panic!("Unexpected result: {result:?}"),
143        }
144    }
145
146    #[test]
147    fn template_prefix_and_suffix() {
148        let template = Template::try_from("{content<prefix > suffix}").unwrap();
149        let result = FormatTable::new().render(&template);
150        assert_eq!(result, "prefix content suffix");
151    }
152
153    #[test]
154    fn template_no_content_no_prefix_and_suffix() {
155        let template = Template::try_from("{$empty<prefix > suffix}").unwrap();
156        let result = FormatTable::new().render(&template);
157        assert_eq!(result, "");
158    }
159
160    #[test]
161    fn template_fallback() {
162        let template = Template::try_from("{$invalid?fallback}").unwrap();
163        let result = FormatTable::new().render(&template);
164        assert_eq!(result, "fallback");
165    }
166
167    #[test]
168    fn template_fallback_uses_prefix_and_suffix() {
169        let template = Template::try_from("{$empty<prefix > suffix?fallback}").unwrap();
170        let result = FormatTable::new().render(&template);
171        assert_eq!(result, "prefix fallback suffix");
172    }
173
174    #[test]
175    fn template_fallback_condition() {
176        let template = Template::try_from("{$invalid?fallback@$invalid}").unwrap();
177        let result = FormatTable::new().render(&template);
178        assert_eq!(result, "");
179    }
180
181    #[test]
182    fn template_or_condition() {
183        let table = FormatTable::from([("a", "foo")]);
184        let template = Template::try_from("{Hello, world!@$invalid||$a}").unwrap();
185        let result = table.render(&template);
186        assert_eq!(result, "Hello, world!");
187    }
188
189    #[test]
190    fn template_and_condition() {
191        let table = FormatTable::from([("a", "foo"), ("b", "bar")]);
192        let template = Template::try_from("{Hello, world!@$a&&$b}").unwrap();
193        let result = table.render(&template);
194        assert_eq!(result, "Hello, world!");
195    }
196
197    #[test]
198    fn template_nand_condition() {
199        let table = FormatTable::from([("a", "foo")]);
200        let template = Template::try_from("{Hello, world!@$a!&$invalid}").unwrap();
201        let result = table.render(&template);
202        assert_eq!(result, "");
203    }
204
205    #[test]
206    fn template_nor_condition() {
207        let template = Template::try_from("{Hello, world!@$invalid!|$invalid}").unwrap();
208        let result = FormatTable::new().render(&template);
209        assert_eq!(result, "");
210    }
211
212    #[test]
213    fn template_verify_conditional_left_to_right() {
214        let table = FormatTable::from([("a", "foo")]);
215        let template = Template::try_from("{Hello, world!@$invalid&&$invalid||$a&&$a}").unwrap();
216        let result = table.render(&template);
217        assert_eq!(result, "Hello, world!");
218    }
219}