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