1use crate::error::{OjphError, Result};
8
9pub struct CliInterpreter {
14 args: Vec<String>,
15 available: Vec<bool>,
16}
17
18impl CliInterpreter {
19 pub fn init(args: Vec<String>) -> Self {
21 let len = args.len();
22 Self {
23 args,
24 available: vec![true; len],
25 }
26 }
27
28 pub fn find_argument(&self, name: &str) -> Option<usize> {
30 self.args
31 .iter()
32 .enumerate()
33 .find(|(i, a)| *a == name && self.available[*i])
34 .map(|(i, _)| i)
35 }
36
37 pub fn get_next_value(&mut self, index: usize) -> Result<&str> {
40 let next = index + 1;
41 if next >= self.args.len() || !self.available[next] {
42 return Err(OjphError::InvalidParam(format!(
43 "expected value after argument '{}'",
44 self.args.get(index).unwrap_or(&String::new()),
45 )));
46 }
47 self.available[index] = false;
48 self.available[next] = false;
49 Ok(&self.args[next])
50 }
51
52 pub fn release_argument(&mut self, index: usize) {
54 if index < self.available.len() {
55 self.available[index] = false;
56 }
57 }
58
59 pub fn is_exhausted(&self) -> bool {
61 self.available.iter().all(|&a| !a)
62 }
63
64 pub fn first_unconsumed(&self) -> Option<&str> {
66 self.args
67 .iter()
68 .enumerate()
69 .find(|(i, _)| self.available[*i])
70 .map(|(_, a)| a.as_str())
71 }
72
73 pub fn reinterpret_i32(&mut self, index: usize) -> Result<i32> {
79 let s = self.get_next_value(index)?;
80 s.parse::<i32>()
81 .map_err(|_| OjphError::InvalidParam(format!("cannot parse '{}' as i32", s)))
82 }
83
84 pub fn reinterpret_u32(&mut self, index: usize) -> Result<u32> {
86 let s = self.get_next_value(index)?;
87 s.parse::<u32>()
88 .map_err(|_| OjphError::InvalidParam(format!("cannot parse '{}' as u32", s)))
89 }
90
91 pub fn reinterpret_f32(&mut self, index: usize) -> Result<f32> {
93 let s = self.get_next_value(index)?;
94 s.parse::<f32>()
95 .map_err(|_| OjphError::InvalidParam(format!("cannot parse '{}' as f32", s)))
96 }
97
98 pub fn reinterpret_bool(&mut self, index: usize) -> Result<bool> {
101 let s = self.get_next_value(index)?;
102 match s.to_ascii_lowercase().as_str() {
103 "true" | "1" | "yes" => Ok(true),
104 "false" | "0" | "no" => Ok(false),
105 _ => Err(OjphError::InvalidParam(format!(
106 "cannot parse '{}' as bool",
107 s
108 ))),
109 }
110 }
111
112 pub fn reinterpret_string(&mut self, index: usize) -> Result<String> {
114 self.get_next_value(index).map(|s| s.to_owned())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn find_and_consume() {
124 let args = vec![
125 "-i".to_string(),
126 "input.j2c".to_string(),
127 "-o".to_string(),
128 "output.ppm".to_string(),
129 ];
130 let mut cli = CliInterpreter::init(args);
131
132 let idx = cli.find_argument("-i").unwrap();
133 let val = cli.reinterpret_string(idx).unwrap();
134 assert_eq!(val, "input.j2c");
135
136 let idx = cli.find_argument("-o").unwrap();
137 let val = cli.reinterpret_string(idx).unwrap();
138 assert_eq!(val, "output.ppm");
139
140 assert!(cli.is_exhausted());
141 }
142}