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