xref: /cloud-hypervisor/option_parser/src/lib.rs (revision bc6acb842f1ebb263245cd95fe5a92fe5f350bd3)
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(|c| c == 'K' || c == 'M' || c == '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         assert!(parser.parse("size=128M,hanging_param").is_err());
385         assert!(parser.parse("size=128M,too_many_equals=foo=bar").is_err());
386         assert!(parser.parse("size=128M,file=/dev/shm").is_err());
387 
388         assert!(parser.parse("size=128M").is_ok());
389         assert_eq!(parser.get("size"), Some("128M".to_owned()));
390         assert!(!parser.is_set("mergeable"));
391         assert!(parser.is_set("size"));
392 
393         assert!(parser.parse("size=128M,mergeable=on").is_ok());
394         assert_eq!(parser.get("size"), Some("128M".to_owned()));
395         assert_eq!(parser.get("mergeable"), Some("on".to_owned()));
396 
397         assert!(parser
398             .parse("size=128M,mergeable=on,topology=[1,2]")
399             .is_ok());
400         assert_eq!(parser.get("size"), Some("128M".to_owned()));
401         assert_eq!(parser.get("mergeable"), Some("on".to_owned()));
402         assert_eq!(parser.get("topology"), Some("[1,2]".to_owned()));
403 
404         assert!(parser
405             .parse("size=128M,mergeable=on,topology=[[1,2],[3,4]]")
406             .is_ok());
407         assert_eq!(parser.get("size"), Some("128M".to_owned()));
408         assert_eq!(parser.get("mergeable"), Some("on".to_owned()));
409         assert_eq!(parser.get("topology"), Some("[[1,2],[3,4]]".to_owned()));
410 
411         assert!(parser.parse("topology=[").is_err());
412         assert!(parser.parse("topology=[[[]]]]").is_err());
413 
414         assert!(parser.parse("cmdline=\"console=ttyS0,9600n8\"").is_ok());
415         assert_eq!(
416             parser.get("cmdline"),
417             Some("console=ttyS0,9600n8".to_owned())
418         );
419         assert!(parser.parse("cmdline=\"").is_err());
420         assert!(parser.parse("cmdline=\"\"\"").is_err());
421     }
422 }
423