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