1use crate::formatter::lexer::lex_str;
2
3#[derive(Debug, Clone, Copy)]
4pub enum ErrorKind {
5 BadClosure(&'static str),
7
8 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
48pub 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}