1use nom::{
2 branch::alt,
3 bytes::complete::{escaped_transform, tag, take_while, take_while1},
4 character::complete::{anychar, char},
5 combinator::{cut, eof, map, not, opt},
6 multi::{many0, separated_list0},
7 sequence::{preceded, separated_pair, terminated, tuple},
8 IResult, Parser,
9};
10
11use crate::errors::*;
12
13#[derive(Debug, PartialEq, Eq)]
14pub struct Arg<'a> {
15 pub key: &'a str,
16 pub val: &'a str,
17}
18
19#[derive(Debug, PartialEq, Eq)]
20pub struct Formatter<'a> {
21 pub name: &'a str,
22 pub args: Vec<Arg<'a>>,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub struct Placeholder<'a> {
27 pub name: &'a str,
28 pub formatter: Option<Formatter<'a>>,
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub enum Token<'a> {
33 Text(String),
34 Placeholder(Placeholder<'a>),
35 Icon(&'a str),
36 Recursive(FormatTemplate<'a>),
37}
38
39#[derive(Debug, PartialEq, Eq)]
40pub struct TokenList<'a>(pub Vec<Token<'a>>);
41
42#[derive(Debug, PartialEq, Eq)]
43pub struct FormatTemplate<'a>(pub Vec<TokenList<'a>>);
44
45#[derive(Debug, PartialEq, Eq)]
46enum PError<'a> {
47 Expected {
48 expected: char,
49 actual: Option<char>,
50 },
51 Other {
52 input: &'a str,
53 kind: nom::error::ErrorKind,
54 },
55}
56
57impl<'a> nom::error::ParseError<&'a str> for PError<'a> {
58 fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
59 Self::Other { input, kind }
60 }
61
62 fn append(_: &'a str, _: nom::error::ErrorKind, other: Self) -> Self {
63 other
64 }
65
66 fn from_char(input: &'a str, expected: char) -> Self {
67 let actual = input.chars().next();
68 Self::Expected { expected, actual }
69 }
70
71 fn or(self, other: Self) -> Self {
72 other
73 }
74}
75
76fn spaces(i: &str) -> IResult<&str, &str, PError> {
77 take_while(|x: char| x.is_ascii_whitespace())(i)
78}
79
80fn alphanum1(i: &str) -> IResult<&str, &str, PError> {
81 take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-')(i)
82}
83
84fn arg1(i: &str) -> IResult<&str, &str, PError> {
87 alt((
88 take_while1(|x: char| x.is_alphanumeric() || x == '_' || x == '-' || x == '.' || x == '%'),
89 preceded(
90 char('\''),
91 cut(terminated(take_while(|x: char| x != '\''), char('\''))),
92 ),
93 ))(i)
94}
95
96fn parse_arg(i: &str) -> IResult<&str, Arg, PError> {
98 map(
99 separated_pair(alphanum1, cut(char(':')), cut(arg1)),
100 |(key, val)| Arg { key, val },
101 )(i)
102}
103
104fn parse_args(i: &str) -> IResult<&str, Vec<Arg>, PError> {
107 let inner = separated_list0(preceded(spaces, char(',')), preceded(spaces, parse_arg));
108 preceded(
109 char('('),
110 cut(terminated(inner, preceded(spaces, char(')')))),
111 )(i)
112}
113
114fn parse_formatter(i: &str) -> IResult<&str, Formatter, PError> {
117 preceded(char('.'), cut(tuple((alphanum1, opt(parse_args)))))
118 .map(|(name, args)| Formatter {
119 name,
120 args: args.unwrap_or_default(),
121 })
122 .parse(i)
123}
124
125fn parse_placeholder(i: &str) -> IResult<&str, Placeholder, PError> {
128 preceded(char('$'), cut(tuple((alphanum1, opt(parse_formatter)))))
129 .map(|(name, formatter)| Placeholder { name, formatter })
130 .parse(i)
131}
132
133fn parse_string(i: &str) -> IResult<&str, String, PError> {
135 preceded(
136 not(eof),
137 escaped_transform(
138 take_while1(|x| x != '$' && x != '^' && x != '{' && x != '}' && x != '|' && x != '\\'),
139 '\\',
140 anychar,
141 ),
142 )(i)
143}
144
145fn parse_icon(i: &str) -> IResult<&str, &str, PError> {
147 preceded(char('^'), cut(preceded(tag("icon_"), alphanum1)))(i)
148}
149
150fn parse_recursive_template(i: &str) -> IResult<&str, FormatTemplate, PError> {
152 preceded(char('{'), cut(terminated(parse_format_template, char('}'))))(i)
153}
154
155fn parse_token_list(i: &str) -> IResult<&str, TokenList, PError> {
156 map(
157 many0(alt((
158 map(parse_string, Token::Text),
159 map(parse_placeholder, Token::Placeholder),
160 map(parse_icon, Token::Icon),
161 map(parse_recursive_template, Token::Recursive),
162 ))),
163 TokenList,
164 )(i)
165}
166
167fn parse_format_template(i: &str) -> IResult<&str, FormatTemplate, PError> {
168 map(separated_list0(char('|'), parse_token_list), FormatTemplate)(i)
169}
170
171pub fn parse_full(i: &str) -> Result<FormatTemplate> {
172 match parse_format_template(i) {
173 Ok((rest, template)) => {
174 if rest.is_empty() {
175 Ok(template)
176 } else {
177 Err(Error::new(format!(
178 "unexpected '{}'",
179 rest.chars().next().unwrap()
180 )))
181 }
182 }
183 Err(err) => Err(match err {
184 nom::Err::Incomplete(_) => unreachable!(),
185 nom::Err::Error(err) | nom::Err::Failure(err) => match err {
186 PError::Expected { expected, actual } => {
187 if let Some(actual) = actual {
188 Error::new(format!("expected '{expected}', got '{actual}'"))
189 } else {
190 Error::new(format!("expected '{expected}', got EOF"))
191 }
192 }
193 PError::Other { input, kind } => {
194 Error::new(format!("{kind:?} error near '{input}'"))
196 }
197 },
198 }),
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn arg() {
208 assert_eq!(
209 parse_arg("key:val,"),
210 Ok((
211 ",",
212 Arg {
213 key: "key",
214 val: "val"
215 }
216 ))
217 );
218 assert_eq!(
219 parse_arg("key:'val ue',"),
220 Ok((
221 ",",
222 Arg {
223 key: "key",
224 val: "val ue"
225 }
226 ))
227 );
228 assert_eq!(
229 parse_arg("key:'',"),
230 Ok((
231 ",",
232 Arg {
233 key: "key",
234 val: ""
235 }
236 ))
237 );
238 assert!(parse_arg("key:,").is_err());
239 }
240
241 #[test]
242 fn args() {
243 assert_eq!(
244 parse_args("(key:val)"),
245 Ok((
246 "",
247 vec![Arg {
248 key: "key",
249 val: "val"
250 }]
251 ))
252 );
253 assert_eq!(
254 parse_args("( abc:d , key:val )"),
255 Ok((
256 "",
257 vec![
258 Arg {
259 key: "abc",
260 val: "d",
261 },
262 Arg {
263 key: "key",
264 val: "val"
265 }
266 ]
267 ))
268 );
269 }
270
271 #[test]
272 fn formatter() {
273 assert_eq!(
274 parse_formatter(".str(key:val)"),
275 Ok((
276 "",
277 Formatter {
278 name: "str",
279 args: vec![Arg {
280 key: "key",
281 val: "val"
282 }]
283 }
284 ))
285 );
286 assert_eq!(
287 parse_formatter(".eng(w:3 , bin:true )"),
288 Ok((
289 "",
290 Formatter {
291 name: "eng",
292 args: vec![
293 Arg { key: "w", val: "3" },
294 Arg {
295 key: "bin",
296 val: "true"
297 }
298 ]
299 }
300 ))
301 );
302 }
303
304 #[test]
305 fn placeholder() {
306 assert_eq!(
307 parse_placeholder("$key"),
308 Ok((
309 "",
310 Placeholder {
311 name: "key",
312 formatter: None,
313 }
314 ))
315 );
316 assert_eq!(
317 parse_placeholder("$var.str()"),
318 Ok((
319 "",
320 Placeholder {
321 name: "var",
322 formatter: Some(Formatter {
323 name: "str",
324 args: vec![]
325 }),
326 }
327 ))
328 );
329 assert_eq!(
330 parse_placeholder("$var.str(a:b, c:d)"),
331 Ok((
332 "",
333 Placeholder {
334 name: "var",
335 formatter: Some(Formatter {
336 name: "str",
337 args: vec![Arg { key: "a", val: "b" }, Arg { key: "c", val: "d" }]
338 }),
339 }
340 ))
341 );
342 assert!(parse_placeholder("$key.").is_err());
343 }
344
345 #[test]
346 fn icon() {
347 assert_eq!(parse_icon("^icon_my_icon"), Ok(("", "my_icon")));
348 assert_eq!(parse_icon("^icon_m"), Ok(("", "m")));
349 assert!(parse_icon("^icon_").is_err());
350 assert!(parse_icon("^2").is_err());
351 }
352
353 #[test]
354 fn token_list() {
355 assert_eq!(
356 parse_token_list(" abc \\$ $var.str(a:b)$x "),
357 Ok((
358 "",
359 TokenList(vec![
360 Token::Text(" abc $ ".into()),
361 Token::Placeholder(Placeholder {
362 name: "var",
363 formatter: Some(Formatter {
364 name: "str",
365 args: vec![Arg { key: "a", val: "b" }]
366 })
367 }),
368 Token::Placeholder(Placeholder {
369 name: "x",
370 formatter: None,
371 }),
372 Token::Text(" ".into())
373 ])
374 ))
375 );
376 }
377
378 #[test]
379 fn format_template() {
380 assert_eq!(
381 parse_format_template("simple"),
382 Ok((
383 "",
384 FormatTemplate(vec![TokenList(vec![Token::Text("simple".into())]),])
385 ))
386 );
387 assert_eq!(
388 parse_format_template(" $x.str() | N/A "),
389 Ok((
390 "",
391 FormatTemplate(vec![
392 TokenList(vec![
393 Token::Text(" ".into()),
394 Token::Placeholder(Placeholder {
395 name: "x",
396 formatter: Some(Formatter {
397 name: "str",
398 args: vec![]
399 })
400 }),
401 Token::Text(" ".into()),
402 ]),
403 TokenList(vec![Token::Text(" N/A ".into())]),
404 ])
405 ))
406 );
407 }
408
409 #[test]
410 fn full() {
411 assert_eq!(
412 parse_format_template(" ^icon_my_icon {$x.str()|N/A} "),
413 Ok((
414 "",
415 FormatTemplate(vec![TokenList(vec![
416 Token::Text(" ".into()),
417 Token::Icon("my_icon"),
418 Token::Text(" ".into()),
419 Token::Recursive(FormatTemplate(vec![
420 TokenList(vec![Token::Placeholder(Placeholder {
421 name: "x",
422 formatter: Some(Formatter {
423 name: "str",
424 args: vec![]
425 })
426 })]),
427 TokenList(vec![Token::Text("N/A".into())]),
428 ])),
429 Token::Text(" ".into()),
430 ]),])
431 ))
432 );
433 }
434}