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 #[derive(Error, Debug)] 159 pub enum ToggleParseError { 160 #[error("invalid value: {0}")] 161 InvalidValue(String), 162 } 163 164 impl FromStr for Toggle { 165 type Err = ToggleParseError; 166 167 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 168 match s.to_lowercase().as_str() { 169 "" => Ok(Toggle(false)), 170 "on" => Ok(Toggle(true)), 171 "off" => Ok(Toggle(false)), 172 "true" => Ok(Toggle(true)), 173 "false" => Ok(Toggle(false)), 174 _ => Err(ToggleParseError::InvalidValue(s.to_owned())), 175 } 176 } 177 } 178 179 pub struct ByteSized(pub u64); 180 181 #[derive(Error, Debug)] 182 pub enum ByteSizedParseError { 183 #[error("invalid value: {0}")] 184 InvalidValue(String), 185 } 186 187 impl FromStr for ByteSized { 188 type Err = ByteSizedParseError; 189 190 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 191 Ok(ByteSized({ 192 let s = s.trim(); 193 let shift = if s.ends_with('K') { 194 10 195 } else if s.ends_with('M') { 196 20 197 } else if s.ends_with('G') { 198 30 199 } else { 200 0 201 }; 202 203 let s = s.trim_end_matches(['K', 'M', 'G']); 204 s.parse::<u64>() 205 .map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))? 206 << shift 207 })) 208 } 209 } 210 211 pub struct IntegerList(pub Vec<u64>); 212 213 #[derive(Error, Debug)] 214 pub enum IntegerListParseError { 215 #[error("invalid value: {0}")] 216 InvalidValue(String), 217 } 218 219 impl FromStr for IntegerList { 220 type Err = IntegerListParseError; 221 222 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 223 let mut integer_list = Vec::new(); 224 let ranges_list: Vec<&str> = s 225 .trim() 226 .trim_matches(|c| c == '[' || c == ']') 227 .split(',') 228 .collect(); 229 230 for range in ranges_list.iter() { 231 let items: Vec<&str> = range.split('-').collect(); 232 233 if items.len() > 2 { 234 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 235 } 236 237 let start_range = items[0] 238 .parse::<u64>() 239 .map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?; 240 241 integer_list.push(start_range); 242 243 if items.len() == 2 { 244 let end_range = items[1] 245 .parse::<u64>() 246 .map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?; 247 if start_range >= end_range { 248 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 249 } 250 251 for i in start_range..end_range { 252 integer_list.push(i + 1); 253 } 254 } 255 } 256 257 Ok(IntegerList(integer_list)) 258 } 259 } 260 261 pub trait TupleValue { 262 fn parse_value(input: &str) -> Result<Self, TupleError> 263 where 264 Self: Sized; 265 } 266 267 impl TupleValue for u64 { 268 fn parse_value(input: &str) -> Result<Self, TupleError> { 269 input.parse::<u64>().map_err(TupleError::InvalidInteger) 270 } 271 } 272 273 impl TupleValue for Vec<u8> { 274 fn parse_value(input: &str) -> Result<Self, TupleError> { 275 Ok(IntegerList::from_str(input) 276 .map_err(TupleError::InvalidIntegerList)? 277 .0 278 .iter() 279 .map(|v| *v as u8) 280 .collect()) 281 } 282 } 283 284 impl TupleValue for Vec<u64> { 285 fn parse_value(input: &str) -> Result<Self, TupleError> { 286 Ok(IntegerList::from_str(input) 287 .map_err(TupleError::InvalidIntegerList)? 288 .0) 289 } 290 } 291 292 impl TupleValue for Vec<usize> { 293 fn parse_value(input: &str) -> Result<Self, TupleError> { 294 Ok(IntegerList::from_str(input) 295 .map_err(TupleError::InvalidIntegerList)? 296 .0 297 .iter() 298 .map(|v| *v as usize) 299 .collect()) 300 } 301 } 302 303 pub struct Tuple<S, T>(pub Vec<(S, T)>); 304 305 #[derive(Error, Debug)] 306 pub enum TupleError { 307 #[error("invalid value: {0}")] 308 InvalidValue(String), 309 #[error("split outside brackets: {0}")] 310 SplitOutsideBrackets(#[source] OptionParserError), 311 #[error("invalid integer list: {0}")] 312 InvalidIntegerList(#[source] IntegerListParseError), 313 #[error("invalid integer: {0}")] 314 InvalidInteger(#[source] ParseIntError), 315 } 316 317 impl<S: FromStr, T: TupleValue> FromStr for Tuple<S, T> { 318 type Err = TupleError; 319 320 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 321 let mut list: Vec<(S, T)> = Vec::new(); 322 323 let body = s 324 .trim() 325 .strip_prefix('[') 326 .and_then(|s| s.strip_suffix(']')) 327 .ok_or_else(|| TupleError::InvalidValue(s.to_string()))?; 328 329 let tuples_list = split_commas(body).map_err(TupleError::SplitOutsideBrackets)?; 330 for tuple in tuples_list.iter() { 331 let items: Vec<&str> = tuple.split('@').collect(); 332 333 if items.len() != 2 { 334 return Err(TupleError::InvalidValue((*tuple).to_string())); 335 } 336 337 let item1 = items[0] 338 .parse::<S>() 339 .map_err(|_| TupleError::InvalidValue(items[0].to_owned()))?; 340 let item2 = TupleValue::parse_value(items[1])?; 341 342 list.push((item1, item2)); 343 } 344 345 Ok(Tuple(list)) 346 } 347 } 348 349 #[derive(Default)] 350 pub struct StringList(pub Vec<String>); 351 352 #[derive(Error, Debug)] 353 pub enum StringListParseError { 354 #[error("invalid value: {0}")] 355 InvalidValue(String), 356 } 357 358 impl FromStr for StringList { 359 type Err = StringListParseError; 360 361 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 362 let string_list: Vec<String> = s 363 .trim() 364 .trim_matches(|c| c == '[' || c == ']') 365 .split(',') 366 .map(|e| e.to_owned()) 367 .collect(); 368 369 Ok(StringList(string_list)) 370 } 371 } 372 373 #[cfg(test)] 374 mod tests { 375 use super::*; 376 377 #[test] 378 fn test_option_parser() { 379 let mut parser = OptionParser::new(); 380 parser 381 .add("size") 382 .add("mergeable") 383 .add("hotplug_method") 384 .add("hotplug_size") 385 .add("topology") 386 .add("cmdline"); 387 388 parser.parse("size=128M,hanging_param").unwrap_err(); 389 parser 390 .parse("size=128M,too_many_equals=foo=bar") 391 .unwrap_err(); 392 parser.parse("size=128M,file=/dev/shm").unwrap_err(); 393 394 parser.parse("size=128M").unwrap(); 395 assert_eq!(parser.get("size"), Some("128M".to_owned())); 396 assert!(!parser.is_set("mergeable")); 397 assert!(parser.is_set("size")); 398 399 parser.parse("size=128M,mergeable=on").unwrap(); 400 assert_eq!(parser.get("size"), Some("128M".to_owned())); 401 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 402 403 parser 404 .parse("size=128M,mergeable=on,topology=[1,2]") 405 .unwrap(); 406 assert_eq!(parser.get("size"), Some("128M".to_owned())); 407 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 408 assert_eq!(parser.get("topology"), Some("[1,2]".to_owned())); 409 410 parser 411 .parse("size=128M,mergeable=on,topology=[[1,2],[3,4]]") 412 .unwrap(); 413 assert_eq!(parser.get("size"), Some("128M".to_owned())); 414 assert_eq!(parser.get("mergeable"), Some("on".to_owned())); 415 assert_eq!(parser.get("topology"), Some("[[1,2],[3,4]]".to_owned())); 416 417 parser.parse("topology=[").unwrap_err(); 418 parser.parse("topology=[[[]]]]").unwrap_err(); 419 420 parser.parse("cmdline=\"console=ttyS0,9600n8\"").unwrap(); 421 assert_eq!( 422 parser.get("cmdline"), 423 Some("console=ttyS0,9600n8".to_owned()) 424 ); 425 parser.parse("cmdline=\"").unwrap_err(); 426 parser.parse("cmdline=\"\"\"").unwrap_err(); 427 } 428 } 429