use std::collections::HashMap;
pub struct ArgumentParser {
name: String,
args: HashMap<String, (Argument, bool, usize)>,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum ArgumentType {
Boolean,
Integer,
Float,
Double,
String,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ArgumentValue {
None,
Boolean(bool),
Integer(i64),
Float(f32),
Double(f64),
String(String),
}
impl Into<String> for ArgumentValue {
fn into(self) -> String {
return match self {
ArgumentValue::String(val) => val,
_ => panic!("Cannot convert {:?} into a String.", self),
};
}
}
impl Into<i64> for ArgumentValue {
fn into(self) -> i64 {
return match self {
ArgumentValue::Integer(val) => val,
ArgumentValue::Float(val) => val as i64,
ArgumentValue::Double(val) => val as i64,
_ => panic!("Cannot convert {:?} into an i64", self),
};
}
}
impl Into<f32> for ArgumentValue {
fn into(self) -> f32 {
return match self {
ArgumentValue::Float(val) => val,
_ => panic!("Cannot convert {:?} into a f32", self),
};
}
}
impl Into<f64> for ArgumentValue {
fn into(self) -> f64 {
return match self {
ArgumentValue::Double(val) => val,
ArgumentValue::Float(val) => val as f64,
ArgumentValue::Integer(val) => val as f64,
_ => panic!("Cannot convert {:?} into a f64", self),
};
}
}
#[derive(Debug)]
pub struct Argument {
name: String,
description: String,
required: bool,
arg_type: ArgumentType,
default_value: ArgumentValue,
}
impl Argument {
pub fn new(name: &str) -> Self {
return Argument {
name: name.to_string(),
description: String::new(),
required: false,
arg_type: ArgumentType::String,
default_value: ArgumentValue::None,
};
}
pub fn is_required(mut self, required: bool) -> Self {
self.required = required;
return self;
}
pub fn with_type(mut self, arg_type: ArgumentType) -> Self {
self.arg_type = arg_type;
return self;
}
pub fn with_description(mut self, description: &str) -> Self {
self.description = description.to_string();
return self;
}
pub fn with_default_value(mut self, value: ArgumentValue) -> Self {
match (self.arg_type, value.clone()) {
(ArgumentType::String, ArgumentValue::String(_))
| (ArgumentType::Integer, ArgumentValue::Integer(_))
| (ArgumentType::Float, ArgumentValue::Float(_))
| (ArgumentType::Double, ArgumentValue::Double(_)) => {
self.default_value = value;
}
(_, _) => panic!(
"Argument type: {:?} is incompatible with default value: {:?}",
self.arg_type, value
),
}
return self;
}
pub fn arg_type(&self) -> ArgumentType {
return self.arg_type.clone();
}
pub fn name(&self) -> &str {
return self.name.as_ref();
}
pub fn default_value(&self) -> ArgumentValue {
return self.default_value.clone();
}
pub fn description(&self) -> &str {
return self.description.as_ref();
}
}
#[derive(Debug, Clone)]
pub struct ParsedArgument {
name: String,
value: ArgumentValue,
}
impl ParsedArgument {
fn new(name: &str, value: ArgumentValue) -> Self {
return ParsedArgument {
name: name.to_string(),
value,
};
}
pub fn name(&self) -> String {
return self.name.clone();
}
pub fn value(&self) -> ArgumentValue {
return self.value.clone();
}
}
impl ArgumentParser {
pub fn new(name: &str) -> Self {
return ArgumentParser {
name: name.to_string(),
args: HashMap::new(),
};
}
pub fn name(&self) -> &str {
return self.name.as_ref();
}
pub fn argument_count(&self) -> usize {
return self.args.len();
}
pub fn with_author(mut self, author: &str) {
todo!("Implement adding authors to a command line parser")
}
pub fn with_description(mut self, description: &str) {
todo!("Implement adding a description to the command line parser.")
}
pub fn with_argument(mut self, argument: Argument) -> Self {
self.args.insert(
argument.name().to_string(),
(argument, false, self.args.len()),
);
return self;
}
pub fn compile(mut self, args: &[String]) -> Vec<ParsedArgument> {
let mut collecting_values = false;
let mut last_argument: Option<&mut (Argument, bool, usize)> = None;
let mut parsed_arguments = vec![];
parsed_arguments.resize(
self.args.len(),
ParsedArgument::new("", ArgumentValue::None),
);
for arg in args.into_iter().skip(1) {
if collecting_values {
let (arg_ref, found, index) = last_argument.as_mut().unwrap();
let parsed_value = match arg_ref.arg_type() {
ArgumentType::String => ArgumentValue::String(arg.clone()),
ArgumentType::Float => {
ArgumentValue::Float(arg.parse().unwrap_or_else(|err| {
panic!(
"Could not convert {:?} to a float because of: {}",
arg, err
)
}))
}
ArgumentType::Double => {
ArgumentValue::Double(arg.parse().unwrap_or_else(|err| {
panic!(
"Could not convert {:?} to a double because of: {}",
arg, err
)
}))
}
ArgumentType::Integer => {
ArgumentValue::Integer(arg.parse().unwrap_or_else(|err| {
panic!(
"Could not convert {:?} to an integer because of: {}",
arg, err
)
}))
}
ArgumentType::Boolean => {
ArgumentValue::Boolean(arg.parse().unwrap_or_else(|err| {
panic!(
"Could not convert {:?} to a boolean because of: {}",
arg, err
)
}))
}
};
parsed_arguments[*index] =
ParsedArgument::new(arg_ref.name.as_str(), parsed_value);
collecting_values = false;
*found = true;
continue;
}
let found_argument = self.args.get_mut(arg).unwrap_or_else(|| {
panic!("Argument: {} is not a valid argument", &arg)
});
if found_argument.1 == true {
panic!("{} was set more than once.", found_argument.0.name.clone());
}
collecting_values = true;
last_argument = Some(found_argument);
}
for (arg, found, index) in self.args.values() {
match (arg.required, found, arg.default_value.clone()) {
(true, false, _) => panic!(
"--{} is a required argument, but was not found.",
arg.name.clone()
),
(false, false, value) => {
parsed_arguments[*index] =
ParsedArgument::new(arg.name.as_str(), value);
}
(_, _, _) => {}
}
}
return parsed_arguments;
}
}