| 1 | //! Options support. |
| 2 | //! |
| 3 | //! This module contains the definitions for options and names for common options. |
| 4 | //! Options are used to set custom parameters in e.g. decoders or muxers. |
| 5 | //! |
| 6 | //! As a rule target for options should provide a list of supported options and ignore unknown options. |
| 7 | |
| 8 | use std::sync::Arc; |
| 9 | use std::fmt; |
| 10 | |
| 11 | /// Common name for keyframe interval option. |
| 12 | pub const KEYFRAME_OPTION: &'static str = "key_int"; |
| 13 | /// Common description for keyframe interval option. |
| 14 | pub const KEYFRAME_OPTION_DESC: &'static str = "Keyframe interval (0 - automatic)"; |
| 15 | |
| 16 | /// A list specifying option parsing and validating errors. |
| 17 | #[derive(Clone,Copy,Debug,PartialEq)] |
| 18 | pub enum OptionError { |
| 19 | /// Input is not intended for the current option definition. |
| 20 | WrongName, |
| 21 | /// Option value is not in the expected format. |
| 22 | InvalidFormat, |
| 23 | /// Option value was not in the range. |
| 24 | InvalidValue, |
| 25 | /// Parse error. |
| 26 | ParseError, |
| 27 | } |
| 28 | |
| 29 | /// A specialised `Result` type for option parsing/validation. |
| 30 | pub type OptionResult<T> = Result<T, OptionError>; |
| 31 | |
| 32 | /// Option definition type. |
| 33 | #[derive(Debug)] |
| 34 | pub enum NAOptionDefinitionType { |
| 35 | /// Option may just be present. |
| 36 | None, |
| 37 | /// Option is a boolean value. |
| 38 | Bool, |
| 39 | /// Option is an integer with optional minimum and maximum value. |
| 40 | Int(Option<i64>, Option<i64>), |
| 41 | /// Option is a floating point number with optional minimum and maximum value. |
| 42 | Float(Option<f64>, Option<f64>), |
| 43 | /// Option is a string with an optional list of allowed values. |
| 44 | String(Option<&'static [&'static str]>), |
| 45 | /// Option is some binary data. |
| 46 | Data, |
| 47 | } |
| 48 | |
| 49 | /// Option definition. |
| 50 | #[derive(Debug)] |
| 51 | pub struct NAOptionDefinition { |
| 52 | /// Option name. |
| 53 | pub name: &'static str, |
| 54 | /// Option meaning. |
| 55 | pub description: &'static str, |
| 56 | /// Option type. |
| 57 | pub opt_type: NAOptionDefinitionType, |
| 58 | } |
| 59 | |
| 60 | impl NAOptionDefinition { |
| 61 | /// Tries to parse input string(s) as an option and returns new option and number of arguments used (1 or 2) on success. |
| 62 | pub fn parse(&self, name: &String, value: Option<&String>) -> OptionResult<(NAOption, usize)> { |
| 63 | let no_name = "no".to_owned() + self.name; |
| 64 | let opt_no_name = "--no".to_owned() + self.name; |
| 65 | if name == &no_name || name == &opt_no_name { |
| 66 | match self.opt_type { |
| 67 | NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)), |
| 68 | _ => return Err(OptionError::InvalidFormat), |
| 69 | }; |
| 70 | } |
| 71 | let opt_name = "--".to_owned() + self.name; |
| 72 | if self.name != name && &opt_name != name { |
| 73 | return Err(OptionError::WrongName); |
| 74 | } |
| 75 | match self.opt_type { |
| 76 | NAOptionDefinitionType::None => Ok((NAOption { name: self.name, value: NAValue::None }, 1)), |
| 77 | NAOptionDefinitionType::Bool => Ok((NAOption { name: self.name, value: NAValue::Bool(true) }, 1)), |
| 78 | NAOptionDefinitionType::Int(_, _) => { |
| 79 | if let Some(str) = value { |
| 80 | let ret = str.parse::<i64>(); |
| 81 | if let Ok(val) = ret { |
| 82 | let opt = NAOption { name: self.name, value: NAValue::Int(val) }; |
| 83 | self.check(&opt)?; |
| 84 | Ok((opt, 2)) |
| 85 | } else { |
| 86 | Err(OptionError::ParseError) |
| 87 | } |
| 88 | } else { |
| 89 | Err(OptionError::ParseError) |
| 90 | } |
| 91 | }, |
| 92 | NAOptionDefinitionType::Float(_, _) => { |
| 93 | if let Some(str) = value { |
| 94 | let ret = str.parse::<f64>(); |
| 95 | if let Ok(val) = ret { |
| 96 | let opt = NAOption { name: self.name, value: NAValue::Float(val) }; |
| 97 | self.check(&opt)?; |
| 98 | Ok((opt, 2)) |
| 99 | } else { |
| 100 | Err(OptionError::ParseError) |
| 101 | } |
| 102 | } else { |
| 103 | Err(OptionError::ParseError) |
| 104 | } |
| 105 | }, |
| 106 | NAOptionDefinitionType::String(_) => { |
| 107 | if let Some(str) = value { |
| 108 | let opt = NAOption { name: self.name, value: NAValue::String(str.to_string()) }; |
| 109 | self.check(&opt)?; |
| 110 | Ok((opt, 2)) |
| 111 | } else { |
| 112 | Err(OptionError::ParseError) |
| 113 | } |
| 114 | }, |
| 115 | _ => unimplemented!(), |
| 116 | } |
| 117 | } |
| 118 | /// Checks whether input option conforms to the definition i.e. whether it has proper format and it lies in range. |
| 119 | pub fn check(&self, option: &NAOption) -> OptionResult<()> { |
| 120 | if option.name != self.name { |
| 121 | return Err(OptionError::WrongName); |
| 122 | } |
| 123 | match option.value { |
| 124 | NAValue::None => Ok(()), |
| 125 | NAValue::Bool(_) => Ok(()), |
| 126 | NAValue::Int(intval) => { |
| 127 | match self.opt_type { |
| 128 | NAOptionDefinitionType::Int(minval, maxval) => { |
| 129 | if let Some(minval) = minval { |
| 130 | if intval < minval { return Err(OptionError::InvalidValue); } |
| 131 | } |
| 132 | if let Some(maxval) = maxval { |
| 133 | if intval > maxval { return Err(OptionError::InvalidValue); } |
| 134 | } |
| 135 | }, |
| 136 | NAOptionDefinitionType::Float(minval, maxval) => { |
| 137 | let fval = intval as f64; |
| 138 | if let Some(minval) = minval { |
| 139 | if fval < minval { return Err(OptionError::InvalidValue); } |
| 140 | } |
| 141 | if let Some(maxval) = maxval { |
| 142 | if fval > maxval { return Err(OptionError::InvalidValue); } |
| 143 | } |
| 144 | }, |
| 145 | _ => return Err(OptionError::InvalidFormat), |
| 146 | }; |
| 147 | Ok(()) |
| 148 | }, |
| 149 | NAValue::Float(fval) => { |
| 150 | match self.opt_type { |
| 151 | NAOptionDefinitionType::Int(minval, maxval) => { |
| 152 | let intval = fval as i64; |
| 153 | if let Some(minval) = minval { |
| 154 | if intval < minval { return Err(OptionError::InvalidValue); } |
| 155 | } |
| 156 | if let Some(maxval) = maxval { |
| 157 | if intval > maxval { return Err(OptionError::InvalidValue); } |
| 158 | } |
| 159 | }, |
| 160 | NAOptionDefinitionType::Float(minval, maxval) => { |
| 161 | if let Some(minval) = minval { |
| 162 | if fval < minval { return Err(OptionError::InvalidValue); } |
| 163 | } |
| 164 | if let Some(maxval) = maxval { |
| 165 | if fval > maxval { return Err(OptionError::InvalidValue); } |
| 166 | } |
| 167 | }, |
| 168 | _ => return Err(OptionError::InvalidFormat), |
| 169 | }; |
| 170 | Ok(()) |
| 171 | }, |
| 172 | NAValue::String(ref cur_str) => { |
| 173 | if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type { |
| 174 | for str in strings.iter() { |
| 175 | if cur_str == str { |
| 176 | return Ok(()); |
| 177 | } |
| 178 | } |
| 179 | Err(OptionError::InvalidValue) |
| 180 | } else { |
| 181 | Ok(()) |
| 182 | } |
| 183 | }, |
| 184 | NAValue::Data(_) => Ok(()), |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | impl fmt::Display for NAOptionDefinition { |
| 190 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 191 | match self.opt_type { |
| 192 | NAOptionDefinitionType::None => write!(f, "{}: {}", self.name, self.description), |
| 193 | NAOptionDefinitionType::Bool => write!(f, "[no]{}: {}", self.name, self.description), |
| 194 | NAOptionDefinitionType::String(ref str_list) => { |
| 195 | if let Some(ref opts) = str_list { |
| 196 | write!(f, "{} {}: {}", self.name, opts.join("|"), self.description) |
| 197 | } else { |
| 198 | write!(f, "{} <string>: {}", self.name, self.description) |
| 199 | } |
| 200 | }, |
| 201 | NAOptionDefinitionType::Int(minval, maxval) => { |
| 202 | let range = match (&minval, &maxval) { |
| 203 | (Some(minval), None) => format!("{}-..", minval), |
| 204 | (None, Some(maxval)) => format!("..-{}", maxval), |
| 205 | (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), |
| 206 | _ => "<integer>".to_string(), |
| 207 | }; |
| 208 | write!(f, "{} {}: {}", self.name, range, self.description) |
| 209 | }, |
| 210 | NAOptionDefinitionType::Float(minval, maxval) => { |
| 211 | let range = match (&minval, &maxval) { |
| 212 | (Some(minval), None) => format!("{}-..", minval), |
| 213 | (None, Some(maxval)) => format!("..-{}", maxval), |
| 214 | (Some(minval), Some(maxval)) => format!("{}-{}", minval, maxval), |
| 215 | _ => "<float>".to_string(), |
| 216 | }; |
| 217 | write!(f, "{} {}: {}", self.name, range, self.description) |
| 218 | }, |
| 219 | NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description), |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | /// Option. |
| 225 | #[derive(Clone,Debug,PartialEq)] |
| 226 | pub struct NAOption { |
| 227 | /// Option name. |
| 228 | pub name: &'static str, |
| 229 | /// Option value. |
| 230 | pub value: NAValue, |
| 231 | } |
| 232 | |
| 233 | /// A list of accepted option values. |
| 234 | #[derive(Debug,Clone,PartialEq)] |
| 235 | pub enum NAValue { |
| 236 | /// Empty value. |
| 237 | None, |
| 238 | /// Boolean value. |
| 239 | Bool(bool), |
| 240 | /// Integer value. |
| 241 | Int(i64), |
| 242 | /// Floating point value. |
| 243 | Float(f64), |
| 244 | /// String value. |
| 245 | String(String), |
| 246 | /// Binary data value. |
| 247 | Data(Arc<Vec<u8>>), |
| 248 | } |
| 249 | |
| 250 | /// Trait for all objects that handle `NAOption`. |
| 251 | pub trait NAOptionHandler { |
| 252 | /// Returns the options recognised by current object. |
| 253 | fn get_supported_options(&self) -> &[NAOptionDefinition]; |
| 254 | /// Passes options for the object to set (or ignore). |
| 255 | fn set_options(&mut self, options: &[NAOption]); |
| 256 | /// Queries the current option value in the object (if present). |
| 257 | fn query_option_value(&self, name: &str) -> Option<NAValue>; |
| 258 | } |
| 259 | |
| 260 | #[cfg(test)] |
| 261 | mod test { |
| 262 | use super::*; |
| 263 | #[test] |
| 264 | fn test_option_validation() { |
| 265 | let option = NAOption {name: "option", value: NAValue::Int(42) }; |
| 266 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) }; |
| 267 | assert!(def.check(&option).is_ok()); |
| 268 | def.opt_type = NAOptionDefinitionType::Int(None, Some(30)); |
| 269 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
| 270 | def.opt_type = NAOptionDefinitionType::Int(Some(40), None); |
| 271 | assert!(def.check(&option).is_ok()); |
| 272 | def.name = "option2"; |
| 273 | assert_eq!(def.check(&option), Err(OptionError::WrongName)); |
| 274 | let option = NAOption {name: "option", value: NAValue::String("test".to_string()) }; |
| 275 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) }; |
| 276 | assert!(def.check(&option).is_ok()); |
| 277 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"])); |
| 278 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
| 279 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"])); |
| 280 | assert!(def.check(&option).is_ok()); |
| 281 | } |
| 282 | #[test] |
| 283 | fn test_option_parsing() { |
| 284 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) }; |
| 285 | assert_eq!(def.parse(&"--option".to_string(), None), Err(OptionError::ParseError)); |
| 286 | assert_eq!(def.parse(&"--nooption".to_string(), None), Err(OptionError::InvalidFormat)); |
| 287 | assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())), |
| 288 | Ok((NAOption{name:"option",value: NAValue::Float(42.0)}, 2))); |
| 289 | def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0)); |
| 290 | assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())), |
| 291 | Err(OptionError::InvalidValue)); |
| 292 | let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool }; |
| 293 | assert_eq!(def.parse(&"option".to_string(), None), |
| 294 | Ok((NAOption{name: "option", value: NAValue::Bool(true) }, 1))); |
| 295 | assert_eq!(def.parse(&"nooption".to_string(), None), |
| 296 | Ok((NAOption{name: "option", value: NAValue::Bool(false) }, 1))); |
| 297 | } |
| 298 | } |