pub struct CommandInfo<'a> {
pub name: &'a str,
pub short: &'a char,
pub description: &'a str,
}
impl<'a> Default for CommandInfo<'a> {
fn default() -> Self {
Self { name: Default::default(), short: &'\0', description: Default::default() }
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CommandInfoWithArgs<'a> {
pub name: &'a str,
pub short: &'a char,
pub description: &'a str,
pub examples: &'a [&'a str],
pub flags: &'a [FlagInfo<'a>],
pub notes: &'a [&'a str],
pub commands: Vec<SubCommandInfo<'a>>,
pub positionals: &'a [PositionalInfo<'a>],
pub error_codes: &'a [ErrorCodeInfo<'a>],
}
impl<'a> Default for CommandInfoWithArgs<'a> {
fn default() -> Self {
Self {
name: Default::default(),
short: &'\0',
description: Default::default(),
examples: Default::default(),
flags: Default::default(),
notes: Default::default(),
commands: Default::default(),
positionals: Default::default(),
error_codes: Default::default(),
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ErrorCodeInfo<'a> {
pub code: i32,
pub description: &'a str,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PositionalInfo<'a> {
pub name: &'a str,
pub description: &'a str,
pub optionality: Optionality,
pub hidden: bool,
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SubCommandInfo<'a> {
pub name: &'a str,
pub command: CommandInfoWithArgs<'a>,
}
#[derive(Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FlagInfo<'a> {
pub kind: FlagInfoKind<'a>,
pub optionality: Optionality,
pub long: &'a str,
pub short: Option<char>,
pub description: &'a str,
pub hidden: bool,
}
#[derive(Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FlagInfoKind<'a> {
#[default]
Switch,
Option { arg_name: &'a str },
}
#[derive(Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Optionality {
#[default]
Required,
Optional,
Repeating,
Greedy,
}
pub const INDENT: &str = " ";
const DESCRIPTION_INDENT: usize = 20;
const WRAP_WIDTH: usize = 80;
pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {
let mut current_line = INDENT.to_string();
current_line.push_str(cmd.name);
if *cmd.short != '\0' {
current_line.push_str(&format!(" {}", cmd.short));
}
if cmd.description.is_empty() {
new_line(&mut current_line, out);
return;
}
if !indent_description(&mut current_line) {
new_line(&mut current_line, out);
}
let mut words = cmd.description.split(' ').peekable();
while let Some(first_word) = words.next() {
indent_description(&mut current_line);
current_line.push_str(first_word);
'inner: while let Some(&word) = words.peek() {
if (char_len(¤t_line) + char_len(word) + 1) > WRAP_WIDTH {
new_line(&mut current_line, out);
break 'inner;
} else {
let _ = words.next();
current_line.push(' ');
current_line.push_str(word);
}
}
}
new_line(&mut current_line, out);
}
fn indent_description(line: &mut String) -> bool {
let cur_len = char_len(line);
if cur_len < DESCRIPTION_INDENT {
let num_spaces = DESCRIPTION_INDENT - cur_len;
line.extend(std::iter::repeat_n(' ', num_spaces));
true
} else {
false
}
}
fn char_len(s: &str) -> usize {
s.chars().count()
}
fn new_line(current_line: &mut String, out: &mut String) {
out.push('\n');
out.push_str(current_line);
current_line.truncate(0);
}