core: split options into separate module
[nihav.git] / nihav-core / src / options.rs
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 }