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 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
46/// Returns the format [`Arguments`] for the input format string
47///
48/// There is not yet any documentation for how the format string works, that will come soon in the repository wiki
49pub 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/// Renders the input [`Arguments`], replacing variables with values using the `format_table` input
55#[inline(always)]
56pub fn format(args: &Arguments, format_table: &FormatTable) -> String {
57    args.render(format_table)
58}
59
60/// Helper function around [`format_args()`] and [`format()`]
61pub 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}