Skip to main content

lunar_lib/formatter/
template.rs

1use std::fmt::{self, Write};
2
3use crate::formatter::{
4    ErrorKind, FormatTable, Render, TemplateError,
5    block::{Block, BlockBuilder, BlockMode},
6    condition::ConditionalToken,
7    lexer::{Lex, lex_str},
8    tag::Tag,
9};
10
11/// Ready-to-render arguments for [`crate::formatter::format()`]
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Template<'a> {
14    args: Vec<TemplateItem<'a>>,
15}
16
17impl<'a> Template<'a> {
18    pub(super) fn from_lex(
19        lexes: impl IntoIterator<Item = Lex<'a>>,
20    ) -> Result<Template<'a>, TemplateError> {
21        let mut args = Vec::new();
22        let mut escaped = false;
23
24        let mut stack: Vec<ParserEntry<'_>> = Vec::new();
25
26        for lex in lexes {
27            if escaped {
28                push_arg(&mut stack, &mut args, TemplateItem::Text(lex.to_str()));
29                escaped = false;
30            } else {
31                match lex {
32                Lex::BlockStart => stack.push(ParserEntry::Block(BlockBuilder::default())),
33                Lex::BlockEnd => end_block(&mut stack, &mut args)?,
34                Lex::Variable => {
35                    end_tag(&mut stack, &mut args);
36                    stack.push(ParserEntry::Tag(Tag::default()));
37                }
38                Lex::Conditional | Lex::Prefix | Lex::Suffix | Lex::Fallback => block_mode_switch(
39                    &mut stack,
40                    &mut args,
41                    lex.to_block_mode()
42                        .expect("The lex was already matched on block modes. This should not fail"),
43                )?,
44                Lex::Or | Lex::And | Lex::Not => push_conditional_token(
45                    &mut stack,
46                    &mut args,
47                    lex.to_condition_token().expect(
48                        "The lex was already matched on conditional tokens. This should not fail",
49                    ),
50                ),
51                Lex::Space => {
52                    end_tag(&mut stack, &mut args);
53                    push_arg(&mut stack, &mut args, TemplateItem::Text(lex.to_str()));
54                }
55                Lex::Escape => escaped = true,
56                Lex::Text(str) => push_arg(&mut stack, &mut args, TemplateItem::Text(str)),
57            }
58            }
59        }
60
61        end_tag(&mut stack, &mut args);
62
63        if !stack.is_empty() {
64            return Err(TemplateError::new(ErrorKind::BadClosure(
65                "Completed parsing all lexes, but the stack was not empty. This means a tag or block was left unclosed",
66            )));
67        }
68
69        Ok(Template { args })
70    }
71}
72
73impl<'a> TryFrom<&'a str> for Template<'a> {
74    type Error = TemplateError;
75
76    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
77        let lexes = lex_str(value);
78        Template::from_lex(lexes)
79    }
80}
81
82impl Render for Template<'_> {
83    fn render(&self, format_table: &FormatTable) -> String {
84        self.args.render(format_table)
85    }
86}
87
88impl fmt::Display for Template<'_> {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        for arg in &self.args {
91            arg.fmt(f)?;
92        }
93        Ok(())
94    }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub(super) enum TemplateItem<'a> {
99    Text(&'a str),
100    Tag(Tag<'a>),
101    Block(Block<'a>),
102}
103
104impl Render for TemplateItem<'_> {
105    fn render(&self, format_table: &super::FormatTable) -> String {
106        match self {
107            TemplateItem::Text(str) => str.to_string(),
108            TemplateItem::Tag(tag) => tag.render(format_table),
109            TemplateItem::Block(block) => block.render(format_table),
110        }
111    }
112}
113
114impl Render for [TemplateItem<'_>] {
115    fn render(&self, format_table: &FormatTable) -> String {
116        self.iter().map(|arg| arg.render(format_table)).collect()
117    }
118}
119
120enum ParserEntry<'a> {
121    Tag(Tag<'a>),
122    Block(BlockBuilder<'a>),
123}
124
125#[inline(always)]
126fn push_arg<'a>(
127    stack: &mut Vec<ParserEntry<'a>>,
128    args: &mut Vec<TemplateItem<'a>>,
129    arg: TemplateItem<'a>,
130) {
131    match stack.last_mut() {
132        Some(ParserEntry::Block(block_builder)) => {
133            block_builder.push_arg(arg);
134        }
135        Some(ParserEntry::Tag(tag)) => tag.args.push(arg),
136        None => args.push(arg),
137    }
138}
139
140fn push_conditional_token<'a>(
141    stack: &mut Vec<ParserEntry<'a>>,
142    args: &mut Vec<TemplateItem<'a>>,
143    token: ConditionalToken<'a>,
144) {
145    if let Some(ParserEntry::Block(block)) = stack.last_mut() {
146        block.push_conditional_token(token);
147    } else {
148        push_arg(stack, args, TemplateItem::Text(token.to_str()));
149    }
150}
151
152fn end_tag<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<TemplateItem<'a>>) {
153    if let Some(ParserEntry::Tag(_)) = stack.last() {
154        let tag = match stack.pop().unwrap() {
155            ParserEntry::Tag(tag) => tag,
156            _ => unreachable!(),
157        };
158
159        push_arg(stack, args, TemplateItem::Tag(tag));
160    }
161}
162
163fn end_tag_if_in_block<'a>(stack: &mut Vec<ParserEntry<'a>>, args: &mut Vec<TemplateItem<'a>>) {
164    if let Some(ParserEntry::Tag(_)) = stack.last()
165        && let Some(ParserEntry::Block(_)) = stack.get(stack.len() - 2)
166    {
167        let tag = match stack.pop().unwrap() {
168            ParserEntry::Tag(tag) => tag,
169            _ => unreachable!(),
170        };
171        push_arg(stack, args, TemplateItem::Tag(tag));
172    }
173}
174
175fn end_block<'a>(
176    stack: &mut Vec<ParserEntry<'a>>,
177    args: &mut Vec<TemplateItem<'a>>,
178) -> Result<(), TemplateError> {
179    end_tag(stack, args);
180
181    let block = match stack.pop() {
182        Some(ParserEntry::Block(block)) => block,
183        Some(_) => {
184            panic!("'}}' was found unescaped but it didn't close a block");
185        }
186        None => {
187            return Err(TemplateError::new(ErrorKind::BadClosure(
188                "'}' was found unescaped with nothing to close",
189            )));
190        }
191    };
192
193    push_arg(stack, args, TemplateItem::Block(block.build()));
194
195    Ok(())
196}
197
198fn block_mode_switch<'a>(
199    stack: &mut Vec<ParserEntry<'a>>,
200    args: &mut Vec<TemplateItem<'a>>,
201    mode: BlockMode,
202) -> Result<(), TemplateError> {
203    end_tag_if_in_block(stack, args);
204
205    if let Some(ParserEntry::Block(block)) = stack.last_mut() {
206        block.set_mode(mode)
207    } else {
208        args.push(TemplateItem::Text(mode.to_str()));
209        Ok(())
210    }
211}
212
213pub(super) fn write_escaped(f: &mut fmt::Formatter<'_>, s: &str, tag_context: bool) -> fmt::Result {
214    for byte in s.bytes() {
215        if byte == b' ' && !tag_context {
216            f.write_char(' ')?;
217            continue;
218        }
219
220        if matches!(
221            byte,
222            b'{' | b'}' | b'$' | b'@' | b'<' | b'>' | b'?' | b'|' | b'&' | b'!' | b'\\' | b' '
223        ) {
224            f.write_char('\\')?;
225        }
226
227        f.write_char(byte as char)?;
228    }
229    Ok(())
230}
231
232impl fmt::Display for TemplateItem<'_> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        match self {
235            TemplateItem::Text(s) => write_escaped(f, s, false),
236            TemplateItem::Tag(tag) => tag.fmt(f),
237            TemplateItem::Block(block) => block.fmt(f),
238        }
239    }
240}
241
242#[derive(Debug)]
243pub struct TemplateOwned {
244    args: Template<'static>,
245    string: Box<str>,
246}
247
248impl TemplateOwned {
249    pub fn new(s: impl Into<String>) -> Result<Self, TemplateError> {
250        let string: Box<str> = s.into().into_boxed_str();
251        let ptr: *const str = &raw const *string;
252
253        // SAFETY:
254        // - 'string' is heap-allocated, moving 'ArgumentsOwned' never moves the string
255        // - No mutable access is ever given to the 'string' field
256        // - 'args' static lifetime is never exposed
257        let args = unsafe {
258            let str_ref: &'static str = &*ptr;
259            let lexes = lex_str(str_ref);
260            Template::from_lex(lexes)?
261        };
262
263        Ok(Self { args, string })
264    }
265
266    #[must_use]
267    pub fn as_arguments(&self) -> &Template<'_> {
268        &self.args
269    }
270
271    #[must_use]
272    pub fn as_str(&self) -> &str {
273        &self.string
274    }
275}
276
277impl Clone for TemplateOwned {
278    fn clone(&self) -> Self {
279        Self::new(self.string.clone()).expect("Previously valid string cannot suddenly fail")
280    }
281}
282
283impl PartialEq for TemplateOwned {
284    fn eq(&self, other: &Self) -> bool {
285        self.args == other.args
286    }
287}
288
289impl Eq for TemplateOwned {}
290
291impl From<Template<'_>> for TemplateOwned {
292    fn from(value: Template<'_>) -> Self {
293        TemplateOwned::new(value.to_string()).expect("Arguments to_string are always valid")
294    }
295}
296
297#[cfg(feature = "serde")]
298impl serde::Serialize for TemplateOwned {
299    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
300        s.serialize_str(&self.string)
301    }
302}
303
304#[cfg(feature = "serde")]
305impl<'de> serde::Deserialize<'de> for TemplateOwned {
306    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
307        let s = String::deserialize(d)?;
308        TemplateOwned::new(s).map_err(serde::de::Error::custom)
309    }
310}
311
312#[cfg(feature = "serde")]
313impl serde::Serialize for Template<'_> {
314    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
315        s.serialize_str(&self.to_string())
316    }
317}