• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Parser example for INI files.
2 
3 use std::{
4     collections::HashMap,
5     env, fmt,
6     fs::File,
7     io::{self, Read},
8 };
9 
10 use combine::{parser::char::space, stream::position, *};
11 
12 #[cfg(feature = "std")]
13 use combine::stream::easy;
14 
15 #[cfg(feature = "std")]
16 use combine::stream::position::SourcePosition;
17 
18 enum Error<E> {
19     Io(io::Error),
20     Parse(E),
21 }
22 
23 impl<E> fmt::Display for Error<E>
24 where
25     E: fmt::Display,
26 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result27     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28         match *self {
29             Error::Io(ref err) => write!(f, "{}", err),
30             Error::Parse(ref err) => write!(f, "{}", err),
31         }
32     }
33 }
34 
35 #[derive(PartialEq, Debug)]
36 pub struct Ini {
37     pub global: HashMap<String, String>,
38     pub sections: HashMap<String, HashMap<String, String>>,
39 }
40 
property<Input>() -> impl Parser<Input, Output = (String, String)> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,41 fn property<Input>() -> impl Parser<Input, Output = (String, String)>
42 where
43     Input: Stream<Token = char>,
44     // Necessary due to rust-lang/rust#24159
45     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
46 {
47     (
48         many1(satisfy(|c| c != '=' && c != '[' && c != ';')),
49         token('='),
50         many1(satisfy(|c| c != '\n' && c != ';')),
51     )
52         .map(|(key, _, value)| (key, value))
53         .message("while parsing property")
54 }
55 
whitespace<Input>() -> impl Parser<Input> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,56 fn whitespace<Input>() -> impl Parser<Input>
57 where
58     Input: Stream<Token = char>,
59     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
60 {
61     let comment = (token(';'), skip_many(satisfy(|c| c != '\n'))).map(|_| ());
62     // Wrap the `spaces().or(comment)` in `skip_many` so that it skips alternating whitespace and
63     // comments
64     skip_many(skip_many1(space()).or(comment))
65 }
66 
properties<Input>() -> impl Parser<Input, Output = HashMap<String, String>> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,67 fn properties<Input>() -> impl Parser<Input, Output = HashMap<String, String>>
68 where
69     Input: Stream<Token = char>,
70     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
71 {
72     // After each property we skip any whitespace that followed it
73     many(property().skip(whitespace()))
74 }
75 
section<Input>() -> impl Parser<Input, Output = (String, HashMap<String, String>)> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,76 fn section<Input>() -> impl Parser<Input, Output = (String, HashMap<String, String>)>
77 where
78     Input: Stream<Token = char>,
79     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
80 {
81     (
82         between(token('['), token(']'), many(satisfy(|c| c != ']'))),
83         whitespace(),
84         properties(),
85     )
86         .map(|(name, _, properties)| (name, properties))
87         .message("while parsing section")
88 }
89 
ini<Input>() -> impl Parser<Input, Output = Ini> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,90 fn ini<Input>() -> impl Parser<Input, Output = Ini>
91 where
92     Input: Stream<Token = char>,
93     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
94 {
95     (whitespace(), properties(), many(section()))
96         .map(|(_, global, sections)| Ini { global, sections })
97 }
98 
99 #[test]
ini_ok()100 fn ini_ok() {
101     let text = r#"
102 language=rust
103 
104 [section]
105 name=combine; Comment
106 type=LL(1)
107 
108 "#;
109     let mut expected = Ini {
110         global: HashMap::new(),
111         sections: HashMap::new(),
112     };
113     expected
114         .global
115         .insert(String::from("language"), String::from("rust"));
116 
117     let mut section = HashMap::new();
118     section.insert(String::from("name"), String::from("combine"));
119     section.insert(String::from("type"), String::from("LL(1)"));
120     expected.sections.insert(String::from("section"), section);
121 
122     let result = ini().parse(text).map(|t| t.0);
123     assert_eq!(result, Ok(expected));
124 }
125 
126 #[cfg(feature = "std")]
127 #[test]
ini_error()128 fn ini_error() {
129     let text = "[error";
130     let result = ini().easy_parse(position::Stream::new(text)).map(|t| t.0);
131     assert_eq!(
132         result,
133         Err(easy::Errors {
134             position: SourcePosition { line: 1, column: 7 },
135             errors: vec![
136                 easy::Error::end_of_input(),
137                 easy::Error::Expected(']'.into()),
138                 easy::Error::Message("while parsing section".into()),
139             ],
140         })
141     );
142 }
143 
main()144 fn main() {
145     let result = match env::args().nth(1) {
146         Some(file) => File::open(file).map_err(Error::Io).and_then(main_),
147         None => main_(io::stdin()),
148     };
149     match result {
150         Ok(_) => println!("OK"),
151         Err(err) => println!("{}", err),
152     }
153 }
154 
155 #[cfg(feature = "std")]
main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>> where R: Read,156 fn main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>>
157 where
158     R: Read,
159 {
160     let mut text = String::new();
161     read.read_to_string(&mut text).map_err(Error::Io)?;
162     ini()
163         .easy_parse(position::Stream::new(&*text))
164         .map_err(|err| Error::Parse(err.map_range(|s| s.to_string())))?;
165     Ok(())
166 }
167 
168 #[cfg(not(feature = "std"))]
main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>> where R: Read,169 fn main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>>
170 where
171     R: Read,
172 {
173     let mut text = String::new();
174     read.read_to_string(&mut text).map_err(Error::Io)?;
175     ini()
176         .parse(position::Stream::new(&*text))
177         .map_err(Error::Parse)?;
178     Ok(())
179 }
180