use crate::messages::get_app_name;
use crate::{Arg, NULL_PTR};
use std::collections::BTreeMap;
pub struct AppProperties<'a> {
pub name: &'a str,
pub desc: &'a str,
pub version: &'a str,
}
pub struct ArgHelp<'a> {
pub short: Option<&'a str>,
pub long: &'a str,
pub desc: &'a str,
pub meta: Option<&'a str>,
pub is_subcommand: bool,
pub is_env: bool,
pub properties: Option<AppProperties<'a>>,
pub context: &'a [&'a str],
}
impl<'a> ArgHelp<'a> {
pub const fn properties(name: &'a str, desc: &'a str, ver: &'a str) -> Self {
Self {
short: None,
long: NULL_PTR,
desc: NULL_PTR,
meta: None,
is_subcommand: false,
is_env: false,
properties: Some(AppProperties {
name,
desc,
version: ver,
}),
context: &[],
}
}
pub const fn arg(short: Option<&'a str>, long: &'a str, desc: &'a str) -> Self {
Self {
short,
long,
desc,
meta: None,
is_env: false,
is_subcommand: false,
properties: None,
context: &[],
}
}
pub const fn env(var: &'a str, desc: &'a str) -> Self {
Self {
short: None,
long: var,
desc,
meta: None,
is_subcommand: false,
is_env: true,
properties: None,
context: &[],
}
}
pub const fn subcommand(short: Option<&'a str>, long: &'a str, desc: &'a str) -> Self {
Self {
short,
long,
desc,
meta: None,
is_env: false,
is_subcommand: true,
properties: None,
context: &[],
}
}
pub const fn context(mut self, context: &'a [&'a str]) -> Self {
self.context = context;
self
}
pub const fn meta(mut self, meta: &'a str) -> Self {
self.meta = Some(meta);
self
}
}
pub(crate) fn print_help(
sub: &str,
props: Option<&AppProperties>,
rules: &[Arg],
help_rules: &[ArgHelp],
is_all: bool,
) {
let app_name = get_app_name();
let has_subcommands = help_rules.iter().any(|h| h.is_subcommand);
if let Some(p) = props {
println!("{} - {}\n\n{}\n", app_name, p.name, p.desc);
} else {
println!("{}\n", app_name);
}
let sub_place = if has_subcommands { "[SUBCOMMAND] " } else { "" };
let place = if sub.is_empty() {
sub_place
} else {
&format!("{} ", sub)
};
println!("Usage: {} {}[OPTIONS] [--] [ARGS...]", app_name, place);
let act: Vec<&ArgHelp> = if is_all {
help_rules.iter().collect()
} else {
help_rules
.iter()
.filter(|h| {
if h.is_subcommand || h.is_env {
return true;
}
rules.iter().any(|r| {
let long_match = r.long.split('|').any(|part| part == h.long);
let short_match = match (h.short, r.short) {
(Some(h_short), Some(r_short)) => h_short == r_short,
(None, _) => true,
(Some(_), None) => false,
};
long_match && short_match
}) && (sub.is_empty() || h.context.contains(&sub) || h.context.is_empty())
})
.collect()
};
if is_all {
let scmd: Vec<&ArgHelp> = help_rules.iter().filter(|h| h.is_subcommand).collect();
if !scmd.is_empty() {
println!("\nAvailable Subcommands:");
print_entries(&scmd);
}
let mut grouped: BTreeMap<&str, Vec<&ArgHelp>> = BTreeMap::new();
for help in help_rules.iter().filter(|h| !h.is_subcommand) {
if help.context.is_empty() {
continue;
}
for ctx in help.context {
grouped.entry(ctx).or_default().push(help);
}
}
for (ctx, items) in grouped {
println!("\nOptions for '{}':", ctx);
print_entries(&items);
}
} else {
if sub.is_empty() {
let scmd: Vec<&ArgHelp> = act.iter().filter(|h| h.is_subcommand).cloned().collect();
if !scmd.is_empty() {
println!("\nAvailable Subcommands:");
print_entries(&scmd);
}
let opts: Vec<&ArgHelp> = act
.iter()
.filter(|h| !h.is_subcommand && !h.is_env && !h.context.is_empty())
.cloned()
.collect();
if !opts.is_empty() {
println!("\nOptions:");
print_entries(&opts);
}
} else {
let opts: Vec<&ArgHelp> = act
.iter()
.filter(|h| !h.is_subcommand && !h.is_env)
.cloned()
.collect();
if !opts.is_empty() {
println!("\nOptions for '{}':", sub);
print_entries(&opts);
}
}
}
println!("\nGeneral Options:");
println!(" {:<30}Show this help message", "-h, --help");
println!(" {:<30}Show this all help message", " --help-all");
println!(" {:<30}Show version", "-V, --version");
let envs: Vec<&ArgHelp> = act.iter().filter(|h| h.is_env).cloned().collect();
if !envs.is_empty() {
println!("\nEnvironment Variables:");
print_entries(&envs);
}
}
fn print_entries(items: &[&ArgHelp]) {
for h in items {
let display_long = match h.meta {
Some(m) => format!("{} {}", h.long, m),
None => h.long.to_string(),
};
let flag = match h.short {
Some(s) => format!("{:>2}, {}", s, display_long),
None => format!("{:>4}{}", NULL_PTR, display_long),
};
println!(" {:<28} {}", flag, h.desc);
}
}