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 FormatError {
15 kind: ErrorKind,
16}
17
18impl FormatError {
19 pub fn new(kind: ErrorKind) -> Self {
20 Self { kind }
21 }
22
23 pub fn kind(&self) -> ErrorKind {
24 self.kind
25 }
26}
27
28mod block;
29mod condition;
30mod lexer;
31mod tag;
32
33mod format_args;
34pub use format_args::Arguments;
35
36mod format_table;
37pub use format_table::*;
38
39mod taggable;
40pub use taggable::*;
41
42pub(crate) trait Render {
43 fn render(&self, format_table: &FormatTable) -> String;
44}
45
46pub fn format_args<'a>(format_string: &'a str) -> Result<Arguments<'a>, FormatError> {
50 let lexes = lex_str(format_string);
51 Arguments::from_lex(lexes)
52}
53
54#[inline(always)]
56pub fn format(args: &Arguments, format_table: &FormatTable) -> String {
57 args.render(format_table)
58}
59
60pub fn format_str(
62 format_string: impl AsRef<str>,
63 format_table: &FormatTable,
64) -> Result<String, FormatError> {
65 Ok(format(&format_args(format_string.as_ref())?, format_table))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn format_equality() {
74 let table = FormatTable::new();
75 let result = format_str("Hello, world!", &table).unwrap();
76 assert_eq!(result, "Hello, world!");
77 }
78
79 #[test]
80 fn format_simple_tag() {
81 let mut table = FormatTable::new();
82 table.add_entry("tag", "Hello, world!");
83 let result = format_str("$tag", &table).unwrap();
84 assert_eq!(result, "Hello, world!");
85 }
86
87 #[test]
88 fn format_block_tag() {
89 let mut table = FormatTable::new();
90 table.add_entry("tag", "Hello, world!");
91 let result = format_str("${tag}", &table).unwrap();
92 assert_eq!(result, "Hello, world!");
93 }
94
95 #[test]
96 fn format_double_tag() {
97 let mut table = FormatTable::new();
98 table.add_entry("foo", "foo");
99 table.add_entry("bar", "bar");
100 let result = format_str("$foo$bar", &table).unwrap();
101 assert_eq!(result, "foobar");
102 }
103
104 #[test]
105 fn format_tag_ends_at_spaces() {
106 let mut table = FormatTable::new();
107 table.add_entry("foo", "foo");
108 table.add_entry("bar", "bar");
109 let result = format_str("$foo $bar", &table).unwrap();
110 assert_eq!(result, "foo bar");
111 }
112
113 #[test]
114 fn format_recursive_block_tag() {
115 let mut table = FormatTable::new();
116 table.add_entry("foo", "bar");
117 table.add_entry("bar", "Hello, world!");
118 let result = format_str("${$foo}", &table).unwrap();
119 assert_eq!(result, "Hello, world!");
120 }
121
122 #[test]
123 fn format_conditional_block() {
124 let table = FormatTable::new();
125 let result = format_str("{Hello, world!@$invalid}", &table).unwrap();
126 assert_eq!(result, "");
127 }
128
129 #[test]
130 fn format_inverted_conditional_block() {
131 let table = FormatTable::new();
132 let result = format_str("{Hello, world!@!$invalid}", &table).unwrap();
133 assert_eq!(result, "Hello, world!");
134 }
135
136 #[test]
137 fn format_double_invert() {
138 let table = FormatTable::new();
139 let result = format_str("{Hello, world!@!!$invalid}", &table).unwrap();
140 assert_eq!(result, "");
141 }
142
143 #[test]
144 fn format_double_definition_fails() {
145 let table = FormatTable::new();
146 let result = format_str("{content<prefix>suffix?fallback<prefix_again}", &table);
147
148 match result {
149 Err(err) if matches!(err.kind(), ErrorKind::DoubleDefinition(_)) => {}
150 _ => panic!("Unexpected result: {result:?}"),
151 };
152 }
153
154 #[test]
155 fn format_prefix_and_suffix() {
156 let table = FormatTable::new();
157 let result = format_str("{content<prefix > suffix}", &table).unwrap();
158 assert_eq!(result, "prefix content suffix");
159 }
160
161 #[test]
162 fn format_no_content_no_prefix_and_suffix() {
163 let table = FormatTable::new();
164 let result = format_str("{$empty<prefix > suffix}", &table).unwrap();
165 assert_eq!(result, "");
166 }
167
168 #[test]
169 fn format_fallback() {
170 let table = FormatTable::new();
171 let result = format_str("{$invalid?fallback}", &table).unwrap();
172 assert_eq!(result, "fallback");
173 }
174
175 #[test]
176 fn format_fallback_uses_prefix_and_suffix() {
177 let table = FormatTable::new();
178 let result = format_str("{$empty<prefix > suffix?fallback}", &table).unwrap();
179 assert_eq!(result, "prefix fallback suffix");
180 }
181
182 #[test]
183 fn format_fallback_condition() {
184 let table = FormatTable::new();
185 let result = format_str("{$invalid?fallback@$invalid}", &table).unwrap();
186 assert_eq!(result, "");
187 }
188
189 #[test]
190 fn format_or_condition() {
191 let mut table = FormatTable::new();
192 table.add_entry("a", "foo");
193 let result = format_str("{Hello, world!@$invalid||$a}", &table).unwrap();
194 assert_eq!(result, "Hello, world!");
195 }
196
197 #[test]
198 fn format_and_condition() {
199 let mut table = FormatTable::new();
200 table.add_entry("a", "foo");
201 table.add_entry("b", "bar");
202 let result = format_str("{Hello, world!@$a&&$b}", &table).unwrap();
203 assert_eq!(result, "Hello, world!");
204 }
205
206 #[test]
207 fn format_nand_condition() {
208 let mut table = FormatTable::new();
209 table.add_entry("a", "foo");
210 let result = format_str("{Hello, world!@$a!&$invalid}", &table).unwrap();
211 assert_eq!(result, "");
212 }
213
214 #[test]
215 fn format_nor_condition() {
216 let table = FormatTable::new();
217 let result = format_str("{Hello, world!@$invalid!|$invalid}", &table).unwrap();
218 assert_eq!(result, "");
219 }
220
221 #[test]
222 fn format_verify_conditional_left_to_right() {
223 let mut table = FormatTable::new();
224 table.add_entry("a", "foo");
225 let result = format_str("{Hello, world!@$invalid&&$invalid||$a&&$a}", &table).unwrap();
226 assert_eq!(result, "Hello, world!");
227 }
228}