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