use crate::{ArgInfo, Error};
use core::cell::Cell;
pub use crate::input::{
Arg, Input, arg, bytes, opt, text, unexpected_argument, unknown_subcommand, unnamed,
};
#[cfg(feature = "std")]
pub use crate::os::{OsArg, args_os, os_str, path};
#[cfg(feature = "std")]
pub use std::ffi::OsStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HelpEntry {
pub label: &'static str,
pub usage: &'static str,
pub positional: bool,
pub hidden: bool,
pub global: bool,
pub heading: &'static str,
pub order: usize,
pub ordered: bool,
pub short: &'static str,
pub short_wraps: bool,
pub long: &'static str,
pub long_wraps: bool,
}
impl HelpEntry {
pub const EMPTY: HelpEntry = HelpEntry {
label: "",
usage: "",
positional: false,
hidden: true,
global: false,
heading: "",
order: 0,
ordered: false,
short: "",
short_wraps: true,
long: "",
long_wraps: true,
};
pub const fn reserved(label: &'static str, short: &'static str, long: &'static str) -> Self {
HelpEntry {
label,
short,
long,
hidden: false,
..HelpEntry::EMPTY
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Context<'a, 'r> {
pub raw: Option<&'r Cell<Option<&'a [u8]>>>,
pub last: bool,
pub negative_numbers: bool,
pub blocked: bool,
pub helps: &'static [&'static str],
pub node: usize,
}
impl<'a, 'r> Context<'a, 'r> {
pub const fn new(raw: Option<&'r Cell<Option<&'a [u8]>>>) -> Self {
Context {
raw,
last: false,
negative_numbers: false,
blocked: false,
helps: &[],
node: 0,
}
}
pub const fn with_helps(self, helps: &'static [&'static str]) -> Self {
Context { helps, ..self }
}
pub const fn child(self, last: bool, negative_numbers: bool, blocked: bool) -> Self {
Context {
last: self.last || last,
negative_numbers: self.negative_numbers || negative_numbers,
blocked: self.blocked || blocked,
..self
}
}
pub const fn command(self, node: usize) -> Self {
Context {
raw: self.raw,
last: false,
negative_numbers: false,
blocked: false,
helps: self.helps,
node,
}
}
pub fn help(&self, long: bool) -> &'static str {
let index = 2 * self.node + usize::from(long);
self.helps.get(index).copied().unwrap_or_default()
}
}
pub type Globals<'g, 'a, A, I> =
dyn FnMut(Arg<'a, A>, &mut getargs::Options<A, I>) -> Result<bool, Error<'a>> + 'g;
pub fn no_globals<'a, A: Input<'a>, I: Iterator<Item = A>>(
_argument: Arg<'a, A>,
_options: &mut getargs::Options<A, I>,
) -> Result<bool, Error<'a>> {
Ok(false)
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not derive `GetArgs`",
label = "flattened fields and newtype subcommand variants must wrap a type deriving `GetArgs`"
)]
pub trait Args<'a>: Sized {
type State;
const ENTRIES: &'static [HelpEntry];
const SHORTS: &'static [char];
const LONGS: &'static [&'static str];
const POSITIONALS: &'static [bool];
const LAST: bool;
type Env: EnvFlag;
const HAS_LONG_HELP: bool;
const ARGS: &'static [ArgInfo];
const COMMAND_ARGS: &'static [ArgInfo];
const COMMAND: CommandHelp;
fn init() -> Self::State;
fn filled(state: &Self::State) -> bool;
fn try_arg<A: Input<'a>, I: Iterator<Item = A>>(
state: &mut Self::State,
argument: Arg<'a, A>,
options: &mut getargs::Options<A, I>,
context: Context<'a, '_>,
globals: &mut Globals<'_, 'a, A, I>,
) -> Result<bool, Error<'a>>;
fn try_global<A: Input<'a>, I: Iterator<Item = A>>(
state: &mut Self::State,
argument: Arg<'a, A>,
options: &mut getargs::Options<A, I>,
context: Context<'a, '_>,
) -> Result<bool, Error<'a>>;
fn finish<E: FnMut(&str) -> Option<&'a str>>(
state: Self::State,
env: &mut E,
) -> Result<Self, Error<'a>>;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be used as a subcommand because it does not derive `Subcommand`",
label = "subcommand field type must derive `Subcommand`"
)]
pub trait Subcommand<'a>: Sized {
type State;
type Env: EnvFlag;
const COMMANDS: &'static [CommandEntry];
const NAMES: &'static [&'static str];
const VISIBLE: &'static [&'static str];
const INFOS: &'static [crate::CommandInfo];
fn try_command<A: Input<'a>, I: Iterator<Item = A>>(
name: &'a str,
options: &mut getargs::Options<A, I>,
context: Context<'a, '_>,
globals: &mut Globals<'_, 'a, A, I>,
) -> Result<Option<Self::State>, Error<'a>>;
fn finish<E: FnMut(&str) -> Option<&'a str>>(
state: Self::State,
env: &mut E,
) -> Result<Self, Error<'a>>;
}
#[derive(Debug, Clone, Copy)]
pub struct CommandHelp {
pub name: &'static str,
pub before_help: &'static str,
pub about: &'static str,
pub long_about: &'static str,
pub after_help: &'static str,
pub term_width: usize,
pub help_flag: bool,
pub version: bool,
pub entries: &'static [HelpEntry],
pub has_long_help: bool,
pub shorts: &'static [char],
pub longs: &'static [&'static str],
pub commands: &'static [CommandEntry],
pub subcommand_required: bool,
pub infos: &'static [crate::CommandInfo],
}
pub const fn subcommand_info(
name: &'static str,
about: &'static str,
long_about: &'static str,
args: &'static [ArgInfo],
command: &CommandHelp,
) -> crate::CommandInfo {
crate::CommandInfo::new(name)
.about(about)
.long_about(long_about)
.args(args)
.commands(command.infos)
.subcommand_required(command.subcommand_required)
}
#[derive(Debug, Clone, Copy)]
pub struct CommandEntry {
pub name: &'static str,
pub description: &'static str,
pub about: &'static str,
pub long_about: &'static str,
pub hidden: bool,
pub command: &'static CommandHelp,
}
pub const fn separator(about: &str, aliases: &str) -> &'static str {
if about.is_empty() || aliases.is_empty() {
""
} else {
" "
}
}
pub const fn nodes(command: &CommandHelp) -> usize {
let mut total = 1;
let mut index = 0;
while index < command.commands.len() {
total += nodes(command.commands[index].command);
index += 1;
}
total
}
pub const fn node(commands: &[CommandEntry], parent: usize, index: usize) -> usize {
let mut node = parent + 1;
let mut before = 0;
while before < index {
node += nodes(commands[before].command);
before += 1;
}
node
}
pub const fn commands_use_short(commands: &[CommandEntry], short: char) -> bool {
let mut index = 0;
while index < commands.len() {
let command = commands[index].command;
if contains_short(command.shorts, short)
|| (command.help_flag && short == 'h')
|| commands_use_short(command.commands, short)
{
return true;
}
index += 1;
}
false
}
pub const fn commands_use_long(commands: &[CommandEntry], long: &str) -> bool {
let mut index = 0;
while index < commands.len() {
let command = commands[index].command;
if contains_long(command.longs, long)
|| (command.help_flag && str_eq(long, "help"))
|| commands_use_long(command.commands, long)
{
return true;
}
index += 1;
}
false
}
pub fn next_arg<'a, A: Input<'a>, I: Iterator<Item = A>>(
options: &mut getargs::Options<A, I>,
) -> Result<Option<Arg<'a, A>>, Error<'a>> {
match options.next_arg() {
Ok(Some(argument)) => arg(argument).map(Some),
Ok(None) => Ok(None),
Err(getargs::Error::DoesNotRequireValue(option)) => {
Err(Error::UnexpectedValue(opt(option)?))
}
Err(_) => Err(Error::InvalidArguments),
}
}
pub fn unexpected<'a, A: Input<'a>>(argument: Arg<'a, A>, longs: &[&[&'static str]]) -> Error<'a> {
match argument {
Arg::Positional(value) => unexpected_argument(value),
Arg::Short(short) => Error::UnknownOption {
option: crate::Opt::Short(short),
suggestion: None,
},
Arg::Long(long) => Error::UnknownOption {
option: crate::Opt::Long(long),
suggestion: crate::suggest::suggest(long, longs),
},
}
}
pub fn command<'a, T: Args<'a>, A: Input<'a>, I: Iterator<Item = A>>(
options: &mut getargs::Options<A, I>,
context: Context<'a, '_>,
globals: &mut Globals<'_, 'a, A, I>,
) -> Result<T::State, Error<'a>> {
let mut state = T::init();
while let Some(argument) = next_arg(options)? {
if T::COMMAND.help_flag {
match argument {
Arg::Short('h') => return Err(Error::Help(context.help(false))),
Arg::Long("help") => return Err(Error::Help(context.help(true))),
_ => {}
}
}
if T::try_arg(&mut state, argument, options, context, globals)? {
continue;
}
if globals(argument, options)? {
continue;
}
let reserved: &[&'static str] = if T::COMMAND.help_flag { &["help"] } else { &[] };
return Err(unexpected(argument, &[T::LONGS, reserved]));
}
Ok(state)
}
pub trait EnvFlag {
type Or<Other: EnvFlag>: EnvFlag;
}
pub struct UsesEnv;
pub struct NoEnv;
impl EnvFlag for UsesEnv {
type Or<Other: EnvFlag> = UsesEnv;
}
impl EnvFlag for NoEnv {
type Or<Other: EnvFlag> = Other;
}
#[diagnostic::on_unimplemented(
message = "a flattened struct or a subcommand reads environment variables, which requires the `std` feature of `getargs-derive`",
label = "`parse` and `from_args` read the process environment",
note = "enable the `std` feature of `getargs-derive`, or call `parse_with_env` or `from_args_with_env` with a lookup function"
)]
pub trait EnvSupport<F> {}
impl<T: ?Sized> EnvSupport<NoEnv> for T {}
#[cfg(feature = "std")]
impl<T: ?Sized> EnvSupport<UsesEnv> for T {}
pub fn no_env<'a>(_name: &str) -> Option<&'a str> {
None
}
pub const fn help_flag(long: bool, has_long_help: bool) -> &'static str {
match (long, has_long_help) {
(_, false) => "Print help",
(false, true) => "Print help (see more with '--help')",
(true, true) => "Print help (see a summary with '-h')",
}
}
pub const fn count<T>(parts: &[&[T]]) -> usize {
let mut total = 0;
let mut index = 0;
while index < parts.len() {
total += parts[index].len();
index += 1;
}
total
}
pub const fn concat<T: Copy, const N: usize>(parts: &[&[T]], fill: T) -> [T; N] {
let mut output = [fill; N];
let mut position = 0;
let mut part = 0;
while part < parts.len() {
let mut index = 0;
while index < parts[part].len() {
output[position] = parts[part][index];
position += 1;
index += 1;
}
part += 1;
}
assert!(position == N, "concatenated length mismatch");
output
}
const fn str_eq(first: &str, second: &str) -> bool {
let first = first.as_bytes();
let second = second.as_bytes();
if first.len() != second.len() {
return false;
}
let mut index = 0;
while index < first.len() {
if first[index] != second[index] {
return false;
}
index += 1;
}
true
}
pub const fn help_arg(has_long_help: bool) -> ArgInfo {
ArgInfo::new("help", crate::ArgKind::Flag)
.short('h')
.long("help")
.help(help_flag(false, has_long_help))
.long_help(help_flag(true, has_long_help))
}
pub const fn version_arg() -> ArgInfo {
ArgInfo::new("version", crate::ArgKind::Flag)
.short('V')
.long("version")
.help("Print version")
}
pub const fn contains_short(list: &[char], short: char) -> bool {
let mut index = 0;
while index < list.len() {
if list[index] == short {
return true;
}
index += 1;
}
false
}
pub const fn contains_long(list: &[&str], long: &str) -> bool {
let mut index = 0;
while index < list.len() {
if str_eq(list[index], long) {
return true;
}
index += 1;
}
false
}
pub const fn shares_short(first: &[char], second: &[char]) -> bool {
let mut index = 0;
while index < first.len() {
if contains_short(second, first[index]) {
return true;
}
index += 1;
}
false
}
pub const fn shares_long(first: &[&str], second: &[&str]) -> bool {
let mut index = 0;
while index < first.len() {
if contains_long(second, first[index]) {
return true;
}
index += 1;
}
false
}
pub const fn positionals_ordered(parts: &[&[bool]]) -> bool {
let mut seen_optional = false;
let mut part = 0;
while part < parts.len() {
let mut index = 0;
while index < parts[part].len() {
if parts[part][index] {
seen_optional = true;
} else if seen_optional {
return false;
}
index += 1;
}
part += 1;
}
true
}
const ARGUMENTS: &str = "Arguments";
const OPTIONS: &str = "Options";
const NEXT_LINE_INDENT: usize = 10;
#[derive(Debug, Clone, Copy)]
pub struct Help<'h> {
pub prefix: &'h str,
pub suffix: &'h str,
pub entries: &'h [HelpEntry],
pub reserved: &'h [HelpEntry],
pub long: bool,
pub term_width: usize,
}
const fn decode(bytes: &[u8], index: usize) -> (u32, usize) {
let first = bytes[index] as u32;
if first < 0x80 {
(first, 1)
} else if first < 0xE0 {
(((first & 0x1F) << 6) | (bytes[index + 1] as u32 & 0x3F), 2)
} else if first < 0xF0 {
(
((first & 0x0F) << 12)
| ((bytes[index + 1] as u32 & 0x3F) << 6)
| (bytes[index + 2] as u32 & 0x3F),
3,
)
} else {
(
((first & 0x07) << 18)
| ((bytes[index + 1] as u32 & 0x3F) << 12)
| ((bytes[index + 2] as u32 & 0x3F) << 6)
| (bytes[index + 3] as u32 & 0x3F),
4,
)
}
}
const fn is_whitespace(code: u32) -> bool {
matches!(
code,
0x09..=0x0D
| 0x20
| 0x85
| 0xA0
| 0x1680
| 0x2000..=0x200A
| 0x2028
| 0x2029
| 0x202F
| 0x205F
| 0x3000
)
}
const fn char_count_range(text: &str, start: usize, end: usize) -> usize {
let bytes = text.as_bytes();
let mut count = 0;
let mut index = start;
while index < end {
if bytes[index] & 0xC0 != 0x80 {
count += 1;
}
index += 1;
}
count
}
const fn char_count(text: &str) -> usize {
char_count_range(text, 0, text.len())
}
const fn line_end(text: &str, start: usize) -> usize {
let bytes = text.as_bytes();
let mut index = start;
while index < bytes.len() && bytes[index] != b'\n' {
index += 1;
}
index
}
const fn text_width(text: &str) -> usize {
let bytes = text.as_bytes();
let mut width = 0;
let mut start = 0;
while start < bytes.len() {
let end = line_end(text, start);
let mut visible = end;
if visible > start && bytes[visible - 1] == b'\r' {
visible -= 1;
}
let count = char_count_range(text, start, visible);
if count > width {
width = count;
}
start = end + 1;
}
width
}
struct Writer<'a> {
output: &'a mut [u8],
position: usize,
marks: &'a mut [usize],
mark: usize,
}
impl Writer<'_> {
const fn range(&mut self, text: &str, start: usize, end: usize) {
let bytes = text.as_bytes();
let mut index = start;
while index < end {
if self.position < self.output.len() {
self.output[self.position] = bytes[index];
}
self.position += 1;
index += 1;
}
}
const fn str(&mut self, text: &str) {
self.range(text, 0, text.len());
}
const fn spaces(&mut self, count: usize) {
let mut index = 0;
while index < count {
self.str(" ");
index += 1;
}
}
const fn wrap_line(
&mut self,
text: &str,
start: usize,
end: usize,
width: usize,
indent: usize,
) {
let bytes = text.as_bytes();
let mut body = start;
while body < end {
let (code, length) = decode(bytes, body);
if !is_whitespace(code) {
break;
}
body += length;
}
self.range(text, start, body);
let mut current = char_count_range(text, start, body);
let mut line_start = true;
let mut index = body;
while index < end {
let (code, length) = decode(bytes, index);
if is_whitespace(code) {
index += length;
continue;
}
let word = index;
while index < end {
let (code, length) = decode(bytes, index);
if is_whitespace(code) {
break;
}
index += length;
}
let length = char_count_range(text, word, index);
if !line_start && current + 1 + length > width {
self.str("\n");
self.spaces(indent);
current = 0;
} else if !line_start {
self.str(" ");
current += 1;
}
self.range(text, word, index);
current += length;
line_start = false;
}
}
const fn paragraph(&mut self, text: &str, width: Option<usize>, indent: usize) {
let bytes = text.as_bytes();
let mut start = 0;
loop {
let end = line_end(text, start);
if start > 0 {
self.str("\n");
if end > start {
self.spaces(indent);
}
}
match width {
Some(width) => {
let width = if width == 0 { 1 } else { width };
if char_count_range(text, start, end) <= width {
self.range(text, start, end);
} else {
self.wrap_line(text, start, end, width, indent);
}
}
None => self.range(text, start, end),
}
if end >= bytes.len() {
break;
}
start = end + 1;
}
}
const fn mark(&mut self) {
let position = self.position;
self.mark_at(position);
}
const fn mark_at(&mut self, position: usize) {
if self.mark < self.marks.len() {
self.marks[self.mark] = position;
}
self.mark += 1;
}
#[allow(clippy::too_many_arguments)]
const fn item(
&mut self,
label: &str,
description: &str,
wraps: bool,
long: bool,
term_width: usize,
width: usize,
) {
self.str("\n ");
if description.is_empty() {
self.str(label);
return;
}
let taken = width + 4;
let next_line = if !wraps || long || term_width == 0 {
long
} else {
term_width >= taken
&& taken * 5 > term_width * 2
&& text_width(description) > term_width - taken
};
let wrapped = wraps && term_width > 0;
if next_line {
self.str(label);
self.str("\n");
self.spaces(NEXT_LINE_INDENT);
let available = term_width.saturating_sub(NEXT_LINE_INDENT);
self.paragraph(
description,
if wrapped { Some(available) } else { None },
NEXT_LINE_INDENT,
);
} else {
self.str(label);
self.spaces(width - char_count(label) + 2);
let available = term_width.saturating_sub(taken);
self.paragraph(
description,
if wrapped { Some(available) } else { None },
taken,
);
}
}
const fn entry(&mut self, page: &Page, entry: &HelpEntry, width: usize) {
let (description, wraps) = if page.long {
(entry.long, entry.long_wraps)
} else {
(entry.short, entry.short_wraps)
};
self.item(
entry.label,
description,
wraps,
page.long,
page.term_width,
width,
);
}
const fn section(&mut self, page: &Page, title: &str) {
let count = chain_len(page.entries);
let options = str_eq(title, OPTIONS);
let mut width = 0;
let mut found = false;
let mut index = 0;
while index < count {
if let Some(entry) = shown(page.entries, index)
&& visible_in(entry, title)
{
found = true;
let length = char_count(entry.label);
if length > width {
width = length;
}
}
index += 1;
}
if options {
index = 0;
while index < page.reserved.len() {
found = true;
let length = char_count(page.reserved[index].label);
if length > width {
width = length;
}
index += 1;
}
}
if !found {
return;
}
self.str("\n\n");
self.str(title);
self.str(":");
let mut written = 0;
let mut previous: Option<(usize, usize)> = None;
loop {
let mut next: Option<(usize, usize)> = None;
index = 0;
while index < count {
if let Some(entry) = shown(page.entries, index)
&& visible_in(entry, title)
{
let key = (order(entry, index), index);
let after = match previous {
Some(previous) => greater(key, previous),
None => true,
};
let better = match next {
Some(next) => greater(next, key),
None => true,
};
if after && better {
next = Some(key);
}
}
index += 1;
}
let Some(key) = next else {
break;
};
if written > 0 && page.long {
self.str("\n");
}
let (entry, _) = chain_get(page.entries, key.1);
self.entry(page, entry, width);
written += 1;
previous = Some(key);
}
if options {
index = 0;
while index < page.reserved.len() {
if written > 0 && page.long {
self.str("\n");
}
self.entry(page, &page.reserved[index], width);
written += 1;
index += 1;
}
}
}
const fn commands(&mut self, page: &Page) {
let commands = page.commands;
let mut width = 0;
let mut found = false;
let mut index = 0;
while index < commands.len() {
if !commands[index].hidden {
found = true;
let length = char_count(commands[index].name);
if length > width {
width = length;
}
}
index += 1;
}
if !found {
return;
}
self.str("\n\nCommands:");
index = 0;
while index < commands.len() {
if !commands[index].hidden {
self.item(
commands[index].name,
commands[index].description,
true,
false,
page.term_width,
width,
);
}
index += 1;
}
}
const fn body(&mut self, page: &Page) {
let count = chain_len(page.entries);
let mut options = !page.reserved.is_empty();
let mut index = 0;
while index < count {
if let Some(entry) = shown(page.entries, index)
&& !entry.positional
{
options = true;
}
index += 1;
}
if options {
self.str(" [OPTIONS]");
}
index = 0;
while index < count {
if let Some(entry) = shown(page.entries, index)
&& entry.positional
{
self.str(" ");
self.str(entry.usage);
}
index += 1;
}
if !page.commands.is_empty() {
self.str(if page.subcommand_required {
" <COMMAND>"
} else {
" [COMMAND]"
});
}
self.commands(page);
self.section(page, ARGUMENTS);
self.section(page, OPTIONS);
index = 0;
while index < count {
if let Some(entry) = shown(page.entries, index) {
let heading = title(entry);
if !str_eq(heading, ARGUMENTS) && !str_eq(heading, OPTIONS) {
let mut seen = false;
let mut before = 0;
while before < index {
if let Some(earlier) = shown(page.entries, before)
&& str_eq(title(earlier), heading)
{
seen = true;
}
before += 1;
}
if !seen {
self.section(page, heading);
}
}
}
index += 1;
}
}
const fn path(&mut self, path: &Path) {
if let Some(parent) = path.parent {
self.path(parent);
self.str(" ");
}
self.str(path.name);
}
const fn text(&mut self, text: &str, term_width: usize) {
self.paragraph(
text,
if term_width == 0 {
None
} else {
Some(term_width)
},
0,
);
}
const fn page(&mut self, node: &Node, entries: &Chain, long: bool) {
let command = node.command;
let term_width = command.term_width;
if !command.before_help.is_empty() {
self.text(command.before_help, term_width);
self.str("\n\n");
}
let about = if long && !node.long_about.is_empty() {
node.long_about
} else {
node.about
};
if !about.is_empty() {
self.text(about, term_width);
self.str("\n\n");
}
self.str("Usage: ");
self.path(node.path);
let has_long = node.has_long();
let reserved = [
HelpEntry::reserved(
"-h, --help",
help_flag(false, has_long),
help_flag(true, has_long),
),
HelpEntry::reserved("-V, --version", "Print version", "Print version"),
];
let (reserved, _) = match (command.help_flag, node.root && command.version) {
(true, true) => reserved.split_at(2),
(true, false) => reserved.split_at(1),
(false, true) => reserved.split_at(1).1.split_at(1),
(false, false) => reserved.split_at(0),
};
self.body(&Page {
entries,
reserved,
commands: command.commands,
subcommand_required: command.subcommand_required,
long,
term_width,
});
if !command.after_help.is_empty() {
self.str("\n\n");
self.text(command.after_help, term_width);
}
}
const fn tree(&mut self, node: &Node, parent: Option<&Chain>) {
let command = node.command;
let entries = Chain {
entries: command.entries,
all: true,
parent,
};
let start = self.position;
self.mark();
self.page(node, &entries, false);
let end = self.position;
self.mark();
if node.has_long() {
self.mark();
self.page(node, &entries, true);
self.mark();
} else {
self.mark_at(start);
self.mark_at(end);
}
let globals = Chain {
entries: command.entries,
all: false,
parent,
};
let mut index = 0;
while index < command.commands.len() {
let entry = &command.commands[index];
let path = Path {
name: entry.name,
parent: Some(node.path),
};
self.tree(
&Node {
command: entry.command,
about: entry.about,
long_about: entry.long_about,
path: &path,
root: false,
},
Some(&globals),
);
index += 1;
}
}
}
struct Page<'p> {
entries: &'p Chain<'p>,
reserved: &'p [HelpEntry],
commands: &'p [CommandEntry],
subcommand_required: bool,
long: bool,
term_width: usize,
}
struct Chain<'c> {
entries: &'c [HelpEntry],
all: bool,
parent: Option<&'c Chain<'c>>,
}
struct Path<'p> {
name: &'p str,
parent: Option<&'p Path<'p>>,
}
struct Node<'n> {
command: &'n CommandHelp,
about: &'n str,
long_about: &'n str,
path: &'n Path<'n>,
root: bool,
}
impl Node<'_> {
const fn has_long(&self) -> bool {
!self.long_about.is_empty() || self.command.has_long_help
}
}
const fn chain_len(chain: &Chain) -> usize {
let mut total = 0;
let mut current = Some(chain);
while let Some(link) = current {
total += link.entries.len();
current = link.parent;
}
total
}
const fn chain_get<'c>(chain: &Chain<'c>, index: usize) -> (&'c HelpEntry, bool) {
let mut index = index;
let mut entries = chain.entries;
let mut all = chain.all;
let mut parent = chain.parent;
loop {
if index < entries.len() {
return (&entries[index], all || entries[index].global);
}
index -= entries.len();
match parent {
Some(link) => {
entries = link.entries;
all = link.all;
parent = link.parent;
}
None => panic!("help entry index out of range"),
}
}
}
const fn shown<'c>(chain: &Chain<'c>, index: usize) -> Option<&'c HelpEntry> {
let (entry, included) = chain_get(chain, index);
if included && !entry.hidden {
Some(entry)
} else {
None
}
}
const fn greater(first: (usize, usize), second: (usize, usize)) -> bool {
first.0 > second.0 || (first.0 == second.0 && first.1 > second.1)
}
const fn order(entry: &HelpEntry, index: usize) -> usize {
if entry.ordered { entry.order } else { index }
}
const fn title(entry: &HelpEntry) -> &str {
if !entry.heading.is_empty() {
entry.heading
} else if entry.positional {
ARGUMENTS
} else {
OPTIONS
}
}
const fn visible_in(entry: &HelpEntry, section: &str) -> bool {
str_eq(title(entry), section)
}
const fn render(output: &mut [u8], help: &Help) -> usize {
let mut writer = Writer {
output,
position: 0,
marks: &mut [],
mark: 0,
};
writer.str(help.prefix);
writer.body(&Page {
entries: &Chain {
entries: help.entries,
all: true,
parent: None,
},
reserved: help.reserved,
commands: &[],
subcommand_required: false,
long: help.long,
term_width: help.term_width,
});
writer.str(help.suffix);
writer.position
}
pub const fn help_len(help: &Help) -> usize {
render(&mut [], help)
}
pub const fn help<const N: usize>(help: &Help) -> [u8; N] {
let mut output = [0; N];
let length = render(&mut output, help);
assert!(length == N, "help length mismatch");
output
}
const fn render_tree(output: &mut [u8], marks: &mut [usize], root: &CommandHelp) -> usize {
let mut writer = Writer {
output,
position: 0,
marks,
mark: 0,
};
let path = Path {
name: root.name,
parent: None,
};
writer.tree(
&Node {
command: root,
about: root.about,
long_about: root.long_about,
path: &path,
root: true,
},
None,
);
writer.position
}
pub const fn tree_len(root: &CommandHelp) -> usize {
render_tree(&mut [], &mut [], root)
}
pub const fn tree<const N: usize>(root: &CommandHelp) -> [u8; N] {
let mut output = [0; N];
let length = render_tree(&mut output, &mut [], root);
assert!(length == N, "help length mismatch");
output
}
pub const fn tree_marks<const N: usize>(root: &CommandHelp) -> [usize; N] {
let mut marks = [0; N];
render_tree(&mut [], &mut marks, root);
marks
}
pub const fn tree_helps<const N: usize>(
bytes: &'static [u8],
marks: &[usize],
) -> [&'static str; N] {
let mut helps = [""; N];
let mut index = 0;
while index < N {
let start = marks[2 * index];
let end = marks[2 * index + 1];
let (_, rest) = bytes.split_at(start);
let (text, _) = rest.split_at(end - start);
helps[index] = utf8(text);
index += 1;
}
helps
}
pub const fn utf8(bytes: &[u8]) -> &str {
match core::str::from_utf8(bytes) {
Ok(text) => text,
Err(_) => panic!("help text is not valid UTF-8"),
}
}