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