i3status_rs/formatting/
parse.rs

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
84//val
85//'val ue'
86fn 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
96// `key:val`
97fn 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
104// `(arg,key:val)`
105// `( arg, key:val , abc)`
106fn 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
114// `.str(width:2)`
115// `.eng(unit:bits,bin)`
116fn 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
125// `$var`
126// `$key.eng(unit:bits,bin)`
127fn 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
133// `just escaped \| text`
134fn 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
145// `^icon_name`
146fn parse_icon(i: &str) -> IResult<&str, &str, PError> {
147    preceded(char('^'), cut(preceded(tag("icon_"), alphanum1)))(i)
148}
149
150// `{ a | b | c }`
151fn 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                    // TODO: improve?
195                    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}