Commit | Line | Data |
---|---|---|
a0ddfb3d KS |
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; | |
8a7d01e2 | 9 | use std::fmt; |
a0ddfb3d | 10 | |
ee0ca773 KS |
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 | ||
a0ddfb3d KS |
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 | ||
8a7d01e2 KS |
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 | ||
a0ddfb3d KS |
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, | |
8a7d01e2 KS |
56 | /// Option type. |
57 | pub opt_type: NAOptionDefinitionType, | |
a0ddfb3d KS |
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 { | |
8a7d01e2 KS |
66 | match self.opt_type { |
67 | NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)), | |
a0ddfb3d KS |
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 | } | |
8a7d01e2 KS |
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(_, _) => { | |
a0ddfb3d KS |
79 | if let Some(str) = value { |
80 | let ret = str.parse::<i64>(); | |
81 | if let Ok(val) = ret { | |
8a7d01e2 | 82 | let opt = NAOption { name: self.name, value: NAValue::Int(val) }; |
a0ddfb3d KS |
83 | self.check(&opt)?; |
84 | Ok((opt, 2)) | |
85 | } else { | |
86 | Err(OptionError::ParseError) | |
87 | } | |
88 | } else { | |
89 | Err(OptionError::ParseError) | |
90 | } | |
91 | }, | |
8a7d01e2 | 92 | NAOptionDefinitionType::Float(_, _) => { |
a0ddfb3d KS |
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 | }, | |
8a7d01e2 | 106 | NAOptionDefinitionType::String(_) => { |
a0ddfb3d KS |
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) => { | |
8a7d01e2 KS |
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 | }; | |
a0ddfb3d KS |
147 | Ok(()) |
148 | }, | |
149 | NAValue::Float(fval) => { | |
8a7d01e2 KS |
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 | }; | |
a0ddfb3d KS |
170 | Ok(()) |
171 | }, | |
172 | NAValue::String(ref cur_str) => { | |
8a7d01e2 | 173 | if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type { |
a0ddfb3d KS |
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 | } | |
8a7d01e2 KS |
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 | } | |
a0ddfb3d | 200 | }, |
8a7d01e2 KS |
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) | |
a0ddfb3d | 209 | }, |
8a7d01e2 KS |
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) | |
a0ddfb3d | 218 | }, |
8a7d01e2 | 219 | NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description), |
a0ddfb3d KS |
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. | |
8a7d01e2 | 241 | Int(i64), |
a0ddfb3d KS |
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) }; | |
8a7d01e2 | 266 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) }; |
a0ddfb3d | 267 | assert!(def.check(&option).is_ok()); |
8a7d01e2 | 268 | def.opt_type = NAOptionDefinitionType::Int(None, Some(30)); |
a0ddfb3d | 269 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
8a7d01e2 | 270 | def.opt_type = NAOptionDefinitionType::Int(Some(40), None); |
a0ddfb3d KS |
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()) }; | |
8a7d01e2 | 275 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) }; |
a0ddfb3d | 276 | assert!(def.check(&option).is_ok()); |
8a7d01e2 | 277 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"])); |
a0ddfb3d | 278 | assert_eq!(def.check(&option), Err(OptionError::InvalidValue)); |
8a7d01e2 | 279 | def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"])); |
a0ddfb3d KS |
280 | assert!(def.check(&option).is_ok()); |
281 | } | |
282 | #[test] | |
283 | fn test_option_parsing() { | |
8a7d01e2 | 284 | let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) }; |
a0ddfb3d KS |
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))); | |
8a7d01e2 | 289 | def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0)); |
a0ddfb3d KS |
290 | assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())), |
291 | Err(OptionError::InvalidValue)); | |
8a7d01e2 | 292 | let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool }; |
a0ddfb3d KS |
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 | } |