use std::str;
use owo_colors::OwoColorize;
use prettytable::{format::TableFormat, row, table};
#[derive(Debug, Clone)]
pub struct CmdInfo<'a> {
pub name: &'a str,
pub short_name: &'a str,
pub about: &'a str,
pub quick_help: &'a str,
pub args: &'a [(&'a str, &'a str)],
}
impl<'a> CmdInfo<'a> {
pub fn print_cmd_quick_help(&self, app_name: &str) -> String {
let quick_help_lines_count = self.quick_help.split(|x| x == '\n').count();
if !self.quick_help.is_empty() && quick_help_lines_count > 1 {
return self.quick_help.to_string();
}
let name = self.name.style(CMD_NAME);
let about = self.about;
let app_name = app_name.style(CMD_NAME);
let args = self.format_args().style(ARG_TYPE).to_string();
let usage = "Usage:".style(ARG_VALUE);
return format!(
r###"
{about}
{usage}
{app_name} {name} {args}
"###
);
}
pub const fn new(
name: &'a str,
short_name: &'a str,
about: &'a str,
args: &'a [(&'a str, &'a str)],
quick_help: &'a str,
) -> Self {
CmdInfo {
name,
short_name,
about,
args: args,
quick_help: quick_help,
}
}
fn format_args(&self) -> String {
let mut re = String::new();
for x in self.args {
if is_colection_type(x.1) {
re.push_str("[");
re.push_str(&x.0.to_uppercase());
re.push_str("...");
re.push_str("] ");
} else {
re.push_str("<");
re.push_str(&x.0.to_uppercase());
re.push_str("> ");
}
}
return re;
fn is_colection_type(type_name: &str) -> bool {
let collection_types = ["Vec", "HashSet", "BTreeSet", "VecDeque"];
let type_name: &str = &type_name.replace(" ", "");
for x in collection_types {
if type_name.starts_with(x) {
return true;
}
}
return false;
}
}
}
#[derive(Debug, Clone)]
pub struct App<'a> {
pub _app_name: String,
pub _about: &'a str,
pub _version: &'a str,
pub _author: &'a str,
pub _help: &'a str,
pub _commands: Vec<CmdInfo<'a>>,
pub _user_inputed_cmd_name: Option<String>,
pub _user_inputed_cmd_args: Vec<String>,
}
impl<'a> App<'a> {
pub const fn const_new() -> Self {
Self {
_app_name: String::new(),
_about: "",
_version: "",
_author: "",
_help: "",
_commands: Vec::new(),
_user_inputed_cmd_name: Some(String::new()),
_user_inputed_cmd_args: Vec::new(),
}
}
pub fn new() -> Self {
let env_args: Vec<String> = std::env::args().collect();
let user_inputed_cmd_name = env_args.get(1).map(|x| x.to_string());
let app_name = env_args
.first()
.cloned()
.map(|path| {
std::path::Path::new(&path)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
})
.unwrap_or_default()
.unwrap_or_default();
let user_inputed_cmd_args: Vec<String> = {
if env_args.len() > 2 {
env_args[2..].to_vec()
} else {
vec![]
}
};
return Self {
_app_name: app_name,
_about: "",
_user_inputed_cmd_name: user_inputed_cmd_name,
_user_inputed_cmd_args: user_inputed_cmd_args,
_version: env!("CARGO_PKG_VERSION"),
_commands: Vec::new(),
_author: "",
_help: "",
};
}
pub fn app_name(self, name: &str) -> Self {
let mut re = self;
re._app_name = name.to_string();
return re;
}
pub const fn about(self, description: &'a str) -> Self {
let mut re = self;
re._about = description;
return re;
}
pub const fn version(self, version: &'a str) -> Self {
let mut re = self;
re._version = version;
return re;
}
pub const fn author(self, author: &'a str) -> Self {
let mut re = self;
re._author = author;
return re;
}
pub const fn help(self, help: &'a str) -> Self {
let mut re = self;
re._help = help;
return re;
}
pub fn print_cmd_quick_help_for(&self, cmd_name: &str) {
let re = self
._commands
.iter()
.find(|x| x.name == cmd_name || x.short_name == cmd_name);
match re {
Some(info) => {
println!("{}", info.print_cmd_quick_help(&self._app_name));
}
None => {
eprintln!("Unkone Command: {cmd_name}")
}
};
}
pub fn print_app_help(&self) {
if !self._help.is_empty() {
return println!("{}", self._help);
}
let app_name = self._app_name.style(CMD_NAME);
let description = self._about;
let author = "\n".to_string() + self._author;
let usage_marker = "Usage:".bold().green().to_string();
let usage = format!(" {app_name} {}", "[commands]".style(ARG_TYPE));
let commands_marker = "Commands:".bold().green().to_string();
let command_list = {
let mut table = table!();
{
let mut f = TableFormat::new();
f.column_separator(' ');
f.padding(4, 0);
table.set_format(f);
}
for x in self._commands.iter() {
let name: String = {
let short = if !x.short_name.is_empty() {
", ".to_string() + x.short_name
} else {
"".into()
};
(x.name.to_string() + &short).cyan().bold().to_string()
};
table.add_row(row![name, x.about]);
}
table
};
let app_help_msg: String = format!(
r###"
{description}{author}
{usage_marker}
{usage}
{commands_marker}
{command_list}
Use "{app_name} {h} {c}" for more information about a command.
"###,
h = "help".style(CMD_NAME),
c = "<command>".style(ARG_TYPE)
);
println!("{}", app_help_msg);
}
pub fn print_app_version(&self) {
println!(
"{}: {}",
self._app_name,
format!("{:?}", self._version).bright_green()
)
}
}
const CMD_NAME: owo_colors::Style = owo_colors::style().bold().cyan();
const ARG_TYPE: owo_colors::Style = owo_colors::style().cyan();
const ARG_VALUE: owo_colors::Style = owo_colors::style().bold().green();