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