args/
lib.rs

1//! # Lambda Args
2//! Lambda Args is a simple argument parser for Rust. It is designed to be
3//! simple to use and primarily for use in lambda command line applications.
4
5use std::collections::HashMap;
6
7pub struct ArgumentParser {
8  name: String,
9  args: HashMap<String, (Argument, bool, usize)>,
10}
11
12#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
13pub enum ArgumentType {
14  Boolean,
15  Integer,
16  Float,
17  Double,
18  String,
19}
20
21#[derive(Debug, Clone, PartialEq, PartialOrd)]
22pub enum ArgumentValue {
23  None,
24  Boolean(bool),
25  Integer(i64),
26  Float(f32),
27  Double(f64),
28  String(String),
29}
30
31impl Into<String> for ArgumentValue {
32  fn into(self) -> String {
33    return match self {
34      ArgumentValue::String(val) => val,
35      _ => panic!("Cannot convert {:?} into a String.", self),
36    };
37  }
38}
39
40impl Into<i64> for ArgumentValue {
41  fn into(self) -> i64 {
42    return match self {
43      ArgumentValue::Integer(val) => val,
44      ArgumentValue::Float(val) => val as i64,
45      ArgumentValue::Double(val) => val as i64,
46      _ => panic!("Cannot convert {:?} into an i64", self),
47    };
48  }
49}
50
51impl Into<f32> for ArgumentValue {
52  fn into(self) -> f32 {
53    return match self {
54      ArgumentValue::Float(val) => val,
55      _ => panic!("Cannot convert {:?} into a f32", self),
56    };
57  }
58}
59
60impl Into<f64> for ArgumentValue {
61  fn into(self) -> f64 {
62    return match self {
63      ArgumentValue::Double(val) => val,
64      ArgumentValue::Float(val) => val as f64,
65      ArgumentValue::Integer(val) => val as f64,
66      _ => panic!("Cannot convert {:?} into a f64", self),
67    };
68  }
69}
70
71#[derive(Debug)]
72pub struct Argument {
73  name: String,
74  description: String,
75  required: bool,
76  arg_type: ArgumentType,
77  default_value: ArgumentValue,
78}
79
80impl Argument {
81  /// Creates a new argument where the name represents
82  pub fn new(name: &str) -> Self {
83    return Argument {
84      name: name.to_string(),
85      description: String::new(),
86      required: false,
87      arg_type: ArgumentType::String,
88      default_value: ArgumentValue::None,
89    };
90  }
91
92  /// Sets the Argument explicitly as required or not.
93  pub fn is_required(mut self, required: bool) -> Self {
94    self.required = required;
95    return self;
96  }
97
98  /// Sets the type for the ArgumentParser to parse the arguments value into.
99  pub fn with_type(mut self, arg_type: ArgumentType) -> Self {
100    self.arg_type = arg_type;
101    return self;
102  }
103
104  /// Sets the description (Help string) of the argument for
105  pub fn with_description(mut self, description: &str) -> Self {
106    self.description = description.to_string();
107    return self;
108  }
109
110  /// Sets the default value if the argument type matches the value or panics
111  pub fn with_default_value(mut self, value: ArgumentValue) -> Self {
112    match (self.arg_type, value.clone()) {
113      (ArgumentType::String, ArgumentValue::String(_))
114      | (ArgumentType::Integer, ArgumentValue::Integer(_))
115      | (ArgumentType::Float, ArgumentValue::Float(_))
116      | (ArgumentType::Double, ArgumentValue::Double(_)) => {
117        self.default_value = value;
118      }
119      (_, _) => panic!(
120        "Argument type: {:?} is incompatible with default value: {:?}",
121        self.arg_type, value
122      ),
123    }
124
125    return self;
126  }
127
128  pub fn arg_type(&self) -> ArgumentType {
129    return self.arg_type.clone();
130  }
131
132  pub fn name(&self) -> &str {
133    return self.name.as_ref();
134  }
135
136  pub fn default_value(&self) -> ArgumentValue {
137    return self.default_value.clone();
138  }
139
140  pub fn description(&self) -> &str {
141    return self.description.as_ref();
142  }
143}
144
145#[derive(Debug, Clone)]
146pub struct ParsedArgument {
147  name: String,
148  value: ArgumentValue,
149}
150
151impl ParsedArgument {
152  fn new(name: &str, value: ArgumentValue) -> Self {
153    return ParsedArgument {
154      name: name.to_string(),
155      value,
156    };
157  }
158
159  pub fn name(&self) -> String {
160    return self.name.clone();
161  }
162
163  pub fn value(&self) -> ArgumentValue {
164    return self.value.clone();
165  }
166}
167
168impl ArgumentParser {
169  /// Constructor for the argument parser.
170  pub fn new(name: &str) -> Self {
171    return ArgumentParser {
172      name: name.to_string(),
173      args: HashMap::new(),
174    };
175  }
176
177  /// The name of the parser.
178  pub fn name(&self) -> &str {
179    return self.name.as_ref();
180  }
181
182  /// The number of arguments currently registered with the parser.
183  pub fn argument_count(&self) -> usize {
184    return self.args.len();
185  }
186
187  pub fn with_author(mut self, author: &str) {
188    todo!("Implement adding authors to a command line parser")
189  }
190
191  // TODO(vmarcella): Add description to the name
192  pub fn with_description(mut self, description: &str) {
193    todo!("Implement adding a description to the command line parser.")
194  }
195
196  pub fn with_argument(mut self, argument: Argument) -> Self {
197    self.args.insert(
198      argument.name().to_string(),
199      (argument, false, self.args.len()),
200    );
201    return self;
202  }
203
204  /// Compiles a slice of Strings into an array of Parsed Arguments. This will
205  /// move the parser into this function and return back the parsed arguments if
206  /// everything succeeds. This function assumes that the first item within args
207  /// is the name of the executable being run. (Which is the standard for
208  /// arguments passed in from std::env::args()). The ordering of the arguments
209  /// returned is always the same as order they're registered in with the
210  /// parser.
211  pub fn compile(mut self, args: &[String]) -> Vec<ParsedArgument> {
212    let mut collecting_values = false;
213    let mut last_argument: Option<&mut (Argument, bool, usize)> = None;
214
215    let mut parsed_arguments = vec![];
216    parsed_arguments.resize(
217      self.args.len(),
218      ParsedArgument::new("", ArgumentValue::None),
219    );
220
221    for arg in args.into_iter().skip(1) {
222      if collecting_values {
223        let (arg_ref, found, index) = last_argument.as_mut().unwrap();
224
225        let parsed_value = match arg_ref.arg_type() {
226          ArgumentType::String => ArgumentValue::String(arg.clone()),
227          ArgumentType::Float => {
228            ArgumentValue::Float(arg.parse().unwrap_or_else(|err| {
229              panic!(
230                "Could not convert {:?} to a float because of: {}",
231                arg, err
232              )
233            }))
234          }
235          ArgumentType::Double => {
236            ArgumentValue::Double(arg.parse().unwrap_or_else(|err| {
237              panic!(
238                "Could not convert {:?} to a double because of: {}",
239                arg, err
240              )
241            }))
242          }
243          ArgumentType::Integer => {
244            ArgumentValue::Integer(arg.parse().unwrap_or_else(|err| {
245              panic!(
246                "Could not convert {:?} to an integer because of: {}",
247                arg, err
248              )
249            }))
250          }
251          ArgumentType::Boolean => {
252            ArgumentValue::Boolean(arg.parse().unwrap_or_else(|err| {
253              panic!(
254                "Could not convert {:?} to a boolean because of: {}",
255                arg, err
256              )
257            }))
258          }
259        };
260
261        parsed_arguments[*index] =
262          ParsedArgument::new(arg_ref.name.as_str(), parsed_value);
263
264        collecting_values = false;
265        *found = true;
266        continue;
267      }
268
269      // Panic if the argument cannot be found inside of the registered
270      // arguments.
271      let found_argument = self.args.get_mut(arg).unwrap_or_else(|| {
272        panic!("Argument: {} is not a valid argument", &arg)
273      });
274
275      // If the argument has already been found, throw an error.
276      if found_argument.1 == true {
277        panic!("{} was set more than once.", found_argument.0.name.clone());
278      }
279
280      collecting_values = true;
281      last_argument = Some(found_argument);
282    }
283
284    // Go through all of the registered arguments and check for forgotten flags/
285    // apply default values.
286    for (arg, found, index) in self.args.values() {
287      match (arg.required, found, arg.default_value.clone()) {
288        // Argument was required as user input, but not found.
289        (true, false, _) => panic!(
290          "--{} is a required argument, but was not found.",
291          arg.name.clone()
292        ),
293        // Argument wasn't required & wasn't found, but has a default value
294        (false, false, value) => {
295          parsed_arguments[*index] =
296            ParsedArgument::new(arg.name.as_str(), value);
297        }
298        // Any other situation doesn't really matter and will be a noop
299        (_, _, _) => {}
300      }
301    }
302    return parsed_arguments;
303  }
304}