systemd_parser/
parser.rs

1
2use items::SystemdItem;
3use nom::*;
4
5fn c_always_true(_c: char) -> bool { true }
6fn c_is_category_element(c: char) -> bool {
7    c.is_alphabetic() || c == '-'
8}
9fn c_is_value_element(c: char) -> bool {
10    match c {
11        '\n'|'\r'|'#' => false,
12        _ => true,
13    }
14}
15fn c_is_key_element(c: char) -> bool {
16    match c {
17        '!'|'|'|'@' => true,
18        c if c.is_alphabetic() => true,
19        _ => false
20    }
21}
22
23named!(pub take_whole_line<&str, &str>, take_while_s!(c_always_true));
24
25named!(
26    pub parse_comment<&str, SystemdItem>,
27    complete!(do_parse!(
28        eat_separator!(" \t")      >>
29        tag_s!("#")                >>
30        comment: take_whole_line   >>
31        (SystemdItem::Comment(comment.trim()))
32    ))
33);
34
35named!(
36    pub parse_category<&str, SystemdItem>,
37    complete!(do_parse!(
38        eat_separator!(" \t")   >>
39        tag!("[")               >>
40        eat_separator!(" ")     >>
41        category: take_while1_s!(c_is_category_element) >>
42        eat_separator!(" ")     >>
43        tag!("]")               >>
44        (SystemdItem::Category(category))
45    ))
46);
47
48named!(
49    pub parse_directive<&str, SystemdItem>,
50    complete!(do_parse!(
51        eat_separator!(" \t")   >>
52        key: take_while1_s!(c_is_key_element) >>
53        eat_separator!(" ")     >>
54        tag!("=")               >>
55        eat_separator!(" ")     >>
56        value: take_while_s!(c_is_value_element) >>
57        (SystemdItem::Directive(key, (if value.is_empty() { None } else { Some(value) })))
58    ))
59);
60
61named!(
62    pub parse_line<&str, SystemdItem>,
63    do_parse!(
64        value: alt_complete!(parse_category | parse_comment | parse_directive) >>
65        eat_separator!(" \t") >>
66        eof!()                >>
67        (value)
68    )
69);
70
71pub fn parse_unit(input: &str) -> Result<Vec<SystemdItem>, Vec<(IError<&str>, u32)>> {
72
73    let mut errors = vec!();
74    let mut oks = vec!();
75
76    let mixed_res = input.lines()
77                         .filter(|line| !line.trim().is_empty()) // skip white lines
78                         .map(|line| parse_line(line));
79
80    for res in mixed_res {
81        match res.to_full_result() {
82            Ok(ok_res) => oks.push(ok_res),
83            Err(err_res) => errors.push(err_res),
84        }
85    }
86
87    if errors.len() > 0 {
88        Err(enhance_with_line_numbers(errors, input))
89    } else {
90        Ok(oks)
91    }
92}
93
94fn enhance_with_line_numbers<'a>(errors: Vec<IError<&'a str>>, input: &str)
95    -> Vec<(IError<&'a str>, u32)> {
96
97    use nom::IError::*;
98    use nom::ErrorKind::*;
99    use nom::Err::*;
100
101    errors.iter()
102          .map(|error| {
103              if let &Error(Position(Alt, pattern)) = error {
104                  let line_number = count_lines_by_pattern(pattern, input);
105                  (error.clone(), line_number)
106              } else {
107                  (error.clone(), 0) // FIXME: is it possible ?
108              }
109          }).collect()
110}
111
112fn count_lines_by_pattern(pattern: &str, haystack: &str) -> u32 {
113
114    let mut idx = 0;
115    haystack.lines()
116            .map(|line| { idx += 1; (line, idx) })
117            .find(|&(line, _)| line.contains(pattern))
118            .expect("it has been parsed once, it must be in the input somewhere").1
119
120}
121