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