factor out keyframe interval option
[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 10
ee0ca773
KS
11/// Common name for keyframe interval option.
12pub const KEYFRAME_OPTION: &'static str = "key_int";
13/// Common description for keyframe interval option.
14pub 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)]
18pub 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.
30pub type OptionResult<T> = Result<T, OptionError>;
31
8a7d01e2
KS
32/// Option definition type.
33#[derive(Debug)]
34pub 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)]
51pub 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
60impl 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
189impl 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)]
226pub 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)]
235pub 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`.
251pub 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)]
261mod 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}