mod var;
use var::Var;
use std::any::Any;
use std::fmt::Write;
#[derive(Debug)]
struct Flag<'a> {
name: &'a str,
help: &'a str,
var: Var<'a>,
}
impl<'a> Flag<'a> {
fn unquote_usage(&self) -> (String, String) {
let mut help = self.help.to_owned();
let mut name = String::from(self.var.unquotename());
let seg: Vec<&str> = help.split_terminator('`').collect();
if seg.len() >= 3 {
name = seg[1].to_owned();
help = help.replacen('`', "", 2);
}
(name, help)
}
}
#[derive(Debug)]
pub struct FlagSet<'a> {
name: String,
set: Vec<Flag<'a>>,
}
impl<'a> FlagSet<'a> {
pub fn new(name: &str) -> Self {
Self {
name: String::from(name),
set: Vec::new(),
}
}
pub fn add(mut self, name: &'a str, var: &'a mut dyn Any, help: &'a str) -> Self {
self.set.push(Flag {
name,
help,
var: Var::new(var),
});
self
}
pub fn parse(&mut self, args: &[String]) -> Result<(), String> {
let mut args_iter = args.iter();
while let Some(item) = args_iter.next() {
if let Some(f) = self.set.iter_mut().find(|f| {
f.name
.split_terminator(',')
.filter(|name| !name.is_empty())
.find(|name| name.trim() == item.as_str())
.is_some()
}) {
if let Var::Bool(var) = &mut f.var {
**var = true;
} else {
if let Some(value) = args_iter.next() {
if let Err(err) = f.var.parse(value) {
return Err(format!("parse flag {} failed: {}", f.name, err));
}
} else {
return Err(format!("flag needs an argument: {}", f.name));
}
}
} else {
return Err(format!("flag {} provided but not defined", item));
}
}
Ok(())
}
pub fn name(&self) -> &str {
&self.name
}
pub fn usage(&self) -> String {
let mut usage: String = format!("Usage of {}:\n", self.name);
usage.push_str(self.defaults().as_str());
usage
}
pub fn defaults(&self) -> String {
let mut formated_help: String = String::new();
for f in self.set.iter() {
formated_help
.write_fmt(format_args!(" {}", f.name))
.unwrap();
let (name, help) = f.unquote_usage();
if !name.is_empty() {
formated_help.push_str(" ");
formated_help.push_str(name.as_str());
}
if formated_help.len() <= 4 {
formated_help.push_str("\t");
} else {
formated_help.push_str("\n \t");
}
formated_help.push_str(help.replace("\n", "\n \t").as_str());
formated_help.push_str("\n");
}
formated_help
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flagset_test() {
let args = vec![
"app_name".to_string(),
"-b".to_string(),
"--isize".to_string(),
"-10".to_string(),
"--f32".to_string(),
"1.888".to_string(),
"-u".to_string(),
"40".to_string(),
"-s".to_string(),
"test_value".to_string(),
];
let mut bool_val = false;
let mut isize_val = 20;
let mut usize_val = 15;
let mut f32_val = 1.4;
let mut string_val = String::from("default");
let mut string_val2 = String::from("default");
let mut flag_set = FlagSet::new(&args[0])
.add("-b,--bool", &mut bool_val, "value bool")
.add("-i, --isize", &mut isize_val, "value isize")
.add("-u, --usize", &mut usize_val, "`value` usize")
.add("-f, --f32", &mut f32_val, "`value` f32")
.add("-s, --string", &mut string_val, "test string")
.add("--s2", &mut string_val2, "test string default");
flag_set.parse(&args[1..]).unwrap();
assert_eq!(bool_val, true);
assert_eq!(isize_val, -10);
assert_eq!(usize_val, 40);
assert_eq!(f32_val, 1.888);
assert_eq!(string_val, "test_value");
assert_eq!(string_val2, "default");
}
}