#[cfg(feature = "help")]
use core::fmt::Write as _;
#[cfg(not(feature = "std"))]
use crate::alloc_prelude::*;
use crate::spec::CommandSpec;
#[cfg(feature = "help")]
use crate::spec::{
ArgSpec,
Kind,
SubSpec,
};
pub(crate) fn version_line(spec: &CommandSpec) -> String {
if spec.version.is_empty() {
spec.name.to_owned()
} else {
format!("{} {}", spec.name, spec.version)
}
}
#[cfg(feature = "help")]
fn metavar(a: &ArgSpec) -> String {
let name = if a.value_name.is_empty() {
a.long.unwrap_or("arg")
} else {
a.value_name
};
name.to_uppercase()
}
#[cfg(feature = "help")]
fn usage_positional(a: &ArgSpec) -> String {
let meta = metavar(a);
let dots = if a.multi || a.kind == Kind::Trailing {
"..."
} else {
""
};
if a.required {
format!("{meta}{dots}")
} else {
format!("[{meta}]{dots}")
}
}
#[cfg(feature = "help")]
fn invocation(a: &ArgSpec) -> String {
let mut s = String::new();
let takes_value = a.kind == Kind::Opt;
match (a.short, a.long) {
(Some(c), Some(l)) => {
s.push('-');
s.push(c);
s.push_str(", --");
s.push_str(l);
if takes_value {
s.push('=');
s.push_str(&metavar(a));
}
},
(Some(c), None) => {
s.push('-');
s.push(c);
if takes_value {
s.push(' ');
s.push_str(&metavar(a));
}
},
(None, Some(l)) => {
s.push_str(" --");
s.push_str(l);
if takes_value {
s.push('=');
s.push_str(&metavar(a));
}
},
(None, None) => s.push_str(&metavar(a)),
}
s
}
#[cfg(feature = "help")]
fn help_text(a: &ArgSpec) -> String {
let mut s = a.help.to_owned();
if let Some(values) = a.possible
&& !values.is_empty()
{
if !s.is_empty() {
s.push(' ');
}
let _ = write!(s, "[possible values: {}]", values.join(", "));
}
s
}
#[cfg(feature = "help")]
pub(crate) fn render(spec: &CommandSpec) -> String {
let mut out = String::new();
if !spec.about.is_empty() {
out.push_str(spec.about);
out.push_str("\n\n");
}
let visible_args: Vec<&ArgSpec> = spec.args.iter().filter(|a| !a.hidden).collect();
let visible_subs: Vec<&SubSpec> = spec.subs.iter().filter(|s| !s.hidden).collect();
out.push_str("Usage: ");
out.push_str(spec.name);
if visible_args.iter().any(|a| !a.is_positional()) {
out.push_str(" [OPTION]...");
}
for a in visible_args.iter().filter(|a| a.is_positional()) {
out.push(' ');
out.push_str(&usage_positional(a));
}
if !visible_subs.is_empty() {
out.push_str(" COMMAND");
}
out.push('\n');
if !visible_subs.is_empty() {
out.push_str("\nCommands:\n");
let width = visible_subs.iter().map(|s| s.name.len()).max().unwrap_or(0);
for s in &visible_subs {
let _ = writeln!(out, " {:<width$} {}", s.name, s.about);
}
}
let positionals: Vec<(String, String)> = visible_args
.iter()
.filter(|a| a.is_positional())
.map(|&a| (usage_positional(a), help_text(a)))
.collect();
if !positionals.is_empty() {
out.push_str("\nArguments:\n");
push_rows(&mut out, &positionals);
}
out.push_str("\nOptions:\n");
let mut rows: Vec<(String, String)> = visible_args
.iter()
.filter(|a| !a.is_positional())
.map(|&a| (invocation(a), help_text(a)))
.collect();
if spec.find_short('h').is_none() && spec.find_long("help").is_none() {
rows.push(("-h, --help".to_owned(), "display this help and exit".to_owned()));
}
if !spec.version.is_empty()
&& spec.find_short('V').is_none()
&& spec.find_long("version").is_none()
{
rows.push((
"-V, --version".to_owned(),
"output version information and exit".to_owned(),
));
}
push_rows(&mut out, &rows);
out.truncate(out.trim_end().len());
out
}
#[cfg(feature = "help")]
fn push_rows(out: &mut String, rows: &[(String, String)]) {
let width = rows.iter().map(|(l, _)| l.len()).max().unwrap_or(0);
for (left, help) in rows {
if help.is_empty() {
let _ = writeln!(out, " {left}");
} else {
let _ = writeln!(out, " {left:<width$} {help}");
}
}
}
#[cfg(not(feature = "help"))]
pub(crate) fn render(spec: &CommandSpec) -> String {
let mut out = format!("Usage: {}", spec.name);
if spec.has_subs() {
out.push_str(" COMMAND");
}
out
}