use crate::types::*;
pub fn render_help(cmd: &CommandDef) -> String {
let mut out = String::new();
if !cmd.about.is_empty() {
out.push_str(cmd.about);
out.push_str("\n\n");
}
if cmd.name == "deno" {
out.push_str(&format!("Usage: {} [OPTIONS]", cmd.name));
} else {
out.push_str(&format!("Usage: deno {} [OPTIONS]", cmd.name));
}
let positionals: Vec<&ArgDef> =
cmd.all_args().filter(|a| a.positional).collect();
for pos in &positionals {
let name = pos.value_name.unwrap_or(pos.name).to_uppercase();
match pos.num_args {
NumArgs::Optional => out.push_str(&format!(" [{name}]")),
NumArgs::ZeroOrMore => out.push_str(&format!(" [{name}]...")),
NumArgs::OneOrMore => out.push_str(&format!(" <{name}>...")),
NumArgs::Exact(0) => {}
NumArgs::Exact(_) => out.push_str(&format!(" <{name}>")),
}
}
if !cmd.subcommands.is_empty() {
out.push_str(" [COMMAND]");
}
out.push('\n');
if !cmd.subcommands.is_empty() {
out.push_str("\nCommands:\n");
let max_name_len = cmd
.subcommands
.iter()
.filter(|s| !s.name.starts_with("json_reference"))
.map(|s| s.name.len())
.max()
.unwrap_or(0);
for sub in cmd.subcommands {
if sub.name.starts_with("json_reference") {
continue;
}
let short_about = sub.about.lines().next().unwrap_or("");
out.push_str(&format!(
" {:<width$} {}\n",
sub.name,
short_about,
width = max_name_len
));
}
}
let flags: Vec<&ArgDef> = cmd
.all_args()
.filter(|a| !a.positional && !a.hidden)
.collect();
if !flags.is_empty() {
out.push_str("\nOptions:\n");
for arg in &flags {
let mut flag_str = String::new();
if let Some(short) = arg.short {
flag_str.push_str(&format!("-{short}"));
if arg.long.is_some() {
flag_str.push_str(", ");
}
} else {
flag_str.push_str(" ");
}
if let Some(long) = arg.long {
flag_str.push_str(&format!("--{long}"));
match arg.num_args {
NumArgs::Exact(0) => {}
NumArgs::Optional => {
let vn = arg.value_name.unwrap_or("VALUE");
if arg.require_equals {
flag_str.push_str(&format!("[=<{vn}>]"));
} else {
flag_str.push_str(&format!(" [<{vn}>]"));
}
}
NumArgs::ZeroOrMore => {
let vn = arg.value_name.unwrap_or("VALUE");
if arg.require_equals {
flag_str.push_str(&format!("[={vn}...]"));
} else {
flag_str.push_str(&format!(" [{vn}...]"));
}
}
_ => {
let vn = arg.value_name.unwrap_or("VALUE");
flag_str.push_str(&format!(" <{vn}>"));
}
}
}
let help_lines: Vec<&str> = arg.help.split('\n').collect();
out.push_str(&format!(" {:<22} {}\n", flag_str, help_lines[0]));
for rest_line in &help_lines[1..] {
out.push_str(&format!("{:26}{}\n", "", rest_line));
}
}
}
if has_permission_args(cmd) {
out.push('\n');
out.push_str(crate::permission_help::PERMISSION_HELP);
}
if cmd.name == "deno" {
out.push_str(&render_env_vars());
}
out
}
fn has_permission_args(cmd: &CommandDef) -> bool {
cmd
.arg_groups
.iter()
.any(|g| std::ptr::eq(g.as_ptr(), crate::defs::PERMISSION_ARGS.as_ptr()))
}
fn render_env_vars() -> String {
use crate::env_vars::ENV_VARS;
let mut out = String::from(
"\nEnvironment variables:\nDocs: https://docs.deno.com/go/env-vars\n\n",
);
let width = ENV_VARS.iter().map(|v| v.name.len()).max().unwrap_or(0) + 1;
let entries: Vec<String> = ENV_VARS
.iter()
.map(|var| {
let indent = " ".repeat(width);
let desc = var.description.replace('\n', &format!("\n {indent}"));
let mut entry = format!(
" {}{}{}",
var.name,
" ".repeat(width - var.name.len()),
desc
);
if let Some(example) = var.example {
entry.push_str(&format!("\n {} {}", indent, example));
}
entry
})
.collect();
out.push_str(&entries.join("\n"));
out.push('\n');
out
}
pub fn render_root_help(cmd: &CommandDef) -> String {
render_help(cmd)
}
pub fn render_subcommand_help(root: &CommandDef, name: &str) -> Option<String> {
root.find_subcommand(name).map(render_help)
}