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