#![allow(unused)]
use std::{env, fmt::Debug, path::PathBuf, rc::Rc};
use crate::{
core::errors::CmderError,
parse::{matches::ParserMatches, parser::Parser, Argument},
ui::formatter::FormatGenerator,
utils::{self, HelpWriter},
Event, Pattern, PredefinedThemes, Theme,
};
use super::events::EventListener;
use super::{
super::parse::flags::{CmderFlag, CmderOption},
events::{EventConfig, EventEmitter},
settings::{ProgramSettings, Setting},
};
type Callback = fn(ParserMatches) -> ();
pub struct Program {}
impl Program {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> Command<'static> {
Command {
flags: vec![
CmderFlag::new("-v", "--version", "Print out version information"),
CmderFlag::new("-h", "--help", "Print out help information"),
],
is_root: true,
emitter: Some(EventEmitter::default()),
..Command::new("")
}
}
}
#[derive(Clone)]
pub struct Command<'p> {
name: String,
theme: Theme,
is_root: bool,
pattern: Pattern,
alias: Option<&'p str>,
author: Option<&'p str>,
version: Option<&'p str>,
arguments: Vec<Argument>,
flags: Vec<CmderFlag<'p>>,
options: Vec<CmderOption<'p>>,
description: Option<&'p str>,
more_info: Option<&'p str>,
usage_str: Option<&'p str>,
settings: ProgramSettings,
emitter: Option<EventEmitter>,
subcommands: Vec<Command<'p>>,
callback: Option<Callback>, parent: Option<Rc<Command<'p>>>,
}
impl<'d> Debug for Command<'d> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"
name: {},
alias: {},
args: {:#?},
flags: {:#?},
options: {:#?},
subcmds: {:#?},
",
self.name,
self.alias.unwrap_or(""),
self.arguments,
self.flags,
self.options,
self.subcommands,
))
}
}
impl<'p> Command<'p> {
pub fn new(name: &'p str) -> Self {
Self {
name: name.to_string(),
alias: None,
arguments: vec![],
description: None,
flags: vec![CmderFlag::new("-h", "--help", "Print out help information")],
options: vec![],
subcommands: vec![],
callback: None,
parent: None,
more_info: None,
version: None,
author: None,
theme: Theme::default(),
pattern: Pattern::Legacy,
emitter: None,
settings: ProgramSettings::default(),
is_root: false,
usage_str: None,
}
}
pub fn author(&mut self, author: &'p str) -> &mut Self {
self.author = Some(author);
self
}
pub fn version(&mut self, val: &'p str) -> &mut Self {
self.version = Some(val);
self
}
pub fn bin_name(&mut self, val: &'p str) -> &mut Self {
if self.is_root {
self.name = val.into();
}
self
}
pub fn get_author(&self) -> &str {
self.author.unwrap_or("")
}
pub fn get_version(&self) -> &str {
self.version.unwrap_or("")
}
pub fn get_theme(&self) -> &Theme {
&self.theme
}
pub fn get_pattern(&self) -> &Pattern {
&self.pattern
}
pub fn get_name(&self) -> &str {
self.name.as_str()
}
pub fn get_alias(&self) -> &str {
self.alias.unwrap_or("")
}
pub fn get_flags(&self) -> &Vec<CmderFlag> {
&self.flags
}
pub fn get_description(&self) -> &str {
self.description.unwrap_or("")
}
pub fn get_options(&self) -> &Vec<CmderOption> {
&self.options
}
pub fn get_arguments(&self) -> &Vec<Argument> {
&self.arguments
}
pub fn get_subcommands(&self) -> &Vec<Self> {
&self.subcommands
}
pub fn get_parent(&self) -> Option<&Rc<Self>> {
self.parent.as_ref()
}
pub fn get_usage_str(&self) -> String {
let mut parent = self.get_parent();
let mut usage = vec![self.get_name()];
let mut usage_str = String::new();
while parent.is_some() {
usage.push(parent.unwrap().get_name());
parent = parent.unwrap().get_parent();
}
usage.reverse();
for v in &usage {
usage_str.push_str(v);
usage_str.push(' ');
}
usage_str.trim().into()
}
pub fn find_subcommand(&self, val: &str) -> Option<&Command<'_>> {
self.subcommands
.iter()
.find(|c| c.get_name() == val || c.get_alias() == val)
}
fn _set_bin_name(&mut self, val: &str) {
if self.name.is_empty() {
let p_buff = PathBuf::from(val);
if let Some(name) = p_buff.file_name() {
self.name = name.to_str().unwrap().into();
};
}
}
fn _add_args(&mut self, args: &[&str]) {
for p in args.iter() {
let temp = Argument::new(p, None);
if !self.arguments.contains(&temp) {
self.arguments.push(temp);
}
}
}
fn _add_parent(&mut self, parent: Rc<Self>) -> &mut Self {
self.parent = Some(parent);
self
}
#[deprecated(note = "Subcmds now built automatically")]
pub fn build(&mut self) {}
pub fn alias(&mut self, val: &'p str) -> &mut Self {
self.alias = Some(val);
self
}
pub fn description(&mut self, val: &'p str) -> &mut Self {
self.description = Some(val);
self
}
pub fn subcommand(&mut self, name: &'p str) -> &mut Self {
let parent = Rc::new(self.to_owned());
self.subcommands.push(Self::new(name));
self.subcommands.last_mut().unwrap()._add_parent(parent)
}
pub fn argument(&mut self, val: &str, help: &str) -> &mut Self {
let arg = Argument::new(val, Some(help.to_string()));
if !self.arguments.contains(&arg) {
self.arguments.push(arg);
}
self
}
pub fn action(&mut self, cb: Callback) -> &mut Self {
self.callback = Some(cb);
self
}
fn _generate_option(&mut self, values: Vec<&'p str>, help: &'p str, r: bool) {
let mut short = "";
let mut long = "";
let mut args = vec![];
for v in &values {
if v.starts_with("--") {
long = v;
} else if v.starts_with('-') {
short = v;
} else {
args.push(*v);
}
}
let option = CmderOption::new(short, long, help, &args[..]).required(r);
if !self.options.contains(&option) {
self.options.push(option)
}
}
pub fn required(&mut self, val: &'p str, help: &'p str) -> &mut Self {
let values: Vec<_> = val.split_whitespace().collect();
self._generate_option(values, help, true);
self
}
pub fn option(&mut self, val: &'p str, help: &'p str) -> &mut Self {
let values: Vec<_> = val.split_whitespace().collect();
let mut short = "";
let mut long = "";
let mut args = vec![];
for v in &values {
if v.starts_with("--") {
long = v;
} else if v.starts_with('-') {
short = v;
} else {
args.push(*v);
}
}
if args.is_empty() {
let flag = CmderFlag::new(short, long, help);
if !self.flags.contains(&flag) {
self.flags.push(flag)
};
} else {
self._generate_option(values, help, false);
}
self
}
pub fn on(&mut self, event: Event, cb: EventListener) {
if let Some(emitter) = &mut self.emitter {
emitter.on(event, cb, 0)
}
}
pub(crate) fn emit(&self, cfg: EventConfig) {
if let Some(emitter) = &self.emitter {
emitter.emit(cfg);
}
}
pub fn set(&mut self, setting: Setting) {
let s = &mut self.settings;
use Setting::*;
match setting {
ChoosePredefinedTheme(theme) => match theme {
PredefinedThemes::Plain => self.theme = Theme::plain(),
PredefinedThemes::Colorful => self.theme = Theme::colorful(),
},
EnableCommandSuggestion(enable) => s.enable_command_suggestions = enable,
HideCommandAliases(hide) => s.hide_command_aliases = hide,
SeparateOptionsAndFlags(separate) => s.separate_options_and_flags = separate,
ShowHelpOnAllErrors(show) => s.show_help_on_all_errors = show,
ShowHelpOnEmptyArgs(show) => s.show_help_on_empty_args = show,
DefineCustomTheme(theme) => self.theme = theme,
SetProgramPattern(pattern) => self.pattern = pattern,
OverrideAllDefaultListeners(val) => s.override_all_default_listeners = val,
OverrideSpecificEventListener(event) => s.events_to_override.push(event),
AutoIncludeHelpSubcommand(val) => s.auto_include_help_subcommand = val,
EnableTreeViewSubcommand(val) => s.enable_tree_view_subcommand = val,
IgnoreAllErrors(val) => s.ignore_all_errors = val,
}
}
fn _handle_root_flags(&self, matches: &ParserMatches) {
let cmd = matches.get_matched_cmd().unwrap();
let program = matches.get_program();
let cfg = EventConfig::new(program);
if matches.contains_flag("-h") {
self.emit(cfg.set_matched_cmd(cmd).set_event(Event::OutputHelp));
} else if matches.contains_flag("-v") && cmd.is_root {
self.emit(
cfg.arg_c(1_usize)
.args(vec![program.get_version().to_string()])
.set_event(Event::OutputVersion),
);
}
}
fn __parse(&'p mut self, args: Vec<String>) {
self._set_bin_name(&args[0]);
self.__init();
let mut parser = Parser::new(self);
match parser.parse(args[1..].to_vec()) {
Ok(matches) => {
self._handle_root_flags(&matches);
if let Some(cmd) = matches.get_matched_cmd() {
if let Some(cb) = cmd.callback {
(cb)(matches);
}
}
}
Err(e) => {
let clone = self.clone();
let shared_cfg = EventConfig::new(&clone).error_str(e.clone().into());
use CmderError::*;
let event_cfg = match e {
MissingRequiredArgument(args) => shared_cfg
.arg_c(args.len())
.args(args)
.exit_code(5)
.set_event(Event::MissingRequiredArgument),
OptionMissingArgument(args) => shared_cfg
.arg_c(args.len())
.args(args)
.exit_code(10)
.set_event(Event::OptionMissingArgument),
UnknownCommand(cmd) => shared_cfg
.arg_c(1)
.args(vec![cmd])
.exit_code(15)
.set_event(Event::UnknownCommand),
UnknownOption(opt) => shared_cfg
.arg_c(1)
.args(vec![opt])
.exit_code(20)
.set_event(Event::UnknownOption),
UnresolvedArgument(vals) => shared_cfg
.arg_c(vals.len())
.args(vals)
.exit_code(25)
.set_event(Event::UnresolvedArgument),
};
self.emit(event_cfg);
}
}
}
fn __init(&mut self) {
if !self.subcommands.is_empty() && self.settings.auto_include_help_subcommand {
self.subcommand("help")
.argument("<SUB-COMMAND>", "The subcommand to print out help info for")
.description("A subcommand used for printing out help")
.action(|m| {
let cmd = m.get_matched_cmd().unwrap();
let val = m.get_arg("<SUB-COMMAND>").unwrap();
let parent = cmd.get_parent().unwrap();
if let Some(cmd) = parent.find_subcommand(&val) {
cmd.output_help();
}
});
}
if let Some(emitter) = &mut self.emitter {
let settings = &self.settings;
use Event::*;
emitter.on(
OutputHelp,
|cfg| cfg.get_matched_cmd().unwrap().output_help(),
-4,
);
if !settings.override_all_default_listeners {
if !settings.ignore_all_errors {
emitter.on_all_errors(
|cfg| {
let error = cfg.get_error_str();
if !error.is_empty() {
eprintln!("Error: {error}");
}
},
-4,
);
}
emitter.on(
OutputVersion,
|cfg| {
let p = cfg.get_program();
println!("{}, v{}", p.get_name(), p.get_version());
println!("{}", p.get_author());
println!("{}", p.get_description());
},
-4,
);
for event in &settings.events_to_override {
emitter.rm_lstnr_idx(*event, -4)
}
}
if settings.show_help_on_all_errors && !settings.ignore_all_errors {
let _output_help_ = |cfg: EventConfig| cfg.get_matched_cmd().unwrap().output_help();
emitter.insert_before_all(_output_help_);
}
if settings.enable_command_suggestions {
emitter.rm_lstnr_idx(UnknownCommand, -4);
emitter.on(
UnknownCommand,
|cfg| {
println!("Error: {}\n", cfg.get_error_str());
let prog = cfg.get_program();
let cmd = &cfg.get_args()[0];
if let Some(ans) = utils::suggest_cmd(cmd, prog.get_subcommands()) {
println!(" Did you mean: `{ans}` ?\n")
}
},
-1,
)
}
}
}
pub fn parse(&'p mut self) {
let args = env::args().collect::<Vec<_>>();
self.__parse(args);
}
pub fn parse_from(&'p mut self, list: Vec<&str>) {
let args = list.iter().map(|a| a.to_string()).collect::<Vec<_>>();
self.__parse(args);
}
pub fn output_help(&self) {
HelpWriter::write(self, self.get_theme(), self.get_pattern());
}
pub fn before_all(&mut self, cb: EventListener) {
if let Some(emitter) = &mut self.emitter {
emitter.insert_before_all(cb)
}
}
pub fn after_all(&mut self, cb: EventListener) {
if let Some(emitter) = &mut self.emitter {
emitter.insert_after_all(cb)
}
}
pub fn before_help(&mut self, cb: EventListener) {
if let Some(emitter) = &mut self.emitter {
emitter.on(Event::OutputHelp, cb, -4)
}
}
pub fn after_help(&mut self, cb: EventListener) {
if let Some(emitter) = &mut self.emitter {
emitter.on(Event::OutputHelp, cb, 1)
}
}
pub fn display_commands_tree(&self) {
let mut commands = self.get_subcommands();
let mut empty = String::new();
let mut parent = self.get_parent();
while parent.is_some() {
empty.push('\t');
empty.push('|');
parent = parent.unwrap().get_parent();
}
println!("{}-> {}", &empty, self.get_name());
for cmd in commands.iter() {
cmd.display_commands_tree();
}
}
pub fn init_dbg(&mut self) {
self.__init();
}
}
impl<'f> FormatGenerator for Command<'f> {
fn generate(&self, ptrn: Pattern) -> (String, String) {
match &ptrn {
Pattern::Custom(ptrn) => {
let base = &ptrn.sub_cmds_fmter;
let mut leading = base.replace("{{name}}", self.get_name());
let mut floating = String::from("");
if let Some(alias) = self.alias {
leading = leading.replace("{{alias}}", alias)
} else {
leading = leading.replace("{{alias}}", "")
}
if base.contains("{{args}}") && !self.get_arguments().is_empty() {
let mut value = String::new();
for a in self.get_arguments() {
value.push_str(&(a.literal));
value.push(' ');
}
leading = leading.replace("{{args}}", value.trim());
}
if base.contains("{{description}}") {
leading = leading.replace("{{description}}", self.get_author());
} else {
floating = self.get_description().into()
}
(leading, floating)
}
_ => (self.get_name().into(), self.get_description().into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prog_creation() {
let mut program = Program::new();
assert!(program.is_root);
assert!(program.emitter.is_some());
assert!(program.get_flags().len() == 2);
assert!(program.get_parent().is_none());
assert!(program.get_name().is_empty());
assert!(program.get_version().is_empty());
assert!(program.get_subcommands().is_empty());
program
.author("vndaba")
.bin_name("test1")
.version("0.1.0")
.argument("<dummy>", "Some dummy value");
assert_eq!(program.get_author(), "vndaba");
assert_eq!(program.get_name(), "test1");
assert_eq!(program.get_version(), "0.1.0");
assert_eq!(
program.get_arguments(),
&vec![Argument::new("<dummy>", Some("Some dummy value".into()))]
)
}
#[test]
fn test_cmd_creation() {
let cmd = Command::new("test2");
assert!(!cmd.is_root);
assert!(cmd.emitter.is_none());
assert!(cmd.parent.is_none());
assert_eq!(cmd.get_name(), "test2");
assert_eq!(
cmd.get_flags(),
&vec![CmderFlag::new("-h", "--help", "Print out help information")]
);
}
}