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
39pub(crate) trait Render {
40    fn render(&self, format_table: &FormatTable) -> String;
41}
42
43/// Returns the format [`Arguments`] for the input format string
44///
45/// There is not yet any documentation for how the format string works, that will come soon in the repository wiki
46pub 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/// Renders the input [`Arguments`], replacing variables with values using the `format_table` input
52#[inline(always)]
53pub fn format(args: &Arguments, format_table: &FormatTable) -> String {
54    args.render(format_table)
55}
56
57/// Helper function around [`format_args()`] and [`format()`]
58pub 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}