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 parse {} for {}", value, field) 35 } 36 } 37 } 38 } 39 type OptionParserResult<T> = std::result::Result<T, OptionParserError>; 40 41 fn split_commas_outside_brackets(s: &str) -> OptionParserResult<Vec<String>> { 42 let mut list: Vec<String> = Vec::new(); 43 let mut opened_brackets: usize = 0; 44 for element in s.trim().split(',') { 45 if opened_brackets > 0 { 46 if let Some(last) = list.last_mut() { 47 *last = format!("{},{}", last, element); 48 } else { 49 return Err(OptionParserError::InvalidSyntax(s.to_owned())); 50 } 51 } else { 52 list.push(element.to_string()); 53 } 54 55 opened_brackets += element.matches('[').count(); 56 let closing_brackets = element.matches(']').count(); 57 if closing_brackets > opened_brackets { 58 return Err(OptionParserError::InvalidSyntax(s.to_owned())); 59 } else { 60 opened_brackets -= closing_brackets; 61 } 62 } 63 64 Ok(list) 65 } 66 67 impl OptionParser { 68 pub fn new() -> Self { 69 Self { 70 options: HashMap::new(), 71 } 72 } 73 74 pub fn parse(&mut self, input: &str) -> OptionParserResult<()> { 75 if input.trim().is_empty() { 76 return Ok(()); 77 } 78 79 for option in split_commas_outside_brackets(input)?.iter() { 80 let parts: Vec<&str> = option.splitn(2, '=').collect(); 81 82 match self.options.get_mut(parts[0]) { 83 None => return Err(OptionParserError::UnknownOption(parts[0].to_owned())), 84 Some(value) => { 85 if value.requires_value { 86 if parts.len() != 2 { 87 return Err(OptionParserError::InvalidSyntax((*option).to_owned())); 88 } 89 value.value = Some(parts[1].trim().to_owned()); 90 } else { 91 value.value = Some(String::new()); 92 } 93 } 94 } 95 } 96 97 Ok(()) 98 } 99 100 pub fn add(&mut self, option: &str) -> &mut Self { 101 self.options.insert( 102 option.to_owned(), 103 OptionParserValue { 104 value: None, 105 requires_value: true, 106 }, 107 ); 108 109 self 110 } 111 112 pub fn add_valueless(&mut self, option: &str) -> &mut Self { 113 self.options.insert( 114 option.to_owned(), 115 OptionParserValue { 116 value: None, 117 requires_value: false, 118 }, 119 ); 120 121 self 122 } 123 124 pub fn get(&self, option: &str) -> Option<String> { 125 self.options 126 .get(option) 127 .and_then(|v| v.value.clone()) 128 .and_then(|s| if s.is_empty() { None } else { Some(s) }) 129 } 130 131 pub fn is_set(&self, option: &str) -> bool { 132 self.options 133 .get(option) 134 .and_then(|v| v.value.as_ref()) 135 .is_some() 136 } 137 138 pub fn convert<T: FromStr>(&self, option: &str) -> OptionParserResult<Option<T>> { 139 match self.get(option) { 140 None => Ok(None), 141 Some(v) => Ok(Some(v.parse().map_err(|_| { 142 OptionParserError::Conversion(option.to_owned(), v.to_owned()) 143 })?)), 144 } 145 } 146 } 147 148 pub struct Toggle(pub bool); 149 150 pub enum ToggleParseError { 151 InvalidValue(String), 152 } 153 154 impl FromStr for Toggle { 155 type Err = ToggleParseError; 156 157 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 158 match s.to_lowercase().as_str() { 159 "" => Ok(Toggle(false)), 160 "on" => Ok(Toggle(true)), 161 "off" => Ok(Toggle(false)), 162 "true" => Ok(Toggle(true)), 163 "false" => Ok(Toggle(false)), 164 _ => Err(ToggleParseError::InvalidValue(s.to_owned())), 165 } 166 } 167 } 168 169 pub struct ByteSized(pub u64); 170 171 #[derive(Debug)] 172 pub enum ByteSizedParseError { 173 InvalidValue(String), 174 } 175 176 impl FromStr for ByteSized { 177 type Err = ByteSizedParseError; 178 179 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 180 Ok(ByteSized({ 181 let s = s.trim(); 182 let shift = if s.ends_with('K') { 183 10 184 } else if s.ends_with('M') { 185 20 186 } else if s.ends_with('G') { 187 30 188 } else { 189 0 190 }; 191 192 let s = s.trim_end_matches(|c| c == 'K' || c == 'M' || c == 'G'); 193 s.parse::<u64>() 194 .map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))? 195 << shift 196 })) 197 } 198 } 199 200 pub struct IntegerList(pub Vec<u64>); 201 202 pub enum IntegerListParseError { 203 InvalidValue(String), 204 } 205 206 impl FromStr for IntegerList { 207 type Err = IntegerListParseError; 208 209 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 210 let mut integer_list = Vec::new(); 211 let ranges_list: Vec<&str> = s 212 .trim() 213 .trim_matches(|c| c == '[' || c == ']') 214 .split(',') 215 .collect(); 216 217 for range in ranges_list.iter() { 218 let items: Vec<&str> = range.split('-').collect(); 219 220 if items.len() > 2 { 221 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 222 } 223 224 let start_range = items[0] 225 .parse::<u64>() 226 .map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?; 227 228 integer_list.push(start_range); 229 230 if items.len() == 2 { 231 let end_range = items[1] 232 .parse::<u64>() 233 .map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?; 234 if start_range >= end_range { 235 return Err(IntegerListParseError::InvalidValue((*range).to_string())); 236 } 237 238 for i in start_range..end_range { 239 integer_list.push(i + 1); 240 } 241 } 242 } 243 244 Ok(IntegerList(integer_list)) 245 } 246 } 247 248 pub trait TupleValue { 249 fn parse_value(input: &str) -> Result<Self, TupleError> 250 where 251 Self: Sized; 252 } 253 254 impl TupleValue for u64 { 255 fn parse_value(input: &str) -> Result<Self, TupleError> { 256 input.parse::<u64>().map_err(TupleError::InvalidInteger) 257 } 258 } 259 260 impl TupleValue for Vec<u8> { 261 fn parse_value(input: &str) -> Result<Self, TupleError> { 262 Ok(IntegerList::from_str(input) 263 .map_err(TupleError::InvalidIntegerList)? 264 .0 265 .iter() 266 .map(|v| *v as u8) 267 .collect()) 268 } 269 } 270 271 impl TupleValue for Vec<u64> { 272 fn parse_value(input: &str) -> Result<Self, TupleError> { 273 Ok(IntegerList::from_str(input) 274 .map_err(TupleError::InvalidIntegerList)? 275 .0) 276 } 277 } 278 279 pub struct Tuple<S, T>(pub Vec<(S, T)>); 280 281 pub enum TupleError { 282 InvalidValue(String), 283 SplitOutsideBrackets(OptionParserError), 284 InvalidIntegerList(IntegerListParseError), 285 InvalidInteger(ParseIntError), 286 } 287 288 impl<S: FromStr, T: TupleValue> FromStr for Tuple<S, T> { 289 type Err = TupleError; 290 291 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 292 let mut list: Vec<(S, T)> = Vec::new(); 293 294 let tuples_list = 295 split_commas_outside_brackets(s.trim().trim_matches(|c| c == '[' || c == ']')) 296 .map_err(TupleError::SplitOutsideBrackets)?; 297 for tuple in tuples_list.iter() { 298 let items: Vec<&str> = tuple.split('@').collect(); 299 300 if items.len() != 2 { 301 return Err(TupleError::InvalidValue((*tuple).to_string())); 302 } 303 304 let item1 = items[0] 305 .parse::<S>() 306 .map_err(|_| TupleError::InvalidValue(items[0].to_owned()))?; 307 let item2 = TupleValue::parse_value(items[1])?; 308 309 list.push((item1, item2)); 310 } 311 312 Ok(Tuple(list)) 313 } 314 } 315 316 #[derive(Default)] 317 pub struct StringList(pub Vec<String>); 318 319 pub enum StringListParseError { 320 InvalidValue(String), 321 } 322 323 impl FromStr for StringList { 324 type Err = StringListParseError; 325 326 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 327 let string_list: Vec<String> = s 328 .trim() 329 .trim_matches(|c| c == '[' || c == ']') 330 .split(',') 331 .map(|e| e.to_owned()) 332 .collect(); 333 334 Ok(StringList(string_list)) 335 } 336 } 337