blob: 2d4161976d6d704ba0fabc37b57d0a884682a675 [file] [log] [blame]
//! Parser example for INI files.
use std::{
collections::HashMap,
env, fmt,
fs::File,
io::{self, Read},
};
use combine::{parser::char::space, stream::position, *};
#[cfg(feature = "std")]
use combine::stream::easy;
#[cfg(feature = "std")]
use combine::stream::position::SourcePosition;
enum Error<E> {
Io(io::Error),
Parse(E),
}
impl<E> fmt::Display for Error<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Io(ref err) => write!(f, "{}", err),
Error::Parse(ref err) => write!(f, "{}", err),
}
}
}
#[derive(PartialEq, Debug)]
pub struct Ini {
pub global: HashMap<String, String>,
pub sections: HashMap<String, HashMap<String, String>>,
}
fn property<Input>() -> impl Parser<Input, Output = (String, String)>
where
Input: Stream<Token = char>,
// Necessary due to rust-lang/rust#24159
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
(
many1(satisfy(|c| c != '=' && c != '[' && c != ';')),
token('='),
many1(satisfy(|c| c != '\n' && c != ';')),
)
.map(|(key, _, value)| (key, value))
.message("while parsing property")
}
fn whitespace<Input>() -> impl Parser<Input>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let comment = (token(';'), skip_many(satisfy(|c| c != '\n'))).map(|_| ());
// Wrap the `spaces().or(comment)` in `skip_many` so that it skips alternating whitespace and
// comments
skip_many(skip_many1(space()).or(comment))
}
fn properties<Input>() -> impl Parser<Input, Output = HashMap<String, String>>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
// After each property we skip any whitespace that followed it
many(property().skip(whitespace()))
}
fn section<Input>() -> impl Parser<Input, Output = (String, HashMap<String, String>)>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
(
between(token('['), token(']'), many(satisfy(|c| c != ']'))),
whitespace(),
properties(),
)
.map(|(name, _, properties)| (name, properties))
.message("while parsing section")
}
fn ini<Input>() -> impl Parser<Input, Output = Ini>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
(whitespace(), properties(), many(section()))
.map(|(_, global, sections)| Ini { global, sections })
}
#[test]
fn ini_ok() {
let text = r#"
language=rust
[section]
name=combine; Comment
type=LL(1)
"#;
let mut expected = Ini {
global: HashMap::new(),
sections: HashMap::new(),
};
expected
.global
.insert(String::from("language"), String::from("rust"));
let mut section = HashMap::new();
section.insert(String::from("name"), String::from("combine"));
section.insert(String::from("type"), String::from("LL(1)"));
expected.sections.insert(String::from("section"), section);
let result = ini().parse(text).map(|t| t.0);
assert_eq!(result, Ok(expected));
}
#[cfg(feature = "std")]
#[test]
fn ini_error() {
let text = "[error";
let result = ini().easy_parse(position::Stream::new(text)).map(|t| t.0);
assert_eq!(
result,
Err(easy::Errors {
position: SourcePosition { line: 1, column: 7 },
errors: vec![
easy::Error::end_of_input(),
easy::Error::Expected(']'.into()),
easy::Error::Message("while parsing section".into()),
],
})
);
}
fn main() {
let result = match env::args().nth(1) {
Some(file) => File::open(file).map_err(Error::Io).and_then(main_),
None => main_(io::stdin()),
};
match result {
Ok(_) => println!("OK"),
Err(err) => println!("{}", err),
}
}
#[cfg(feature = "std")]
fn main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>>
where
R: Read,
{
let mut text = String::new();
read.read_to_string(&mut text).map_err(Error::Io)?;
ini()
.easy_parse(position::Stream::new(&*text))
.map_err(|err| Error::Parse(err.map_range(|s| s.to_string())))?;
Ok(())
}
#[cfg(not(feature = "std"))]
fn main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>>
where
R: Read,
{
let mut text = String::new();
read.read_to_string(&mut text).map_err(Error::Io)?;
ini()
.parse(position::Stream::new(&*text))
.map_err(Error::Parse)?;
Ok(())
}