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},
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    ))
113    .parse(i)
114}
115
116// `key:val`
117// `abc`
118fn parse_arg(i: &str) -> IResult<&str, Arg<'_>, PError<'_>> {
119    alt((
120        map(
121            separated_pair(alphanum1, char(':'), cut(arg1)),
122            |(key, val)| Arg {
123                key,
124                val: Some(val),
125            },
126        ),
127        map(alphanum1, |key| Arg { key, val: None }),
128    ))
129    .parse(i)
130}
131
132// `(arg,key:val)`
133// `( arg, key:val , abc)`
134fn parse_args(i: &str) -> IResult<&str, Vec<Arg<'_>>, PError<'_>> {
135    let inner = separated_list0(preceded(spaces, char(',')), preceded(spaces, parse_arg));
136    preceded(
137        char('('),
138        cut(terminated(inner, preceded(spaces, char(')')))),
139    )
140    .parse(i)
141}
142
143// `.str(width:2)`
144// `.eng(unit:bits,show)`
145fn parse_formatter(i: &str) -> IResult<&str, Formatter<'_>, PError<'_>> {
146    preceded(char('.'), cut((alphanum1, opt(parse_args))))
147        .map(|(name, args)| Formatter {
148            name,
149            args: args.unwrap_or_default(),
150        })
151        .parse(i)
152}
153
154// `$var`
155// `$key.eng(unit:bits,show)`
156fn parse_placeholder(i: &str) -> IResult<&str, Placeholder<'_>, PError<'_>> {
157    preceded(char('$'), cut((alphanum1, opt(parse_formatter))))
158        .map(|(name, formatter)| Placeholder { name, formatter })
159        .parse(i)
160}
161
162// `just escaped \| text`
163fn parse_string(i: &str) -> IResult<&str, String, PError<'_>> {
164    preceded(
165        not(eof),
166        escaped_transform(
167            take_while1(|x| x != '$' && x != '^' && x != '{' && x != '}' && x != '|' && x != '\\'),
168            '\\',
169            anychar,
170        ),
171    )
172    .parse(i)
173}
174
175// `^icon_name`
176fn parse_icon(i: &str) -> IResult<&str, &str, PError<'_>> {
177    preceded(char('^'), cut(preceded(tag("icon_"), alphanum1))).parse(i)
178}
179
180// `{ a | b | c }`
181fn parse_recursive_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> {
182    preceded(char('{'), cut(terminated(parse_format_template, char('}')))).parse(i)
183}
184
185fn parse_token_list(i: &str) -> IResult<&str, TokenList<'_>, PError<'_>> {
186    map(
187        many0(alt((
188            map(parse_string, Token::Text),
189            map(parse_placeholder, Token::Placeholder),
190            map(parse_icon, Token::Icon),
191            map(parse_recursive_template, Token::Recursive),
192        ))),
193        TokenList,
194    )
195    .parse(i)
196}
197
198fn parse_format_template(i: &str) -> IResult<&str, FormatTemplate<'_>, PError<'_>> {
199    map(separated_list0(char('|'), parse_token_list), FormatTemplate).parse(i)
200}
201
202pub fn parse_full(i: &str) -> Result<FormatTemplate<'_>> {
203    match parse_format_template(i) {
204        Ok((rest, template)) => {
205            if rest.is_empty() {
206                Ok(template)
207            } else {
208                Err(Error::new(format!(
209                    "unexpected '{}'",
210                    rest.chars().next().unwrap()
211                )))
212            }
213        }
214        Err(err) => Err(match err {
215            nom::Err::Incomplete(_) => unreachable!(),
216            nom::Err::Error(err) | nom::Err::Failure(err) => match err {
217                PError::Expected { expected, actual } => {
218                    if let Some(actual) = actual {
219                        Error::new(format!("expected '{expected}', got '{actual}'"))
220                    } else {
221                        Error::new(format!("expected '{expected}', got EOF"))
222                    }
223                }
224                PError::Other { input, kind } => {
225                    // TODO: improve?
226                    Error::new(format!("{kind:?} error near '{input}'"))
227                }
228            },
229        }),
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn arg() {
239        assert_eq!(
240            parse_arg("key:val,"),
241            Ok((
242                ",",
243                Arg {
244                    key: "key",
245                    val: Some("val")
246                }
247            ))
248        );
249        assert_eq!(
250            parse_arg("key:'val ue',"),
251            Ok((
252                ",",
253                Arg {
254                    key: "key",
255                    val: Some("val ue")
256                }
257            ))
258        );
259        assert_eq!(
260            parse_arg("key:'',"),
261            Ok((
262                ",",
263                Arg {
264                    key: "key",
265                    val: Some("")
266                }
267            ))
268        );
269        assert_eq!(
270            parse_arg("key,"),
271            Ok((
272                ",",
273                Arg {
274                    key: "key",
275                    val: None
276                }
277            ))
278        );
279        assert_eq!(
280            parse_arg("key:,"),
281            Err(nom::Err::Failure(PError::Expected {
282                expected: '\'',
283                actual: Some(',')
284            }))
285        );
286    }
287
288    #[test]
289    fn args() {
290        assert_eq!(
291            parse_args("(key:val)"),
292            Ok((
293                "",
294                vec![Arg {
295                    key: "key",
296                    val: Some("val")
297                }]
298            ))
299        );
300        assert_eq!(
301            parse_args("( abc:d , key:val )"),
302            Ok((
303                "",
304                vec![
305                    Arg {
306                        key: "abc",
307                        val: Some("d"),
308                    },
309                    Arg {
310                        key: "key",
311                        val: Some("val")
312                    }
313                ]
314            ))
315        );
316        assert_eq!(
317            parse_args("(abc)"),
318            Ok((
319                "",
320                vec![Arg {
321                    key: "abc",
322                    val: None
323                }]
324            ))
325        );
326        assert_eq!(
327            parse_args("( key:, )"),
328            Err(nom::Err::Failure(PError::Expected {
329                expected: '\'',
330                actual: Some(',')
331            }))
332        );
333    }
334
335    #[test]
336    fn formatter() {
337        assert_eq!(
338            parse_formatter(".str(key:val)"),
339            Ok((
340                "",
341                Formatter {
342                    name: "str",
343                    args: vec![Arg {
344                        key: "key",
345                        val: Some("val")
346                    }]
347                }
348            ))
349        );
350        assert_eq!(
351            parse_formatter(".eng(w:3 , show:true )"),
352            Ok((
353                "",
354                Formatter {
355                    name: "eng",
356                    args: vec![
357                        Arg {
358                            key: "w",
359                            val: Some("3")
360                        },
361                        Arg {
362                            key: "show",
363                            val: Some("true")
364                        }
365                    ]
366                }
367            ))
368        );
369        assert_eq!(
370            parse_formatter(".eng(w:3 , show)"),
371            Ok((
372                "",
373                Formatter {
374                    name: "eng",
375                    args: vec![
376                        Arg {
377                            key: "w",
378                            val: Some("3")
379                        },
380                        Arg {
381                            key: "show",
382                            val: None
383                        }
384                    ]
385                }
386            ))
387        );
388    }
389
390    #[test]
391    fn placeholder() {
392        assert_eq!(
393            parse_placeholder("$key"),
394            Ok((
395                "",
396                Placeholder {
397                    name: "key",
398                    formatter: None,
399                }
400            ))
401        );
402        assert_eq!(
403            parse_placeholder("$var.str()"),
404            Ok((
405                "",
406                Placeholder {
407                    name: "var",
408                    formatter: Some(Formatter {
409                        name: "str",
410                        args: vec![]
411                    }),
412                }
413            ))
414        );
415        assert_eq!(
416            parse_placeholder("$var.str(a:b, c:d)"),
417            Ok((
418                "",
419                Placeholder {
420                    name: "var",
421                    formatter: Some(Formatter {
422                        name: "str",
423                        args: vec![
424                            Arg {
425                                key: "a",
426                                val: Some("b")
427                            },
428                            Arg {
429                                key: "c",
430                                val: Some("d")
431                            }
432                        ]
433                    }),
434                }
435            ))
436        );
437        assert!(parse_placeholder("$key.").is_err());
438    }
439
440    #[test]
441    fn icon() {
442        assert_eq!(parse_icon("^icon_my_icon"), Ok(("", "my_icon")));
443        assert_eq!(parse_icon("^icon_m"), Ok(("", "m")));
444        assert!(parse_icon("^icon_").is_err());
445        assert!(parse_icon("^2").is_err());
446    }
447
448    #[test]
449    fn token_list() {
450        assert_eq!(
451            parse_token_list(" abc \\$ $var.str(a:b)$x "),
452            Ok((
453                "",
454                TokenList(vec![
455                    Token::Text(" abc $ ".into()),
456                    Token::Placeholder(Placeholder {
457                        name: "var",
458                        formatter: Some(Formatter {
459                            name: "str",
460                            args: vec![Arg {
461                                key: "a",
462                                val: Some("b")
463                            }]
464                        })
465                    }),
466                    Token::Placeholder(Placeholder {
467                        name: "x",
468                        formatter: None,
469                    }),
470                    Token::Text(" ".into())
471                ])
472            ))
473        );
474    }
475
476    #[test]
477    fn format_template() {
478        assert_eq!(
479            parse_format_template("simple"),
480            Ok((
481                "",
482                FormatTemplate(vec![TokenList(vec![Token::Text("simple".into())]),])
483            ))
484        );
485        assert_eq!(
486            parse_format_template(" $x.str() | N/A "),
487            Ok((
488                "",
489                FormatTemplate(vec![
490                    TokenList(vec![
491                        Token::Text(" ".into()),
492                        Token::Placeholder(Placeholder {
493                            name: "x",
494                            formatter: Some(Formatter {
495                                name: "str",
496                                args: vec![]
497                            })
498                        }),
499                        Token::Text(" ".into()),
500                    ]),
501                    TokenList(vec![Token::Text(" N/A ".into())]),
502                ])
503            ))
504        );
505    }
506
507    #[test]
508    fn full() {
509        assert_eq!(
510            parse_format_template(" ^icon_my_icon {$x.str()|N/A} "),
511            Ok((
512                "",
513                FormatTemplate(vec![TokenList(vec![
514                    Token::Text(" ".into()),
515                    Token::Icon("my_icon"),
516                    Token::Text(" ".into()),
517                    Token::Recursive(FormatTemplate(vec![
518                        TokenList(vec![Token::Placeholder(Placeholder {
519                            name: "x",
520                            formatter: Some(Formatter {
521                                name: "str",
522                                args: vec![]
523                            })
524                        })]),
525                        TokenList(vec![Token::Text("N/A".into())]),
526                    ])),
527                    Token::Text(" ".into()),
528                ]),])
529            ))
530        );
531    }
532}