1use nom::{ 2 bytes::complete::{is_a, tag, take_till, take_while}, 3 character::complete::{alphanumeric1 as alphanumeric, char, space0 as space}, 4 combinator::opt, 5 multi::many0, 6 sequence::{delimited, pair, terminated, tuple}, 7 IResult, 8}; 9 10use std::collections::HashMap; 11 12fn is_line_ending_or_comment(chr: char) -> bool { 13 chr == ';' || chr == '\n' 14} 15 16fn not_line_ending(i: &str) -> IResult<&str, &str> { 17 take_while(|c| c != '\r' && c != '\n')(i) 18} 19 20fn space_or_line_ending(i: &str) -> IResult<&str, &str> { 21 is_a(" \r\n")(i) 22} 23 24fn category(i: &str) -> IResult<&str, &str> { 25 terminated( 26 delimited(char('['), take_while(|c| c != ']'), char(']')), 27 opt(is_a(" \r\n")), 28 )(i) 29} 30 31fn key_value(i: &str) -> IResult<&str, (&str, &str)> { 32 let (i, key) = alphanumeric(i)?; 33 let (i, _) = tuple((opt(space), tag("="), opt(space)))(i)?; 34 let (i, val) = take_till(is_line_ending_or_comment)(i)?; 35 let (i, _) = opt(space)(i)?; 36 let (i, _) = opt(pair(tag(";"), not_line_ending))(i)?; 37 let (i, _) = opt(space_or_line_ending)(i)?; 38 39 Ok((i, (key, val))) 40} 41 42fn keys_and_values_aggregator(i: &str) -> IResult<&str, Vec<(&str, &str)>> { 43 many0(key_value)(i) 44} 45 46fn keys_and_values(input: &str) -> IResult<&str, HashMap<&str, &str>> { 47 match keys_and_values_aggregator(input) { 48 Ok((i, tuple_vec)) => Ok((i, tuple_vec.into_iter().collect())), 49 Err(e) => Err(e), 50 } 51} 52 53fn category_and_keys(i: &str) -> IResult<&str, (&str, HashMap<&str, &str>)> { 54 pair(category, keys_and_values)(i) 55} 56 57fn categories_aggregator(i: &str) -> IResult<&str, Vec<(&str, HashMap<&str, &str>)>> { 58 many0(category_and_keys)(i) 59} 60 61fn categories(input: &str) -> IResult<&str, HashMap<&str, HashMap<&str, &str>>> { 62 match categories_aggregator(input) { 63 Ok((i, tuple_vec)) => Ok((i, tuple_vec.into_iter().collect())), 64 Err(e) => Err(e), 65 } 66} 67 68#[test] 69fn parse_category_test() { 70 let ini_file = "[category] 71 72parameter=value 73key = value2"; 74 75 let ini_without_category = "parameter=value 76key = value2"; 77 78 let res = category(ini_file); 79 println!("{:?}", res); 80 match res { 81 Ok((i, o)) => println!("i: {} | o: {:?}", i, o), 82 _ => println!("error"), 83 } 84 85 assert_eq!(res, Ok((ini_without_category, "category"))); 86} 87 88#[test] 89fn parse_key_value_test() { 90 let ini_file = "parameter=value 91key = value2"; 92 93 let ini_without_key_value = "key = value2"; 94 95 let res = key_value(ini_file); 96 println!("{:?}", res); 97 match res { 98 Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2), 99 _ => println!("error"), 100 } 101 102 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value")))); 103} 104 105#[test] 106fn parse_key_value_with_space_test() { 107 let ini_file = "parameter = value 108key = value2"; 109 110 let ini_without_key_value = "key = value2"; 111 112 let res = key_value(ini_file); 113 println!("{:?}", res); 114 match res { 115 Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2), 116 _ => println!("error"), 117 } 118 119 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value")))); 120} 121 122#[test] 123fn parse_key_value_with_comment_test() { 124 let ini_file = "parameter=value;abc 125key = value2"; 126 127 let ini_without_key_value = "key = value2"; 128 129 let res = key_value(ini_file); 130 println!("{:?}", res); 131 match res { 132 Ok((i, (o1, o2))) => println!("i: {} | o: ({:?},{:?})", i, o1, o2), 133 _ => println!("error"), 134 } 135 136 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value")))); 137} 138 139#[test] 140fn parse_multiple_keys_and_values_test() { 141 let ini_file = "parameter=value;abc 142 143key = value2 144 145[category]"; 146 147 let ini_without_key_value = "[category]"; 148 149 let res = keys_and_values(ini_file); 150 println!("{:?}", res); 151 match res { 152 Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o), 153 _ => println!("error"), 154 } 155 156 let mut expected: HashMap<&str, &str> = HashMap::new(); 157 expected.insert("parameter", "value"); 158 expected.insert("key", "value2"); 159 assert_eq!(res, Ok((ini_without_key_value, expected))); 160} 161 162#[test] 163fn parse_category_then_multiple_keys_and_values_test() { 164 //FIXME: there can be an empty line or a comment line after a category 165 let ini_file = "[abcd] 166parameter=value;abc 167 168key = value2 169 170[category]"; 171 172 let ini_after_parser = "[category]"; 173 174 let res = category_and_keys(ini_file); 175 println!("{:?}", res); 176 match res { 177 Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o), 178 _ => println!("error"), 179 } 180 181 let mut expected_h: HashMap<&str, &str> = HashMap::new(); 182 expected_h.insert("parameter", "value"); 183 expected_h.insert("key", "value2"); 184 assert_eq!(res, Ok((ini_after_parser, ("abcd", expected_h)))); 185} 186 187#[test] 188fn parse_multiple_categories_test() { 189 let ini_file = "[abcd] 190 191parameter=value;abc 192 193key = value2 194 195[category] 196parameter3=value3 197key4 = value4 198"; 199 200 let res = categories(ini_file); 201 //println!("{:?}", res); 202 match res { 203 Ok((i, ref o)) => println!("i: {} | o: {:?}", i, o), 204 _ => println!("error"), 205 } 206 207 let mut expected_1: HashMap<&str, &str> = HashMap::new(); 208 expected_1.insert("parameter", "value"); 209 expected_1.insert("key", "value2"); 210 let mut expected_2: HashMap<&str, &str> = HashMap::new(); 211 expected_2.insert("parameter3", "value3"); 212 expected_2.insert("key4", "value4"); 213 let mut expected_h: HashMap<&str, HashMap<&str, &str>> = HashMap::new(); 214 expected_h.insert("abcd", expected_1); 215 expected_h.insert("category", expected_2); 216 assert_eq!(res, Ok(("", expected_h))); 217} 218