core: redesign options module
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 4 Jun 2020 09:51:27 +0000 (11:51 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 4 Jun 2020 09:51:27 +0000 (11:51 +0200)
nihav-core/src/options.rs

index d3c9eb76b000fc7aa06eb41bd03a28c57e670f05..c2f2d4d0ab2971502a0014cc744962e489001b78 100644 (file)
@@ -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<T> = Result<T, OptionError>;
 
+/// 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<i64>, Option<i64>),
+    /// Option is a floating point number with optional minimum and maximum value.
+    Float(Option<f64>, Option<f64>),
+    /// 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<NAValue>,
-    /// Maximum value for the option (if applicable).
-    pub max_value:      Option<NAValue>,
-    /// Allowed string values (when value is a string).
-    pub valid_strings:  Option<Vec<String>>,
-    /// 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::<i32>();
-                    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::<i64>();
                     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::<f64>();
                     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, "{} <string>: {}", 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),
+                        _ => "<integer>".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),
+                        _ => "<float>".to_string(),
+                    };
+                write!(f, "{} {}: {}", self.name, range, self.description)
             },
-            _ => Err(OptionError::InvalidFormat),
+            NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", 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),