core: redesign options module
[nihav.git] / nihav-core / src / options.rs
CommitLineData
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
8use std::sync::Arc;
8a7d01e2 9use std::fmt;
a0ddfb3d
KS
10
11/// A list specifying option parsing and validating errors.
12#[derive(Clone,Copy,Debug,PartialEq)]
13pub 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.
25pub type OptionResult<T> = Result<T, OptionError>;
26
8a7d01e2
KS
27/// Option definition type.
28#[derive(Debug)]
29pub 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
a0ddfb3d
KS
44/// Option definition.
45#[derive(Debug)]
46pub struct NAOptionDefinition {
47 /// Option name.
48 pub name: &'static str,
49 /// Option meaning.
50 pub description: &'static str,
8a7d01e2
KS
51 /// Option type.
52 pub opt_type: NAOptionDefinitionType,
a0ddfb3d
KS
53}
54
55impl 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 {
8a7d01e2
KS
61 match self.opt_type {
62 NAOptionDefinitionType::Bool => return Ok((NAOption { name: self.name, value: NAValue::Bool(false) }, 1)),
a0ddfb3d
KS
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 }
8a7d01e2
KS
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(_, _) => {
a0ddfb3d
KS
74 if let Some(str) = value {
75 let ret = str.parse::<i64>();
76 if let Ok(val) = ret {
8a7d01e2 77 let opt = NAOption { name: self.name, value: NAValue::Int(val) };
a0ddfb3d
KS
78 self.check(&opt)?;
79 Ok((opt, 2))
80 } else {
81 Err(OptionError::ParseError)
82 }
83 } else {
84 Err(OptionError::ParseError)
85 }
86 },
8a7d01e2 87 NAOptionDefinitionType::Float(_, _) => {
a0ddfb3d
KS
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 },
8a7d01e2 101 NAOptionDefinitionType::String(_) => {
a0ddfb3d
KS
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) => {
8a7d01e2
KS
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 };
a0ddfb3d
KS
142 Ok(())
143 },
144 NAValue::Float(fval) => {
8a7d01e2
KS
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 };
a0ddfb3d
KS
165 Ok(())
166 },
167 NAValue::String(ref cur_str) => {
8a7d01e2 168 if let NAOptionDefinitionType::String(Some(ref strings)) = self.opt_type {
a0ddfb3d
KS
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 }
8a7d01e2
KS
182}
183
184impl 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 }
a0ddfb3d 195 },
8a7d01e2
KS
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)
a0ddfb3d 204 },
8a7d01e2
KS
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)
a0ddfb3d 213 },
8a7d01e2 214 NAOptionDefinitionType::Data => write!(f, "{} <binary data>: {}", self.name, self.description),
a0ddfb3d
KS
215 }
216 }
217}
218
219/// Option.
220#[derive(Clone,Debug,PartialEq)]
221pub 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)]
230pub enum NAValue {
231 /// Empty value.
232 None,
233 /// Boolean value.
234 Bool(bool),
235 /// Integer value.
8a7d01e2 236 Int(i64),
a0ddfb3d
KS
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`.
246pub 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)]
256mod test {
257 use super::*;
258 #[test]
259 fn test_option_validation() {
260 let option = NAOption {name: "option", value: NAValue::Int(42) };
8a7d01e2 261 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Int(None, None) };
a0ddfb3d 262 assert!(def.check(&option).is_ok());
8a7d01e2 263 def.opt_type = NAOptionDefinitionType::Int(None, Some(30));
a0ddfb3d 264 assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
8a7d01e2 265 def.opt_type = NAOptionDefinitionType::Int(Some(40), None);
a0ddfb3d
KS
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()) };
8a7d01e2 270 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::String(None) };
a0ddfb3d 271 assert!(def.check(&option).is_ok());
8a7d01e2 272 def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test string"]));
a0ddfb3d 273 assert_eq!(def.check(&option), Err(OptionError::InvalidValue));
8a7d01e2 274 def.opt_type = NAOptionDefinitionType::String(Some(&["a string", "test"]));
a0ddfb3d
KS
275 assert!(def.check(&option).is_ok());
276 }
277 #[test]
278 fn test_option_parsing() {
8a7d01e2 279 let mut def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Float(None, None) };
a0ddfb3d
KS
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)));
8a7d01e2 284 def.opt_type = NAOptionDefinitionType::Float(None, Some(40.0));
a0ddfb3d
KS
285 assert_eq!(def.parse(&"--option".to_string(), Some(&"42".to_string())),
286 Err(OptionError::InvalidValue));
8a7d01e2 287 let def = NAOptionDefinition { name: "option", description: "", opt_type: NAOptionDefinitionType::Bool };
a0ddfb3d
KS
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}