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