config.rs (4715B)
1 // generic config handling 2 // # comment 3 // [Header] 4 // key = [a-zA-Z_][a-zA-Z_-0-9]* 5 6 use std::collections::HashMap; 7 8 use nom::IResult; 9 use nom::branch::alt; 10 use nom::bytes::complete::{is_a, tag}; 11 use nom::character::complete::{ 12 alpha1, alphanumeric1, char, multispace0, multispace1, not_line_ending, 13 }; 14 use nom::combinator::{all_consuming, map, recognize, value}; 15 use nom::multi::many0; 16 use nom::sequence::{delimited, pair, separated_pair}; 17 18 pub fn config_file(input: &str) -> IResult<&str, HashMap<&str, HashMap<&str, &str>>> { 19 all_consuming(map(pair(body, many0(category_body)), |(root, mut rest)| { 20 rest.push((".root", root)); 21 rest.into_iter().collect() 22 }))(input) 23 } 24 25 fn category_body(input: &str) -> IResult<&str, (&str, HashMap<&str, &str>)> { 26 pair(category, body)(input) 27 } 28 29 fn category(input: &str) -> IResult<&str, &str> { 30 delimited(char('['), identifier, char(']'))(input) 31 } 32 33 fn body(input: &str) -> IResult<&str, HashMap<&str, &str>> { 34 map(many0(delimited(ignored, key_value, ignored)), |map| { 35 map.into_iter().collect() 36 })(input) 37 } 38 39 fn ignored(input: &str) -> IResult<&str, ()> { 40 value((), many0(alt((comment, value((), multispace1)))))(input) 41 } 42 43 fn comment(input: &str) -> IResult<&str, ()> { 44 value((), pair(char('#'), not_line_ending))(input) 45 } 46 47 fn key_value(input: &str) -> IResult<&str, (&str, &str)> { 48 separated_pair( 49 identifier, 50 delimited(multispace0, char('='), multispace0), 51 not_line_ending, 52 )(input) 53 } 54 55 fn identifier(input: &str) -> IResult<&str, &str> { 56 recognize(pair( 57 alt((alpha1, tag("_"))), 58 many0(alt((alphanumeric1, is_a("-_")))), 59 ))(input) 60 } 61 62 #[cfg(test)] 63 mod tests { 64 use nom::error::Error; 65 66 use crate::parser_test; 67 68 use super::*; 69 70 #[test] 71 fn parse_identifier() -> Result<(), Error<&'static str>> { 72 parser_test!( 73 identifier, 74 [ 75 "foo" => "foo", 76 "foo2" => "foo2", 77 "foo_bar" => "foo_bar", 78 "foo-bar" => "foo-bar" 79 ] 80 ); 81 82 Ok(()) 83 } 84 85 #[test] 86 fn parse_key_value() -> Result<(), Error<&'static str>> { 87 parser_test!( 88 key_value, 89 [ 90 "foo=bar" => ("foo", "bar"), 91 "foo = bar" => ("foo", "bar") 92 ] 93 ); 94 95 Ok(()) 96 } 97 98 #[test] 99 fn parse_comment() -> Result<(), Error<&'static str>> { 100 parser_test!( 101 comment, 102 [ 103 "#foo" => (), 104 "# foo" => () 105 ] 106 ); 107 108 Ok(()) 109 } 110 111 #[test] 112 fn parse_ignored() -> Result<(), Error<&'static str>> { 113 parser_test!( 114 ignored, 115 [ 116 "#foo" => (), 117 "# foo" => (), 118 " " => (), 119 " \t \n" => () 120 ] 121 ); 122 123 Ok(()) 124 } 125 126 #[test] 127 fn parse_body() -> Result<(), Error<&'static str>> { 128 parser_test!( 129 body, 130 [ 131 "x = y\ny=z" => HashMap::from([ 132 ("x", "y"), 133 ("y", "z") 134 ]), 135 "x = y\n# foo\n\ny=z" => HashMap::from([ 136 ("x", "y"), 137 ("y", "z") 138 ]) 139 ] 140 ); 141 142 Ok(()) 143 } 144 145 #[test] 146 fn parse_category() -> Result<(), Error<&'static str>> { 147 parser_test!( 148 category, 149 [ 150 "[foo]" => "foo", 151 "[foo-bar]" => "foo-bar" 152 ] 153 ); 154 155 Ok(()) 156 } 157 158 #[test] 159 fn parse_category_body() -> Result<(), Error<&'static str>> { 160 parser_test!( 161 category_body, 162 [ 163 "[foo]\nx = y\n# foo\ny=z" => ( 164 "foo", 165 HashMap::from([ 166 ("x", "y"), 167 ("y", "z") 168 ]) 169 ) 170 ] 171 ); 172 173 Ok(()) 174 } 175 176 #[test] 177 fn parse_config_file() -> Result<(), Error<&'static str>> { 178 parser_test!( 179 config_file, 180 [ 181 "# foo\n\nx = y\n[foo]\nx = y\n# foo\ny=z" => HashMap::from([ 182 ( 183 ".root", 184 HashMap::from([ 185 ("x", "y") 186 ]) 187 ), 188 ( 189 "foo", 190 HashMap::from([ 191 ("x", "y"), 192 ("y", "z") 193 ]) 194 ) 195 ]) 196 ] 197 ); 198 199 Ok(()) 200 } 201 }