use crate::help::CliHelpScreen;
use crate::CliCommand;
use crate::*;
use std::collections::HashMap;
use std::env;
use strsim::levenshtein;
pub struct CliRouter {
pub commands: HashMap<String, Box<dyn CliCommand>>,
pub shortcuts: HashMap<String, String>,
pub value_flags: HashMap<String, Vec<String>>,
pub categories: HashMap<String, (String, String)>,
}
pub struct CliRequest {
pub cmd_alias: String,
pub is_help: bool,
pub args: Vec<String>,
pub flags: Vec<String>,
pub value_flags: HashMap<String, String>,
pub shortcuts: Vec<String>,
}
impl Default for CliRouter {
fn default() -> Self {
Self::new()
}
}
impl CliRouter {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
shortcuts: HashMap::new(),
value_flags: HashMap::new(),
categories: HashMap::new(),
}
}
pub fn add<T: CliCommand + Default + 'static>(
&mut self,
alias: &str,
shortcuts: Vec<&str>,
value_flags: Vec<&str>,
) {
let cmd = Box::<T>::default();
self.commands.insert(alias.to_string(), cmd);
for shortcut in shortcuts {
self.shortcuts
.insert(shortcut.to_string(), alias.to_string());
}
let flags: Vec<String> = value_flags.iter().map(|s| s.to_string()).collect();
self.value_flags.insert(alias.to_string(), flags);
}
pub fn lookup(&self) -> (&Box<dyn CliCommand>, CliRequest) {
let mut cmdargs: Vec<String> = env::args().collect();
cmdargs.remove(0);
if cmdargs.is_empty() {
cli_error("You did not specify a command to run. Please specify a command or use 'help' or '-h' to view a list of all available commands.");
}
let mut extra_args: Vec<String> = Vec::new();
let mut cmd_alias = String::new();
let mut is_help: bool = false;
if cmdargs[0] == "help" || cmdargs[0] == "-h" {
is_help = true;
cmdargs.remove(0);
}
if is_help && cmdargs.is_empty() {
CliHelpScreen::render_index(self);
}
loop {
if cmdargs.is_empty() && is_help {
break;
} else if cmdargs.is_empty() {
cli_error("No command exists with that name. Use either 'help' or the -h flag to view a list of all available commands.");
}
let alias = cmdargs.join(" ").to_string();
if self.commands.contains_key(&alias) {
cmd_alias = alias;
break;
} else if self.shortcuts.contains_key(&alias) {
cmd_alias = self.shortcuts.get(&alias).unwrap().to_string();
break;
} else if is_help && self.categories.contains_key(&alias) {
CliHelpScreen::render_category(self, &alias);
} else if let Some(found_cmd) = self.lookup_similar(&alias) {
let confirm_msg = format!("No command with that name exists, but a similar command with the name '{}' does exist. Is this the command you wish to run?", found_cmd);
if cli_confirm(&confirm_msg) {
cmd_alias = found_cmd.to_string();
break;
}
}
extra_args.insert(0, cmdargs.pop().unwrap());
}
let mut args: Vec<String> = Vec::new();
let mut flags: Vec<String> = Vec::new();
let mut value_flags: HashMap<String, String> = HashMap::new();
let flag_values = self.value_flags.get(&cmd_alias).unwrap();
while !extra_args.is_empty() {
let chk_arg = extra_args[0].to_string();
extra_args.remove(0);
if chk_arg.starts_with("--") {
let arg = chk_arg.trim_start_matches('-').to_string();
if flag_values.contains(&arg) {
value_flags.insert(arg, extra_args[0].to_string());
extra_args.remove(0);
} else {
flags.push(arg);
}
} else if chk_arg.starts_with('-') {
let arg = chk_arg.trim_start_matches('-').to_string();
for c in arg.chars() {
flags.push(c.to_string());
}
} else {
args.push(chk_arg);
}
}
let shortcuts: Vec<String> = self
.shortcuts
.iter()
.filter_map(|(key, value)| {
if *value == cmd_alias {
Some(key.to_string())
} else {
None
}
})
.collect();
let cmd = self.commands.get(&cmd_alias).unwrap();
let req = CliRequest {
cmd_alias,
is_help,
args,
flags,
value_flags,
shortcuts,
};
(cmd, req)
}
fn lookup_similar(&self, chk_cmd: &String) -> Option<&String> {
let mut distance = 4;
let mut res = None;
for cmd in self.commands.keys() {
let num = levenshtein(chk_cmd, cmd);
if num < distance {
distance = num;
res = Some(cmd)
}
}
res
}
pub fn add_category(&mut self, alias: &str, name: &str, description: &str) {
self.categories.insert(
alias.to_string(),
(name.to_string(), description.to_string()),
);
}
}