core: split options into separate module
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 31 May 2020 13:14:16 +0000 (15:14 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 31 May 2020 13:18:57 +0000 (15:18 +0200)
nihav-core/src/frame.rs
nihav-core/src/lib.rs
nihav-core/src/options.rs [new file with mode: 0644]

index badc7f8c0a542a3a64b60e860cd5490b72404344..4e317fe3e76edc36d02bdb95ee4aa48c3fbe14c1 100644 (file)
@@ -857,47 +857,6 @@ pub const DUMMY_CODEC_INFO: NACodecInfo = NACodecInfo {
                                 properties: NACodecTypeInfo::None,
                                 extradata: None };
 
-/// Option definition.
-#[derive(Debug)]
-pub struct NAOptionDefinition {
-    /// Option name.
-    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>,
-}
-
-/// Option.
-#[derive(Clone,Debug,PartialEq)]
-pub struct NAOption {
-    /// Option name.
-    pub name:   String,
-    /// Option value.
-    pub value:  NAValue,
-}
-
-/// A list of accepted option values.
-#[derive(Debug,Clone,PartialEq)]
-pub enum NAValue {
-    /// Empty value.
-    None,
-    /// Boolean value.
-    Bool(bool),
-    /// Integer value.
-    Int(i32),
-    /// Long integer value.
-    Long(i64),
-    /// Floating point value.
-    Float(f32),
-    /// String value.
-    String(String),
-    /// Binary data value.
-    Data(Arc<Vec<u8>>),
-}
-
 /// A list of recognized frame types.
 #[derive(Debug,Clone,Copy,PartialEq)]
 #[allow(dead_code)]
index c98956d85a3957ecc92ab3685d1dfabc5c2fb61b..303fb244ddb01a1fc9164b13bb480b2c571f497b 100644 (file)
@@ -20,6 +20,7 @@ pub mod formats;
 pub mod frame;
 #[allow(clippy::too_many_arguments)]
 pub mod io;
+pub mod options;
 pub mod refs;
 pub mod reorder;
 pub mod scale;
diff --git a/nihav-core/src/options.rs b/nihav-core/src/options.rs
new file mode 100644 (file)
index 0000000..d3c9eb7
--- /dev/null
@@ -0,0 +1,292 @@
+//! Options support.
+//!
+//! This module contains the definitions for options and names for common options.
+//! Options are used to set custom parameters in e.g. decoders or muxers.
+//!
+//! As a rule target for options should provide a list of supported options and ignore unknown options.
+
+use std::sync::Arc;
+
+/// A list specifying option parsing and validating errors.
+#[derive(Clone,Copy,Debug,PartialEq)]
+pub enum OptionError {
+    /// Input is not intended for the current option definition.
+    WrongName,
+    /// Option value is not in the expected format.
+    InvalidFormat,
+    /// Option value was not in the range.
+    InvalidValue,
+    /// Parse error.
+    ParseError,
+}
+
+/// A specialised `Result` type for option parsing/validation.
+pub type OptionResult<T> = Result<T, OptionError>;
+
+/// Option definition.
+#[derive(Debug)]
+pub struct NAOptionDefinition {
+    /// Option name.
+    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,
+}
+
+impl NAOptionDefinition {
+    /// Tries to parse input string(s) as an option and returns new option and number of arguments used (1 or 2) on success.
+    pub fn parse(&self, name: &String, value: Option<&String>) -> OptionResult<(NAOption, usize)> {
+        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)),
+                _ => return Err(OptionError::InvalidFormat),
+            };
+        }
+        let opt_name = "--".to_owned() + self.name;
+        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(_) => {
+                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) };
+                        self.check(&opt)?;
+                        Ok((opt, 2))
+                    } else {
+                        Err(OptionError::ParseError)
+                    }
+                } else {
+                    Err(OptionError::ParseError)
+                }
+            },
+            NAValue::Float(_) => {
+                if let Some(str) = value {
+                    let ret = str.parse::<f64>();
+                    if let Ok(val) = ret {
+                        let opt = NAOption { name: self.name, value: NAValue::Float(val) };
+                        self.check(&opt)?;
+                        Ok((opt, 2))
+                    } else {
+                        Err(OptionError::ParseError)
+                    }
+                } else {
+                    Err(OptionError::ParseError)
+                }
+            },
+            NAValue::String(_) => {
+                if let Some(str) = value {
+                    let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) };
+                    self.check(&opt)?;
+                    Ok((opt, 2))
+                } else {
+                    Err(OptionError::ParseError)
+                }
+            },
+            _ => unimplemented!(),
+        }
+    }
+    /// Checks whether input option conforms to the definition i.e. whether it has proper format and it lies in range.
+    pub fn check(&self, option: &NAOption) -> OptionResult<()> {
+        if option.name != self.name {
+            return Err(OptionError::WrongName);
+        }
+        match option.value {
+            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);
+                    }
+                }
+                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);
+                    }
+                }
+                Ok(())
+            },
+            NAValue::String(ref cur_str) => {
+                if let Some(ref strings) = self.valid_strings {
+                    for str in strings.iter() {
+                        if cur_str == str {
+                            return Ok(());
+                        }
+                    }
+                    Err(OptionError::InvalidValue)
+                } else {
+                    Ok(())
+                }
+            },
+            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)))
+            },
+            NAValue::Long(ref_min) => {
+                Ok((val >= (*ref_min as f64), val <= (*ref_min as f64)))
+            },
+            NAValue::Float(ref_min) => {
+                Ok((val >= *ref_min, val <= *ref_min))
+            },
+            _ => Err(OptionError::InvalidFormat),
+        }
+    }
+}
+
+/// Option.
+#[derive(Clone,Debug,PartialEq)]
+pub struct NAOption {
+    /// Option name.
+    pub name:   &'static str,
+    /// Option value.
+    pub value:  NAValue,
+}
+
+/// A list of accepted option values.
+#[derive(Debug,Clone,PartialEq)]
+pub enum NAValue {
+    /// Empty value.
+    None,
+    /// Boolean value.
+    Bool(bool),
+    /// Integer value.
+    Int(i32),
+    /// Long integer value.
+    Long(i64),
+    /// Floating point value.
+    Float(f64),
+    /// String value.
+    String(String),
+    /// Binary data value.
+    Data(Arc<Vec<u8>>),
+}
+
+/// Trait for all objects that handle `NAOption`.
+pub trait NAOptionHandler {
+    /// Returns the options recognised by current object.
+    fn get_supported_options(&self) -> &[NAOptionDefinition];
+    /// Passes options for the object to set (or ignore).
+    fn set_options(&mut self, options: &[NAOption]);
+    /// Queries the current option value in the object (if present).
+    fn query_option_value(&self, name: &str) -> Option<NAValue>;
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[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) };
+        assert!(def.check(&option).is_ok());
+        def.max_value = Some(NAValue::Long(30));
+        assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
+        def.max_value = None;
+        def.min_value = Some(NAValue::Int(40));
+        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()) };
+        assert!(def.check(&option).is_ok());
+        def.valid_strings = Some(vec!["a string".to_string(), "test string".to_string()]);
+        assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
+        def.valid_strings = Some(vec!["a string".to_string(), "test".to_string()]);
+        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) };
+        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));
+        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) };
+        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),
+                   Ok((NAOption{name: "option", value: NAValue::Bool(false) }, 1)));
+    }
+}