use nom::branch::alt;
use nom::bytes::complete::{tag, take_till};
use nom::combinator::{complete, eof, opt, value};
use nom::character::complete::{none_of, one_of};
use nom::multi::{many0, many1, many_till, separated_list0, separated_list1};
use nom::IResult;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Property {
pub key: String,
pub value: String,
}
fn consume_whitespaces(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = many0(one_of(" \t\u{c}"))(input)?;
Ok((input, ()))
}
fn consume_eol(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = alt((complete(tag("\r\n")), tag("\r"), tag("\n")))(input)?;
Ok((input, ()))
}
fn consume_eol_or_eof(input: &[u8]) -> IResult<&[u8], ()> {
alt((value((), eof), consume_eol))(input)
}
fn blank_line(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = consume_whitespaces(input)?;
consume_eol_or_eof(input)
}
fn comment_line(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = consume_whitespaces(input)?;
let (input, _) = one_of("#!")(input)?;
let (input, _) = take_till(eol)(input)?;
consume_eol_or_eof(input)
}
fn eol(c: u8) -> bool {
c as char == '\r' || c as char == '\n'
}
fn consume_line(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = tag(r"\")(input)?;
let (input, _) = consume_eol(input)?;
let (input, _) = consume_whitespaces(input)?;
Ok((input, ()))
}
fn consume_whitespaces_and_lines(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = separated_list0(many1(consume_line), consume_whitespaces)(input)?;
Ok((input, ()))
}
fn char_in_key(input: &[u8]) -> IResult<&[u8], char> {
none_of(":=\n\r \t\u{c}\\")(input)
}
fn char_in_value(input: &[u8]) -> IResult<&[u8], char> {
none_of("\n\r\\")(input)
}
fn escaped_char_to_char(v: char) -> char {
match v {
't' => '\t',
'n' => '\n',
'f' => '\u{c}',
'r' => '\r',
'\\' => '\\',
_ => v,
}
}
fn escape_in_key_or_value(input: &[u8]) -> IResult<&[u8], char> {
let (input, _) = tag(r"\")(input)?;
let (input, c) = none_of("u\r\n")(input)?;
Ok((input, escaped_char_to_char(c)))
}
fn one_char_in_key(input: &[u8]) -> IResult<&[u8], char> {
alt((escape_in_key_or_value, char_in_key))(input)
}
fn one_char_in_value(input: &[u8]) -> IResult<&[u8], char> {
alt((escape_in_key_or_value, char_in_value))(input)
}
fn consume_key(input: &[u8]) -> IResult<&[u8], String> {
let (input, chars) = separated_list1(many1(consume_line), many1(one_char_in_key))(input)?;
Ok((input, chars.into_iter().flatten().collect::<String>()))
}
fn consume_value(input: &[u8]) -> IResult<&[u8], String> {
let (input, chars) = separated_list0(many1(consume_line), many0(one_char_in_value))(input)?;
Ok((input, chars.into_iter().flatten().collect::<String>()))
}
fn kv_line(input: &[u8]) -> IResult<&[u8], Property> {
let (input, _) = consume_whitespaces_and_lines(input)?;
let (input, key) = consume_key(input)?;
let (input, _) = consume_whitespaces_and_lines(input)?;
let (input, _) = opt(complete(one_of(":=")))(input)?;
let (input, _) = consume_whitespaces_and_lines(input)?;
let (input, value) = consume_value(input)?;
let (input, _) = consume_eol_or_eof(input)?;
Ok((input, Property { key, value }))
}
type ParsedProps<'a> = (Vec<Option<Property>>, &'a [u8]);
fn _fparser(input: &[u8]) -> IResult<&[u8], ParsedProps> {
many_till(
alt((
value(None, complete(comment_line)),
value(None, complete(blank_line)),
opt(complete(kv_line)),
)),
eof,
)(input)
}
pub fn parser(input: &[u8]) -> IResult<&[u8], Vec<Property>> {
let (input, props) = _fparser(input)?;
let v = props.0.into_iter().flatten().collect();
Ok((input, v))
}
#[cfg(test)]
mod test {
use super::*;
use nom::error::dbg_dmp;
macro_rules! assert_done {
($t:expr, $v:expr) => {
assert_eq!($t, Ok((&b""[..], $v)))
};
}
macro_rules! assert_done_partial {
($t:expr, $v:expr, $s:tt) => {
assert_eq!($t, Ok((&$s[..], $v)))
};
}
macro_rules! assert_incomplete {
($t:expr) => {
let r = $t;
assert!(r.is_err(), "Expected IResult::Incomplete, got {:?}", r);
};
}
#[test]
fn test_key() {
assert_done!(consume_key(b"hello"), String::from("hello"));
assert_done_partial!(
consume_key(b"hello world"),
String::from("hello"),
b" world"
);
assert_done_partial!(
consume_key(b"hello:world"),
String::from("hello"),
b":world"
);
assert_done_partial!(
consume_key(b"hello=world"),
String::from("hello"),
b"=world"
);
assert_done_partial!(
consume_key(b"hello\nworld"),
String::from("hello"),
b"\nworld"
);
assert_done_partial!(
consume_key(b"hello\rworld"),
String::from("hello"),
b"\rworld"
);
assert_done!(
consume_key(b"@#$%^&*()_+-`~?/.>,<|][{};\""),
String::from("@#$%^&*()_+-`~?/.>,<|][{};\"")
);
assert_done!(
consume_key(br"key\ with\ spaces"),
String::from("key with spaces")
);
assert_done!(
consume_key(br"key\:with\:colons"),
String::from("key:with:colons")
);
assert_done!(
consume_key(br"key\=with\=equals"),
String::from("key=with=equals")
);
assert_done!(
consume_key(br"now\nwith\rsome\fspecial\tcharacters\\"),
String::from("now\nwith\rsome\u{c}special\tcharacters\\")
);
assert_done!(
consume_key(br"w\iths\omeran\domch\arse\sca\pe\d"),
String::from("withsomerandomcharsescaped")
);
assert_incomplete!(consume_key(b""));
assert_done!(
dbg_dmp(consume_key, "ell")(b"abc\\\n def"),
String::from("abcdef")
);
assert_done!(
dbg_dmp(consume_key, "ell")(b"gh\\\n \\\r \\\r\nij\\\n\t kl"),
String::from("ghijkl")
);
}
#[allow(dead_code)]
fn test_utf8_keys() {
assert_done!(
consume_key(br"\u0048\u0065\u006c\u006c\u006f"),
String::from("Hello")
);
assert_done!(consume_key(&[0xA9]), String::from("\u{a9}"));
assert_done_partial!(
consume_key(br"abc\uhello"),
String::from("abc"),
br"\uhello"
);
}
#[test]
fn test_value() {
assert_done!(consume_value(b"hello"), String::from("hello"));
assert_done!(consume_value(b"h:l=o"), String::from("h:l=o"));
assert_done!(
consume_value(b"hello world "),
String::from("hello world ")
);
assert_done!(
consume_value(b"/~`!@#$%^&*()-_=+[{]};:'\",<.>/?|"),
String::from("/~`!@#$%^&*()-_=+[{]};:'\",<.>/?|")
);
assert_done_partial!(
consume_value(b"hello\nworld"),
String::from("hello"),
b"\nworld"
);
assert_done_partial!(
consume_value(b"hello\rworld"),
String::from("hello"),
b"\rworld"
);
assert_done!(
consume_value(br"now\nwith\rsome\fspecial\tcharacters\\"),
String::from("now\nwith\rsome\u{c}special\tcharacters\\")
);
assert_done!(
consume_value(br"w\iths\omeran\domch\arse\sca\pe\d"),
String::from("withsomerandomcharsescaped")
);
assert_done!(consume_value(b""), String::from(""));
assert_done!(consume_value(b"abc\\\n def"), String::from("abcdef"));
assert_done!(
consume_value(b"gh\\\n \\\r \\\r\nij\\\n\t kl"),
String::from("ghijkl")
);
}
#[allow(dead_code)]
fn test_utf8_values() {
assert_done!(
consume_value(br"\u0048\u0065\u006c\u006c\u006f"),
String::from("Hello")
);
assert_done!(consume_value(&[0xA9]), String::from("\u{a9}"));
assert_done_partial!(
consume_value(br"abc\uhello"),
String::from("abc"),
br"\uhello"
);
}
#[test]
fn test_kv_line() {
let parsed = kv_line(b"key=value");
assert_eq!(
parsed.unwrap().1,
Property {
key: String::from("key"),
value: String::from("value")
}
);
}
#[test]
fn test_full_parse_simple() {
let prop = br"key.1=value1
key.two=value2
";
let parsed = _fparser(prop);
let props = parsed.unwrap().1;
println!("{:?}", props.0);
assert_eq!(3, props.0.len());
let props: Vec<Property> = props.0.into_iter().flatten().collect();
assert_eq!(2, props.len());
assert_eq!(props[0].key, "key.1");
assert_eq!(props[0].value, "value1");
assert_eq!(props[1].key, "key.two");
assert_eq!(props[1].value, "value2")
}
#[test]
fn test_pub_parser() {
let prop = br"key.1=value1
key.two=value2
";
let parsed = parser(prop);
let props = parsed.unwrap().1;
assert_eq!(2, props.len());
assert_eq!(props[0].key, "key.1");
assert_eq!(props[0].value, "value1");
assert_eq!(props[1].key, "key.two");
assert_eq!(props[1].value, "value2")
}
}