use std::collections::HashMap;
use colored::*;
use regex::Regex;
pub struct Command {
pub name: String,
pub args: HashMap<String, Option<String>>,
pub required: Vec<String>,
pub optional: Vec<String>,
pub definition: String,
}
impl Command {
pub fn get(&self, key: &str) -> Option<&str> {
self.args.get(key).and_then(|v| v.as_deref())
}
pub fn flag(&self, key: &str) -> bool {
self.args.contains_key(key)
}
pub fn has(&self, key: &str) -> bool {
self.args.contains_key(key)
}
pub fn is(&self, name: &str) -> bool {
self.name == name
}
pub fn check_required(&self) {
for key in &self.required {
if !self.args.contains_key(key) {
println!("{} {}", "Error:".red().bold(), format!("Missing required argument: --{}", key));
println!("{}\n{}", "Usage:".green(), self.definition);
std::process::exit(1);
}
}
}
}
pub fn cli(definition: &str, handler: impl Fn(Command)) {
let args: Vec<String> = std::env::args().skip(1).collect();
if args.is_empty() || args.contains(&"--help".to_string()) {
println!("{}\n{}", "Help:".bold().green(), definition);
return;
}
let (required, optional) = parse_definition(definition);
let command_name = args[0].clone();
let mut parsed_args = HashMap::new();
let mut iter = args.into_iter().skip(1);
while let Some(arg) = iter.next() {
if arg.starts_with("--") {
if let Some((key, val)) = arg.split_once('=') {
parsed_args.insert(key.trim_start_matches("--").to_string(), Some(val.to_string()));
} else {
let key = arg.trim_start_matches("--").to_string();
if let Some(next) = iter.clone().next() {
if !next.starts_with("--") {
parsed_args.insert(key, Some(iter.next().unwrap()));
} else {
parsed_args.insert(key, None);
}
} else {
parsed_args.insert(key, None);
}
}
}
}
let cmd = Command {
name: command_name,
args: parsed_args,
required,
optional,
definition: definition.to_string(),
};
cmd.check_required();
handler(cmd);
}
fn parse_definition(def: &str) -> (Vec<String>, Vec<String>) {
let req_re = Regex::new(r"--(\\w+)=<\\w+>").unwrap();
let opt_re = Regex::new(r"\\[--(\\w+)\\]").unwrap();
let mut required = Vec::new();
let mut optional = Vec::new();
for cap in req_re.captures_iter(def) {
required.push(cap[1].to_string());
}
for cap in opt_re.captures_iter(def) {
optional.push(cap[1].to_string());
}
(required, optional)
}