use static_assertions::*;
use std::collections::HashMap;
const FLAG_PREFIX: char = '-';
#[derive(Clone)]
pub struct Flag {
pub identifier: &'static str,
pub flag_help: &'static str,
pub required: bool
}
pub trait Command {
fn execute_command(&self, flags: HashMap<String, String>);
fn get_information(&self) -> CommandInformation;
}
assert_obj_safe!(Command);
#[derive(Clone)]
pub struct CommandInformation {
pub command_name: &'static str,
pub command_help: &'static str,
pub flags: std::vec::Vec<Flag>,
}
pub struct HelpCommand {
pub known_commands: std::vec::Vec<CommandInformation>
}
impl HelpCommand {
pub fn new(commands: &std::vec::Vec<Box<dyn Command>>) -> HelpCommand {
let mut known: std::vec::Vec<CommandInformation> = std::vec::Vec::with_capacity(commands.len() + 1);
known.push(HelpCommand::get_info());
for command in commands {
known.push(command.get_information());
}
return HelpCommand {
known_commands: known
};
}
fn get_info() -> CommandInformation {
return CommandInformation {
command_name: "help",
command_help: "Displays help information about commands and their flags.",
flags:
vec![
Flag {
identifier: "c",
flag_help: "Displays information about the specified command and its flags",
required: false
},
Flag {
identifier: "f",
flag_help: "Displays information about a flag specific to the specified command",
required: false
}
]
}
}
fn display_all_commands_help(&self) {
for info in &self.known_commands {
println!("{}, {}, # Flags: {}", info.command_name, info.command_help, info.flags.len());
}
}
fn display_command_help(&self, command: CommandInformation) {
println!("'{}' help", command.command_name);
println!("{}", command.command_help);
println!("");
for flag in command.flags {
println!("-{}, {}, required: {}", flag.identifier, flag.flag_help, flag.required);
}
}
fn display_flag_help(&self, command: CommandInformation, flag_identifier: &String) {
let flag_help = self.search_flag_help(command.flags, flag_identifier);
if flag_help.is_some() {
println!("{} -{}", command.command_name, flag_identifier);
println!("{}", flag_help.unwrap());
}
else {
println!("{} does not have a flag -{}", command.command_name, flag_identifier);
}
}
fn search_flag_help(&self, flags: std::vec::Vec<Flag>, target_flag_identifier: &String) -> Option<&'static str> {
for flag in flags {
if flag.identifier == target_flag_identifier {
return Some(flag.flag_help);
}
}
return None;
}
fn get_command_info(&self, command_name: &String) -> Option<CommandInformation> {
for command_info in self.known_commands.clone() {
if command_info.command_name == command_name {
return Some(command_info);
}
}
return None;
}
}
impl Command for HelpCommand {
fn execute_command(&self, flags: std::collections::HashMap<std::string::String, std::string::String>) {
let command = flags.get("c");
if command.is_some() {
let command_info = self.get_command_info(command.unwrap());
if command_info.is_some() {
let flag = flags.get("f");
if flag.is_some() {
self.display_flag_help(command_info.unwrap(), flag.unwrap());
}
else {
self.display_command_help(command_info.unwrap());
}
}
else {
println!("{} is not a registered command", command.unwrap());
}
}
else {
self.display_all_commands_help();
}
}
fn get_information(&self) -> CommandInformation {
return HelpCommand::get_info();
}
}
pub struct Commander<'a> {
pub known_commands: HashMap<String, &'a Box<dyn Command>>
}
impl<'a> Commander<'a> {
pub fn new(commands: &'a mut std::vec::Vec<Box<dyn Command>>) -> Commander<'a> {
let help = HelpCommand::new(&commands);
commands.insert(0, Box::new(help));
let mut known: HashMap<String, &Box<dyn Command>> = HashMap::with_capacity(commands.len());
for command in commands {
known.insert(command.get_information().command_name.to_string(), command);
}
return Commander {
known_commands: known
};
}
fn parse_flags(&self, tokens: std::str::SplitWhitespace) -> Option<HashMap<String, String>> {
let mut parsed_flags = HashMap::new();
let mut flag = String::new();
let mut flag_value;
for token in tokens {
if token.starts_with(FLAG_PREFIX) {
flag = token.to_string().replace("-", "");
if parsed_flags.contains_key(&flag) {
println!("Flag {} has been discovered twice", flag);
return None;
}
else {
parsed_flags.insert(flag.clone(), String::default());
}
}
else {
flag_value = token.to_string();
let wrapped_stored_flag_value = parsed_flags.get_key_value(&flag);
if wrapped_stored_flag_value.is_some() {
if wrapped_stored_flag_value.unwrap().1 == &String::new() {
parsed_flags.remove_entry(&flag);
parsed_flags.insert(flag.clone(), flag_value);
}
else {
println!("Flag {} already has a value", flag);
return None;
}
}
else {
println!("Expected a flag, instead found: {}", flag_value);
}
}
}
return Some(parsed_flags);
}
fn verify_flags(&self, parsed_flags: &HashMap<String, String>, required_flags: std::vec::Vec<Flag>) -> bool {
for required_flag in required_flags {
if required_flag.required {
let had_flag = parsed_flags.contains_key(required_flag.identifier);
if !had_flag {
println!("Missing a required flag: {}", required_flag.identifier);
return false;
}
}
}
return true;
}
pub fn handle_input(&self, input: String) {
let mut tokens = input.trim().split_whitespace();
let command_name_wrapped = tokens.nth(0);
if command_name_wrapped.is_some() {
let command_name = command_name_wrapped.unwrap().trim().to_lowercase();
let target_command = self.known_commands.get(&command_name);
if target_command.is_some() {
let command = target_command.unwrap();
let found_flags: Option<HashMap<String, String>> = self.parse_flags(tokens);
if found_flags.is_some() {
let flags = found_flags.unwrap();
if self.verify_flags(&flags, command.get_information().flags) {
command.execute_command(flags);
}
}
}
else {
println!("Failed to find the target command: {}", command_name);
}
}
else {
println!("Expected a command name");
}
}
}