xref: /cloud-hypervisor/option_parser/src/lib.rs (revision 7d7bfb2034001d4cb15df2ddc56d2d350c8da30f)
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.split('=').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