i3status_rs/formatting/
parse.rs

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
103//val
104//'val ue'
105fn 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
115// `key:val`
116// `abc`
117fn 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
130// `(arg,key:val)`
131// `( arg, key:val , abc)`
132fn 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
140// `.str(width:2)`
141// `.eng(unit:bits,show)`
142fn 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
151// `$var`
152// `$key.eng(unit:bits,show)`
153fn 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
159// `just escaped \| text`
160fn 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
171// `^icon_name`
172fn parse_icon(i: &str) -> IResult<&str, &str, PError> {
173    preceded(char('^'), cut(preceded(tag("icon_"), alphanum1)))(i)
174}
175
176// `{ a | b | c }`
177fn 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                    // TODO: improve?
221                    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}