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