1 // Copyright © 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use std::collections::HashMap; 7 use std::fmt; 8 use std::num::ParseIntError; 9 use std::str::FromStr; 10 11 #[derive(Default)] 12 pub struct OptionParser { 13 options: HashMap<String, OptionParserValue>, 14 } 15 16 struct OptionParserValue { 17 value: Option<String>, 18 requires_value: bool, 19 } 20 21 #[derive(Debug)] 22 pub enum OptionParserError { 23 UnknownOption(String), 24 InvalidSyntax(String), 25 Conversion(String, String), 26 } 27 28 impl fmt::Display for OptionParserError { 29 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 match self { 31 OptionParserError::UnknownOption(s) => write!(f, "unknown option: {}", s), 32 OptionParserError::InvalidSyntax(s) => write!(f, "invalid syntax:{}", s), 33 OptionParserError::Conversion(field, value) => { 34 write!(f, "unable to convert {} for {}", value, field) 35 } 36 } 37 } 38 } 39 type OptionParserResult<T> = std::result::Result<T, OptionParserError>; 40 41 fn split_commas(s: &str) -> OptionParserResult<Vec<String>> { 42 let mut list: Vec<String> = Vec::new(); 43 let mut opened_brackets = 0; 44 let mut in_quotes = false; 45 let mut current = String::new(); 46 47 for c in s.trim().chars() { 48 match c { 49 '[' => { 50 opened_brackets += 1; 51 current.push('['); 52 } 53 ']' => { 54 opened_brackets -= 1; 55 if opened_brackets < 0 { 56 return Err(OptionParserError::InvalidSyntax(s.to_owned())); 57 } 58 current.push(']'); 59 } 60 '"' => in_quotes = !in_quotes, 61 ',' => { 62 if opened_brackets > 0 || in_quotes { 63 current.push(',') 64 } else { 65 list.push(current); 66 current = String::new(); 67 } 68 } 69 c => current.push(c), 70 } 71 } 72 list.push(current); 73 74 if opened_brackets != 0 || in_quotes { 75 return Err(OptionParserError::InvalidSyntax(s.to_owned())); 76 } 77 78 Ok(list) 79 } 80 81 impl OptionParser { 82 pub fn new() -> Self { 83 Self { 84 options: HashMap::new(), 85 } 86 } 87 88 pub fn parse(&mut self, input: &str) -> OptionParserResult<()> { 89 if input.trim().is_empty() { 90 return Ok(()); 91 } 92 93 for option in split_commas(input)?.iter() { 94 let parts: Vec<&str> = option.splitn(2, '=').collect(); 95 96 match self.options.get_mut(parts[0]) { 97 None => return Err(OptionParserError::UnknownOption(parts[0].to_owned())), 98 Some(value) => { 99 if value.requires_value { 100 if parts.len() != 2 { 101 return Err(OptionParserError::InvalidSyntax((*option).to_owned())); 102 } 103 value.value = Some(parts[1].trim().to_owned()); 104 } else { 105 value.value = Some(String::new()); 106 } 107 } 108 } 109 } 110 111 Ok(()) 112 } 113 114 pub fn add(&mut self, option: &str) -> &mut Self { 115 self.options.insert( 116 option.to_owned(), 117 OptionParserValue { 118 value: None, 119 requires_value: true, 120 }, 121 ); 122 123 self 124 } 125 126 pub fn add_valueless(&mut self, option: &str) -> &mut Self { 127 self.options.insert( 128 option.to_owned(), 129 OptionParserValue { 130 value: None, 131 requires_value: false, 132 }, 133 ); 134 135 self 136 } 137 138 pub fn get(&self, option: &str) -> Option<String> { 139 self.options 140 .get(option) 141 .and_then(|v| v.value.clone()) 142 .and_then(|s| if s.is_empty() { None } else { Some(s) }) 143 } 144 145 pub fn is_set(&self, option: &str) -> bool { 146 self.options 147 .get(option) 148 .and_then(|v| v.value.as_ref()) 149 .is_some() 150 } 151 152 pub fn convert<T: FromStr>(&self, option: &str) -> OptionParserResult<Option<T>> { 153 match self.get(option) { 154 None => Ok(None), 155 Some(v) => Ok(Some(v.parse().map_err(|_| { 156 OptionParserError::Conversion(option.to_owned(), v.to_owned()) 157 })?)), 158 } 159 } 160 } 161 162 pub struct Toggle(pub bool); 163 164 pub enum ToggleParseError { 165 InvalidValue(String), 166 } 167 168 impl FromStr for Toggle { 169 type Err = ToggleParseError; 170 171 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 172 match s.to_lowercase().as_str() { 173 "" => Ok(Toggle(false)), 174 "on" => Ok(Toggle(true)), 175 "off" => Ok(Toggle(false)), 176 "true" => Ok(Toggle(true)), 177 "false" => Ok(Toggle(false)), 178 _ => Err(ToggleParseError::InvalidValue(s.to_owned())), 179 } 180 } 181 } 182 183 pub struct ByteSized(pub u64); 184 185 #[derive(Debug)] 186 pub enum ByteSizedParseError { 187 InvalidValue(String), 188 } 189 190 impl FromStr for ByteSized { 191 type Err = ByteSizedParseError; 192 193 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 194 Ok(ByteSized({ 195 let s = s.trim(); 196 let shift = if s.ends_with('K') { 197 10 198 } else if s.ends_with('M') { 199 20 200 } else if s.ends_with('G') { 201 30 202 } else { 203 0 204 }; 205 206 let s = s.trim_end_matches(|c| c == 'K' || c == 'M' || c == 'G'); 207 s.parse::<u64>() 208 .map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))? 209 << shift 210 })) 211 } 212 } 213 214 pub struct IntegerList(pub Vec<u64>); 215 216 pub enum IntegerListParseError { 217 InvalidValue(String), 218 } 219 220 impl FromStr for IntegerList { 221 type Err = IntegerListParseError; 222 223 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 224 let mut integer_list = Vec::new(); 225 let ranges_list: Vec<&str> = s 226 .trim() 227 .trim_matches(|c| c == '[' || c == ']') 228 .split(',') 229 .collect(); 230 231 for range in ranges_list.iter() { 232 let items: Vec<&str> = range.split('-').collect(); 233 234 if items.len() > 2 { 235 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 236 } 237 238 let start_range = items[0] 239 .parse::<u64>() 240 .map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?; 241 242 integer_list.push(start_range); 243 244 if items.len() == 2 { 245 let end_range = items[1] 246 .parse::<u64>() 247 .map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?; 248 if start_range >= end_range { 249 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 250 } 251 252 for i in start_range..end_range { 253 integer_list.push(i + 1); 254 } 255 } 256 } 257 258 Ok(IntegerList(integer_list)) 259 } 260 } 261 262 pub trait TupleValue { 263 fn parse_value(input: &str) -> Result<Self, TupleError> 264 where 265 Self: Sized; 266 } 267 268 impl TupleValue for u64 { 269 fn parse_value(input: &str) -> Result<Self, TupleError> { 270 input.parse::<u64>().map_err(TupleError::InvalidInteger) 271 } 272 } 273 274 impl TupleValue for Vec<u8> { 275 fn parse_value(input: &str) -> Result<Self, TupleError> { 276 Ok(IntegerList::from_str(input) 277 .map_err(TupleError::InvalidIntegerList)? 278 .0 279 .iter() 280 .map(|v| *v as u8) 281 .collect()) 282 } 283 } 284 285 impl TupleValue for Vec<u64> { 286 fn parse_value(input: &str) -> Result<Self, TupleError> { 287 Ok(IntegerList::from_str(input) 288 .map_err(TupleError::InvalidIntegerList)? 289 .0) 290 } 291 } 292 293 pub struct Tuple<S, T>(pub Vec<(S, T)>); 294 295 pub enum TupleError { 296 InvalidValue(String), 297 SplitOutsideBrackets(OptionParserError), 298 InvalidIntegerList(IntegerListParseError), 299 InvalidInteger(ParseIntError), 300 } 301 302 impl<S: FromStr, T: TupleValue> FromStr for Tuple<S, T> { 303 type Err = TupleError; 304 305 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 306 let mut list: Vec<(S, T)> = Vec::new(); 307 308 let body = s 309 .trim() 310 .strip_prefix('[') 311 .and_then(|s| s.strip_suffix(']')) 312 .ok_or_else(|| TupleError::InvalidValue(s.to_string()))?; 313 314 let tuples_list = split_commas(body).map_err(TupleError::SplitOutsideBrackets)?; 315 for tuple in tuples_list.iter() { 316 let items: Vec<&str> = tuple.split('@').collect(); 317 318 if items.len() != 2 { 319 return Err(TupleError::InvalidValue((*tuple).to_string())); 320 } 321 322 let item1 = items[0] 323 .parse::<S>() 324 .map_err(|_| TupleError::InvalidValue(items[0].to_owned()))?; 325 let item2 = TupleValue::parse_value(items[1])?; 326 327 list.push((item1, item2)); 328 } 329 330 Ok(Tuple(list)) 331 } 332 } 333 334 #[derive(Default)] 335 pub struct StringList(pub Vec<String>); 336 337 pub enum StringListParseError { 338 InvalidValue(String), 339 } 340 341 impl FromStr for StringList { 342 type Err = StringListParseError; 343 344 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 345 let string_list: Vec<String> = s 346 .trim() 347 .trim_matches(|c| c == '[' || c == ']') 348 .split(',') 349 .map(|e| e.to_owned()) 350 .collect(); 351 352 Ok(StringList(string_list)) 353 } 354 } 355 356 #[cfg(test)] 357 mod tests { 358 use super::*; 359 360 #[test] 361 fn test_option_parser() { 362 let mut parser = OptionParser::new(); 363 parser 364 .add("size") 365 .add("mergeable") 366 .add("hotplug_method") 367 .add("hotplug_size") 368 .add("topology") 369 .add("cmdline"); 370 371 assert!(parser.parse("size=128M,hanging_param").is_err()); 372 assert!(parser.parse("size=128M,too_many_equals=foo=bar").is_err()); 373 assert!(parser.parse("size=128M,file=/dev/shm").is_err()); 374 375 assert!(parser.parse("size=128M").is_ok()); 376 assert_eq!(parser.get("size"), Some("128M".to_owned())); 377 assert!(!parser.is_set("mergeable")); 378 assert!(parser.is_set("size")); 379 380 assert!(parser.parse("size=128M,mergeable=on").is_ok()); 381 assert_eq!(parser.get("size"), Some("128M".to_owned())); 382 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 383 384 assert!(parser 385 .parse("size=128M,mergeable=on,topology=[1,2]") 386 .is_ok()); 387 assert_eq!(parser.get("size"), Some("128M".to_owned())); 388 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 389 assert_eq!(parser.get("topology"), Some("[1,2]".to_owned())); 390 391 assert!(parser 392 .parse("size=128M,mergeable=on,topology=[[1,2],[3,4]]") 393 .is_ok()); 394 assert_eq!(parser.get("size"), Some("128M".to_owned())); 395 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 396 assert_eq!(parser.get("topology"), Some("[[1,2],[3,4]]".to_owned())); 397 398 assert!(parser.parse("topology=[").is_err()); 399 assert!(parser.parse("topology=[[[]]]]").is_err()); 400 401 assert!(parser.parse("cmdline=\"console=ttyS0,9600n8\"").is_ok()); 402 assert_eq!( 403 parser.get("cmdline"), 404 Some("console=ttyS0,9600n8".to_owned()) 405 ); 406 assert!(parser.parse("cmdline=\"").is_err()); 407 assert!(parser.parse("cmdline=\"\"\"").is_err()); 408 } 409 } 410