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