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()) .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) }
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