use std::{env, process};
use std::collections::HashMap;
#[derive(Clone, PartialEq)]
pub enum WordType {
Boolean(bool),
String_(String),
}
impl WordType {
pub fn boolean(b: bool) -> Self {
Self::Boolean(b)
}
pub fn string(s: &str) -> Self {
Self::String_(String::from(s))
}
pub fn as_bool(&self) -> Option<bool> {
if let Self::Boolean(b) = self {
return Some(*b);
}
None
}
pub fn as_string(&self) -> Option<String> {
if let Self::String_(s) = self {
return Some(s.clone());
}
None
}
}
#[derive(Clone, PartialEq)]
pub enum ArgType {
Unknown,
Flag(bool),
Option_(String),
Word(WordType),
}
impl ArgType {
pub fn option(opt: &str) -> Self {
Self::Option_(String::from(opt))
}
pub fn flag(f: bool) -> Self {
Self::Flag(f)
}
pub fn word(wt: WordType) -> Self {
Self::Word(wt)
}
}
#[derive(Clone)]
pub struct Arg {
name: String,
short: char,
help: String,
typ: ArgType,
required: bool,
set: bool,
}
impl Arg {
pub fn new(namee: &str) -> Self {
let name = String::from(namee);
Self {
name,
short: namee.chars().nth(0).unwrap(),
help: String::new(),
typ: ArgType::Unknown,
required: false,
set: false,
}
}
pub fn flag(&mut self, val: bool) -> &mut Self {
self.typ = ArgType::flag(val);
self
}
pub fn option(&mut self, val: &str) -> &mut Self {
self.typ = ArgType::option(val);
self
}
pub fn word(&mut self, wt: WordType) -> &mut Self {
self.typ = ArgType::word(wt);
self
}
pub fn help(&mut self, help: &str) -> &mut Self {
self.help = String::from(help);
self
}
pub fn short(&mut self, short: char) -> &mut Self {
self.short = short;
self
}
pub fn required(&mut self, required: bool) -> &mut Self {
self.required = required;
self
}
fn set(&mut self) {
self.set = true;
}
}
pub struct ArgParser {
name: String,
author: String,
version: String,
copyright: String,
info: String,
usage: String,
args: HashMap<String, Arg>,
require_args: bool,
}
impl ArgParser {
pub fn parse(&mut self) -> &mut Self {
let args: Vec<_> = env::args().collect();
self.parse_vec(args);
self
}
pub fn parse_vec(&mut self, args: Vec<String>) -> &mut Self {
if args.len() == 1 && self.require_args {
self.print_help();
process::exit(1);
}
for (idx, arg) in args.iter().enumerate() {
if let Some(arg) = self.args.get_mut(arg) {
if let ArgType::Word(w) = arg.clone().typ {
match w {
WordType::Boolean(boolean) => {arg.word(WordType::Boolean(!boolean));},
WordType::String_(_) => {
let next = args.get(idx + 1);
if let Some(next) = next {
if !next.starts_with('-') {
arg.word(WordType::String_(next.clone()));
arg.set();
}
}
},
}
}
continue;
}
if let Some(arg) = arg.strip_prefix("--") {
if arg == "help" {self.help_exit()}
else if arg == "version" {println!("{} {}", self.name, self.version);process::exit(0);}
if let Some(arg) = self.args.get_mut(arg) {
match arg.typ {
ArgType::Flag(boolean) => {arg.flag(!boolean);arg.set();},
ArgType::Option_(_) => {
if let Some(next) = args.get(idx + 1) {
if !next.starts_with('-') {
arg.option(&next);
arg.set();
}
}
},
_ => {},
}
}
} else if let Some(arg) = arg.strip_prefix('-') {
arg.chars().into_iter().for_each(|ch| {
if ch == 'h' {self.help_exit()}
else if ch == 'v' {println!("{} {}", self.name, self.version);process::exit(0);}
for arg in self.args.values_mut() {
if arg.short == ch {
match arg.typ {
ArgType::Flag(boolean) => {arg.flag(!boolean);arg.set();},
ArgType::Option_(_) => {
if let Some(next) = args.get(idx + 1) {
if !next.starts_with('-') {
arg.option(&next);
arg.set();
}
}
},
_ => {},
}
}
}
});
}
}
self.args.iter().for_each(|(_, arg)| {
if arg.required && !arg.set {
println!("Didn't find \"{}\"\n", arg.name);
self.help_exit();
}
});
self
}
pub fn get_option(&self, name: &str) -> Option<String> {
if let Some(arg) = self.args.get(name) {
if let ArgType::Option_(string) = arg.clone().typ {
return Some(string);
}
}
None
}
pub fn get_flag(&self, name: &str) -> Option<bool> {
if let Some(arg) = self.args.get(name) {
if let ArgType::Flag(boolean) = arg.typ {
return Some(boolean);
}
}
None
}
pub fn get_word(&self, name: &str) -> Option<WordType> {
if let Some(arg) = self.args.get(name) {
if let ArgType::Word(wt) = arg.clone().typ {
return Some(wt);
}
}
None
}
pub fn new(name: &str) -> Self {
let mut s = Self {
name: String::from(name),
author: String::new(),
version: String::new(),
copyright: String::new(),
info: String::new(),
usage: format!("{} [flags] [options]", name),
args: HashMap::new(),
require_args: false,
};
s.args(vec!(
Arg::new("help")
.short('h')
.help("Prints the help dialog")
.flag(false),
Arg::new("version")
.short('v')
.help("Prints the version")
.flag(false),
));
s
}
pub fn print_help(&self) {
println!("{} {}\n{}\n{}\n{}", self.name, self.version, self.author, self.info, self.copyright);
println!("\nUsage:\n\t{}", self.usage);
let flags: Vec<&Arg> = self.args.iter().filter(|(_, arg)| if let ArgType::Flag(_) = arg.typ{true} else {false}).map(|(_, arg)| arg).collect();
let options: Vec<&Arg> = self.args.iter().filter(|(_, arg)| if let ArgType::Option_(_) = arg.typ{true} else {false}).map(|(_, arg)| arg).collect();
let words: Vec<&Arg> = self.args.iter().filter(|(_, arg)| if let ArgType::Word(_) = arg.typ{true} else {false}).map(|(_, arg)| arg).collect();
if !flags.is_empty() {
println!("\nFlags:");
flags.iter().for_each(|flag| {
println!("\t-{}, --{}\t{}", flag.short, flag.name, flag.help);
});
}
if !options.is_empty() {
println!("\nOptions:");
options.iter().for_each(|opt| {
println!("\t-{}, --{}\t{}", opt.short, opt.name, opt.help);
});
}
if !words.is_empty() {
println!("\nWords:");
words.iter().for_each(|opt| {
println!("\t{}\t{}", opt.name, opt.help);
});
}
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = String::from(name);
self
}
pub fn author(&mut self, author: &str) -> &mut Self {
self.author = String::from(author);
self
}
pub fn version(&mut self, version: &str) -> &mut Self {
self.version = String::from(version);
self
}
pub fn copyright(&mut self, copyright: &str) -> &mut Self {
self.copyright = String::from(copyright);
self
}
pub fn info(&mut self, info: &str) -> &mut Self {
self.info = String::from(info);
self
}
pub fn usage(&mut self, usage: &str) -> &mut Self {
self.usage = String::from(usage);
self
}
pub fn args(&mut self, args: Vec<&mut Arg>) -> &mut Self {
for arg in args {
match arg.typ {
ArgType::Unknown => panic!("No Args can have type Unknown!"),
_ => {self.args.insert(arg.name.clone(), arg.clone());}
}
}
self
}
pub fn require_args(&mut self, require: bool) -> &mut Self {
self.require_args = require;
self
}
fn help_exit(&self) {
self.print_help();
process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_dash() {
let args = vec!("--testflag".to_string(),
"-o".to_string(), "monke".to_string(),
"-fa".to_string(), "option".to_string(),
);
let mut parser = ArgParser::new("program_lol");
parser.author("BubbyRoosh")
.version("0.1.0")
.copyright("Copyright (C) 2021 BubbyRoosh")
.info("Example for simple arg parsing crate OwO")
.args(
vec!(
Arg::new("testflag")
.short('t')
.help("This is a test flag.")
.flag(false),
Arg::new("testoption")
.short('o')
.help("This is a test option.")
.option("option"),
Arg::new("combinedtestflag")
.short('f')
.help("This is another test flag.")
.flag(false),
Arg::new("combinedtestoption")
.short('a')
.help("This is another test option.")
.option("monke"),
)
).parse_vec(args);
assert!(parser.get_flag("testflag").unwrap());
assert_eq!(parser.get_option("testoption").unwrap(), "monke");
assert!(parser.get_flag("combinedtestflag").unwrap());
assert_eq!(parser.get_option("combinedtestoption").unwrap(), "option");
}
#[test]
fn parse_word() {
let args = vec!(
"testword".to_string(),
"anothertestword".to_string(),
"wordargument".to_string(),
);
let mut parser = ArgParser::new("program_lol");
parser.author("BubbyRoosh")
.version("0.1.0")
.copyright("Copyright (C) 2021 BubbyRoosh")
.info("Example for simple arg parsing crate OwO")
.args(
vec!(
Arg::new("testword")
.help("This is a test word argument.")
.word(WordType::Boolean(false)),
Arg::new("anothertestword")
.help("This is a *another* test word argument.")
.word(WordType::string("")),
)
).parse_vec(args);
assert!(parser.get_word("testword").unwrap().as_bool().unwrap());
assert_eq!(parser.get_word("anothertestword").unwrap().as_string().unwrap(), "wordargument");
}
}