From: Kostya Shishkov Date: Thu, 4 Jun 2020 09:51:27 +0000 (+0200) Subject: core: redesign options module X-Git-Url: https://git.nihav.org/?p=nihav.git;a=commitdiff_plain;h=8a7d01e22bc077744806be21f81898377d0726b3 core: redesign options module --- diff --git a/nihav-core/src/options.rs b/nihav-core/src/options.rs index d3c9eb7..c2f2d4d 100644 --- a/nihav-core/src/options.rs +++ b/nihav-core/src/options.rs @@ -6,6 +6,7 @@ //! As a rule target for options should provide a list of supported options and ignore unknown options. use std::sync::Arc; +use std::fmt; /// A list specifying option parsing and validating errors. #[derive(Clone,Copy,Debug,PartialEq)] @@ -23,6 +24,23 @@ pub enum OptionError { /// A specialised `Result` type for option parsing/validation. pub type OptionResult = Result; +/// Option definition type. +#[derive(Debug)] +pub enum NAOptionDefinitionType { + /// Option may just be present. + None, + /// Option is a boolean value. + Bool, + /// Option is an integer with optional minimum and maximum value. + Int(Option, Option), + /// Option is a floating point number with optional minimum and maximum value. + Float(Option, Option), + /// Option is a string with an optional list of allowed values. + String(Option<&'static [&'static str]>), + /// Option is some binary data. + Data, +} + /// Option definition. #[derive(Debug)] pub struct NAOptionDefinition { @@ -30,16 +48,8 @@ pub struct NAOptionDefinition { pub name: &'static str, /// Option meaning. pub description: &'static str, - /// Minimal value for the option (if applicable). - pub min_value: Option, - /// Maximum value for the option (if applicable). - pub max_value: Option, - /// Allowed string values (when value is a string). - pub valid_strings: Option>, - /// Default option value. - /// - /// This is used mainly to tell in which format options should be (e.g. bool or float). - pub default_value: NAValue, + /// Option type. + pub opt_type: NAOptionDefinitionType, } impl NAOptionDefinition { @@ -48,8 +58,8 @@ impl NAOptionDefinition { let no_name = "no".to_owned() + self.name; let opt_no_name = "--no".to_owned() + self.name; if name == &no_name || name == &opt_no_name { - match self.default_value { - NAValue::Bool(_) => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)), + match self.opt_type { + NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)), _ => return Err(OptionError::InvalidFormat), }; } @@ -57,28 +67,14 @@ impl NAOptionDefinition { if self.name != name && &opt_name != name { return Err(OptionError::WrongName); } - match self.default_value { - NAValue::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)), - NAValue::Bool(_) => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)), - NAValue::Int(_) => { - if let Some(str) = value { - let ret = str.parse::(); - if let Ok(val) = ret { - let opt = NAOption { name: self.name, value: NAValue::Int(val) }; - self.check(&opt)?; - Ok((opt, 2)) - } else { - Err(OptionError::ParseError) - } - } else { - Err(OptionError::ParseError) - } - }, - NAValue::Long(_) => { + match self.opt_type { + NAOptionDefinitionType::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)), + NAOptionDefinitionType::Bool => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)), + NAOptionDefinitionType::Int(_, _) => { if let Some(str) = value { let ret = str.parse::(); if let Ok(val) = ret { - let opt = NAOption { name: self.name, value: NAValue::Long(val) }; + let opt = NAOption { name: self.name, value: NAValue::Int(val) }; self.check(&opt)?; Ok((opt, 2)) } else { @@ -88,7 +84,7 @@ impl NAOptionDefinition { Err(OptionError::ParseError) } }, - NAValue::Float(_) => { + NAOptionDefinitionType::Float(_, _) => { if let Some(str) = value { let ret = str.parse::(); if let Ok(val) = ret { @@ -102,7 +98,7 @@ impl NAOptionDefinition { Err(OptionError::ParseError) } }, - NAValue::String(_) => { + NAOptionDefinitionType::String(_) => { if let Some(str) = value { let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) }; self.check(&opt)?; @@ -123,52 +119,53 @@ impl NAOptionDefinition { NAValue::None => Ok(()), NAValue::Bool(_) => Ok(()), NAValue::Int(intval) => { - if let Some(ref minval) = self.min_value { - let (minres, _) = Self::compare(i64::from(intval), minval)?; - if !minres { - return Err(OptionError::InvalidValue); - } - } - if let Some(ref maxval) = self.max_value { - let (_, maxres) = Self::compare(i64::from(intval), maxval)?; - if !maxres { - return Err(OptionError::InvalidValue); - } - } - Ok(()) - }, - NAValue::Long(intval) => { - if let Some(ref minval) = self.min_value { - let (minres, _) = Self::compare(intval, minval)?; - if !minres { - return Err(OptionError::InvalidValue); - } - } - if let Some(ref maxval) = self.max_value { - let (_, maxres) = Self::compare(intval, maxval)?; - if !maxres { - return Err(OptionError::InvalidValue); - } - } + match self.opt_type { + NAOptionDefinitionType::Int(minval, maxval) => { + if let Some(minval) = minval { + if intval < minval { return Err(OptionError::InvalidValue); } + } + if let Some(maxval) = maxval { + if intval > maxval { return Err(OptionError::InvalidValue); } + } + }, + NAOptionDefinitionType::Float(minval, maxval) => { + let fval = intval as f64; + if let Some(minval) = minval { + if fval < minval { return Err(OptionError::InvalidValue); } + } + if let Some(maxval) = maxval { + if fval > maxval { return Err(OptionError::InvalidValue); } + } + }, + _ => return Err(OptionError::InvalidFormat), + }; Ok(()) }, NAValue::Float(fval) => { - if let Some(ref minval) = self.min_value { - let (minres, _) = Self::compare_f64(fval, minval)?; - if !minres { - return Err(OptionError::InvalidValue); - } - } - if let Some(ref maxval) = self.max_value { - let (_, maxres) = Self::compare_f64(fval, maxval)?; - if !maxres { - return Err(OptionError::InvalidValue); - } - } + match self.opt_type { + NAOptionDefinitionType::Int(minval, maxval) => { + let intval = fval as i64; + if let Some(minval) = minval { + if intval < minval { return Err(OptionError::InvalidValue); } + } + if let Some(maxval) = maxval { + if intval > maxval { return Err(OptionError::InvalidValue); } + } + }, + NAOptionDefinitionType::Float(minval, maxval) => { + if let Some(minval) = minval { + if fval < minval { return Err(OptionError::InvalidValue); } + } + if let Some(maxval) = maxval { + if fval > maxval { return Err(OptionError::InvalidValue); } + } + }, + _ => return Err(OptionError::InvalidFormat), + }; Ok(()) }, NAValue::String(ref cur_str) => { - if let Some(ref strings) = self.valid_strings { + if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type { for str in strings.iter() { if cur_str == str { return Ok(()); @@ -182,32 +179,39 @@ impl NAOptionDefinition { NAValue::Data(_) => Ok(()), } } - fn compare(val: i64, ref_val: &NAValue) -> OptionResult<(bool, bool)> { - match ref_val { - NAValue::Int(ref_min) => { - Ok((val >= i64::from(*ref_min), val <= i64::from(*ref_min))) - }, - NAValue::Long(ref_min) => { - Ok((val >= *ref_min, val <= *ref_min)) - }, - NAValue::Float(ref_min) => { - Ok(((val as f64) >= *ref_min, (val as f64) <= *ref_min)) - }, - _ => Err(OptionError::InvalidFormat), - } - } - fn compare_f64(val: f64, ref_val: &NAValue) -> OptionResult<(bool, bool)> { - match ref_val { - NAValue::Int(ref_min) => { - Ok((val >= f64::from(*ref_min), val <= f64::from(*ref_min))) +} + +impl fmt::Display for NAOptionDefinition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.opt_type { + NAOptionDefinitionType::None => write!(f, "{}: {}", self.name, self.description), + NAOptionDefinitionType::Bool => write!(f, "[no]{}: {}", self.name, self.description), + NAOptionDefinitionType::String(ref str_list) => { + if let Some(ref opts) = str_list { + write!(f, "{} {}: {}", self.name, opts.join("|"), self.description) + } else { + write!(f, "{} : {}", self.name, self.description) + } }, - NAValue::Long(ref_min) => { - Ok((val >= (*ref_min as f64), val <= (*ref_min as f64))) + NAOptionDefinitionType::Int(minval, maxval) => { + let range = match (&minval, &maxval) { + (Some(minval), None) => format!("{}-..", minval), + (None, Some(maxval)) => format!("..-{}", maxval), + (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), + _ => "".to_string(), + }; + write!(f, "{} {}: {}", self.name, range, self.description) }, - NAValue::Float(ref_min) => { - Ok((val >= *ref_min, val <= *ref_min)) + NAOptionDefinitionType::Float(minval, maxval) => { + let range = match (&minval, &maxval) { + (Some(minval), None) => format!("{}-..", minval), + (None, Some(maxval)) => format!("..-{}", maxval), + (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), + _ => "".to_string(), + }; + write!(f, "{} {}: {}", self.name, range, self.description) }, - _ => Err(OptionError::InvalidFormat), + NAOptionDefinitionType::Data => write!(f, "{} : {}", self.name, self.description), } } } @@ -229,9 +233,7 @@ pub enum NAValue { /// Boolean value. Bool(bool), /// Integer value. - Int(i32), - /// Long integer value. - Long(i64), + Int(i64), /// Floating point value. Float(f64), /// String value. @@ -256,34 +258,33 @@ mod test { #[test] fn test_option_validation() { let option = NAOption {name: "option", value: NAValue::Int(42) }; - let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Float(0.0) }; + let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) }; assert!(def.check(&option).is_ok()); - def.max_value = Some(NAValue::Long(30)); + def.opt_type = NAOptionDefinitionType::Int(None, Some(30)); assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); - def.max_value = None; - def.min_value = Some(NAValue::Int(40)); + def.opt_type = NAOptionDefinitionType::Int(Some(40), None); assert!(def.check(&option).is_ok()); def.name = "option2"; assert_eq!(def.check(&option), Err(OptionError::WrongName)); let option = NAOption {name: "option", value: NAValue::String("test".to_string()) }; - let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::String("".to_string()) }; + let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) }; assert!(def.check(&option).is_ok()); - def.valid_strings = Some(vec!["a string".to_string(), "test string".to_string()]); + def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"])); assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); - def.valid_strings = Some(vec!["a string".to_string(), "test".to_string()]); + def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"])); assert!(def.check(&option).is_ok()); } #[test] fn test_option_parsing() { - let mut def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Float(0.0) }; + let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) }; assert_eq!(def.parse(&"--option".to_string(), None), Err(OptionError::ParseError)); assert_eq!(def.parse(&"--nooption".to_string(), None), Err(OptionError::InvalidFormat)); assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())), Ok((NAOption{name:"option",value: NAValue::Float(42.0)}, 2))); - def.max_value = Some(NAValue::Float(40.0)); + def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0)); assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())), Err(OptionError::InvalidValue)); - let def = NAOptionDefinition { name: "option", description: "", min_value: None, max_value: None, valid_strings: None, default_value: NAValue::Bool(true) }; + let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool }; assert_eq!(def.parse(&"option".to_string(), None), Ok((NAOption{name: "option", value: NAValue::Bool(true) }, 1))); assert_eq!(def.parse(&"nooption".to_string(), None),