use std::env;
use core::fmt::Write;
pub mod termcolors;
use crate::termcolors::*;
pub trait FlagParser {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String>;
}
pub struct Flag<'a> {
pub option_names: &'static [&'static str],
pub argument_descriptions: &'static [&'static str],
pub field: &'a mut dyn FlagParser,
pub description: &'static str,
}
pub struct Arguments<'a> {
option_name: &'static str,
min: usize,
max: usize,
field: &'a mut dyn FlagParser,
}
#[derive(Default)]
pub struct Command<'a> {
pub command_name: &'static str,
pub short_description: &'static str, pub flags_after_position_arguments: bool,
pub flags: &'a mut [Flag<'a>],
pub positional: Option<Arguments<'a>>,
}
pub trait ArgumentParser {
#[allow(clippy::ptr_arg)]
fn parse_arguments(&mut self, input_args: &mut Vec<&str>, command_prefix: &str, action: ParseArgumentAction) -> Result<bool,String>;
#[allow(clippy::ptr_arg)]
fn insert_name(&self, names: &mut Vec<&'static str>);
}
#[derive(Debug,PartialEq,Eq,Copy,Clone)]
pub enum ParseArgumentAction {
Parse,
ShowSmallOverview,
}
#[derive(Debug,PartialEq,Eq,Copy,Clone)]
pub enum TypeOfCommand {
WithSubCommands(),
Other(),
}
impl<'a> Command<'a> {
pub fn parse_args(&mut self, input_args: &mut Vec<&str>, command_prefix: &str, type_of_command: &TypeOfCommand) -> Result<ParseArgumentAction,String> {
let has_subcommands = matches!(type_of_command, TypeOfCommand::WithSubCommands());
let mut seen_other = false;
let mut i = 0;
'outer: while i < input_args.len() {
let arg = input_args[i];
if arg == "--" {
input_args.remove(i);
break;
}
if !arg.starts_with('-') {
if !self.flags_after_position_arguments {
break;
}
seen_other = true;
i += 1;
continue;
}
for flag in self.flags.iter_mut() {
for name in flag.option_names {
if arg == *name {
if i+1 >= input_args.len() {
flag.field.parse_flag(&[])?;
input_args.drain(i..);
} else {
flag.field.parse_flag(&input_args[i+1..i+1+flag.argument_descriptions.len()])?;
input_args.drain(i..i+1+flag.argument_descriptions.len());
}
continue 'outer;
}
}
}
if seen_other && has_subcommands {
i += 1;
continue;
}
if !seen_other && arg.eq_ignore_ascii_case("--help") {
self.print_small_overview(command_prefix, type_of_command);
self.print_flags(" ");
return Ok(ParseArgumentAction::ShowSmallOverview);
}
eprintln!("unknown flag encountered: {}", arg);
std::process::exit(-1);
}
if let Some(positional) = &mut self.positional {
if positional.min > input_args.len() || input_args.len() > positional.max {
eprintln!("{}: expected between {} and {} values, not {}", positional.option_name, positional.min, positional.max, input_args.len());
std::process::exit(-1);
}
positional.field.parse_flag(input_args)?;
}
Ok(ParseArgumentAction::Parse)
}
pub fn positional(mut self, args: (&'static str, usize, usize), field: &'a mut dyn FlagParser) -> Self {
self.positional = Some(Arguments{option_name: args.0, min: args.1, max: args.2, field});
self
}
fn help_flags_overview(&self, suffix_for_help: &str) -> String {
let mut retval = String::new();
for flag in self.flags.iter() {
if flag.argument_descriptions.is_empty() {
write!(retval, " {TERM_BRIGHT_BLACK}[{TERM_BRIGHT_YELLOW}{}{TERM_BRIGHT_BLACK}]{TERM_RESET}", flag.option_names.join(&format!("{TERM_RESET},{TERM_BRIGHT_YELLOW}"))).unwrap();
} else {
write!(retval, " {TERM_BRIGHT_BLACK}[{TERM_BRIGHT_YELLOW}{} {TERM_BRIGHT_ORANGE}{}{TERM_BRIGHT_BLACK}]{TERM_RESET}", flag.option_names.join(&format!("{TERM_RESET},{TERM_BRIGHT_YELLOW}")), flag.argument_descriptions.join(" ")).unwrap();
}
}
if let Some(positional) = &self.positional {
write!(retval, " {TERM_UNDERLINE}{TERM_BRIGHT_ORANGE}{}{TERM_RESET}", positional.option_name.replace(' ', &format!("{TERM_RESET} {TERM_UNDERLINE}{TERM_BRIGHT_ORANGE}"))).unwrap();
} else if !suffix_for_help.is_empty() {
write!(retval, " {TERM_UNDERLINE}{TERM_BRIGHT_GREEN}{}{TERM_RESET}", suffix_for_help).unwrap();
}
retval
}
fn print_flags(&self, prefix: &str) {
for flag in self.flags.iter() {
println!("{}{TERM_BRIGHT_YELLOW}{} {TERM_BRIGHT_ORANGE}{}{TERM_RESET}", prefix, flag.option_names.join(&format!("{TERM_RESET},{TERM_BRIGHT_YELLOW}")), flag.argument_descriptions.join(" "));
println!(" {}", flag.description);
}
}
pub fn print_small_overview(&self, command_prefix: &str, type_of_command: &TypeOfCommand) {
let suffix_for_help = match type_of_command {
TypeOfCommand::WithSubCommands() => "subcommand",
TypeOfCommand::Other() => "",
};
println!("{}{TERM_BOLD}{TERM_BRIGHT_GREEN}{}{TERM_RESET}{}", command_prefix, self.command_name, self.help_flags_overview(suffix_for_help));
println!("{}{TERM_BRIGHT_WHITE}↳{TERM_RESET} {}", [' '].iter().cycle().take(command_prefix.len()).collect::<String>(), self.short_description.trim());
}
}
pub fn parse_args<T: Default + ArgumentParser + 'static>() -> T {
let args : Vec<String> = env::args().collect();
let mut args : Vec<&str> = args.iter().map(|x| x.as_ref()).collect();
let mut retval = T::default();
if let Err(message) = retval.parse_arguments(&mut args, "", ParseArgumentAction::Parse) {
println!("parse error on {:?}: {}", args, message);
std::process::exit(-1);
}
retval
}
impl FlagParser for bool {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.is_empty() {
*self = !*self;
Ok(())
} else if args.len() == 1 {
*self = args[0].parse().map_err(|e: std::str::ParseBoolError| e.to_string())?;
Ok(())
} else {
Err(String::from("bool flag expects zero or one arguments"))
}
}
}
impl FlagParser for String {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.len() != 1 {
return Err(String::from("string flag expects one argument"));
}
*self = args[0].to_string();
Ok(())
}
}
impl<T: FlagParser + Default> FlagParser for Vec<T> {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
for arg in args {
let mut retval = T::default();
retval.parse_flag(&[arg])?;
self.push(retval);
}
Ok(())
}
}
impl FlagParser for usize {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.len() != 1 {
return Err(String::from("int flag expects one argument"));
}
*self = args[0].parse().map_err(|x: core::num::ParseIntError| x.to_string())?;
Ok(())
}
}
impl FlagParser for u16 {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.len() != 1 {
return Err(String::from("int flag expects one argument"));
}
*self = args[0].parse().map_err(|x: core::num::ParseIntError| x.to_string())?;
Ok(())
}
}
impl FlagParser for u32 {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.len() != 1 {
return Err(String::from("int flag expects one argument"));
}
*self = args[0].parse().map_err(|x: core::num::ParseIntError| x.to_string())?;
Ok(())
}
}
impl FlagParser for u64 {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
if args.len() != 1 {
return Err(String::from("int flag expects one argument"));
}
*self = args[0].parse().map_err(|x: core::num::ParseIntError| x.to_string())?;
Ok(())
}
}
impl<T: FlagParser + Default> FlagParser for Option<T> {
fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
let mut value = T::default();
value.parse_flag(args)?;
*self = Some(value);
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}