1pub struct CommandInfo<'a> {
11 pub name: &'a str,
13 pub short: &'a char,
15 pub description: &'a str,
17}
18
19impl<'a> Default for CommandInfo<'a> {
20 fn default() -> Self {
21 Self { name: Default::default(), short: &'\0', description: Default::default() }
22 }
23}
24
25#[derive(Debug, PartialEq, Eq, Clone)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28pub struct CommandInfoWithArgs<'a> {
29 pub name: &'a str,
31 pub short: &'a char,
33 pub description: &'a str,
35 pub examples: &'a [&'a str],
37 pub flags: &'a [FlagInfo<'a>],
39 pub notes: &'a [&'a str],
41 pub commands: Vec<SubCommandInfo<'a>>,
43 pub positionals: &'a [PositionalInfo<'a>],
45 pub error_codes: &'a [ErrorCodeInfo<'a>],
47}
48
49impl<'a> Default for CommandInfoWithArgs<'a> {
50 fn default() -> Self {
51 Self {
52 name: Default::default(),
53 short: &'\0',
54 description: Default::default(),
55 examples: Default::default(),
56 flags: Default::default(),
57 notes: Default::default(),
58 commands: Default::default(),
59 positionals: Default::default(),
60 error_codes: Default::default(),
61 }
62 }
63}
64
65#[derive(Debug, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68pub struct ErrorCodeInfo<'a> {
69 pub code: i32,
71 pub description: &'a str,
73}
74
75#[derive(Debug, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78pub struct PositionalInfo<'a> {
79 pub name: &'a str,
81 pub description: &'a str,
83 pub optionality: Optionality,
85 pub hidden: bool,
89}
90
91#[derive(Debug, Default, PartialEq, Eq, Clone)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97pub struct SubCommandInfo<'a> {
98 pub name: &'a str,
100 pub command: CommandInfoWithArgs<'a>,
102}
103
104#[derive(Debug, Default, PartialEq, Eq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107pub struct FlagInfo<'a> {
108 pub kind: FlagInfoKind<'a>,
110 pub optionality: Optionality,
112 pub long: &'a str,
114 pub short: Option<char>,
117 pub description: &'a str,
119 pub hidden: bool,
123}
124
125#[derive(Debug, Default, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize))]
128pub enum FlagInfoKind<'a> {
129 #[default]
131 Switch,
132 Option { arg_name: &'a str },
135}
136
137#[derive(Debug, Default, PartialEq, Eq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141pub enum Optionality {
142 #[default]
145 Required,
146 Optional,
149 Repeating,
152 Greedy,
156}
157
158pub const INDENT: &str = " ";
159const DESCRIPTION_INDENT: usize = 20;
160const WRAP_WIDTH: usize = 80;
161
162pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {
164 let mut current_line = INDENT.to_string();
165 current_line.push_str(cmd.name);
166
167 if *cmd.short != '\0' {
168 current_line.push_str(&format!(" {}", cmd.short));
169 }
170
171 if cmd.description.is_empty() {
172 new_line(&mut current_line, out);
173 return;
174 }
175
176 if !indent_description(&mut current_line) {
177 new_line(&mut current_line, out);
180 }
181
182 let mut words = cmd.description.split(' ').peekable();
183 while let Some(first_word) = words.next() {
184 indent_description(&mut current_line);
185 current_line.push_str(first_word);
186
187 'inner: while let Some(&word) = words.peek() {
188 if (char_len(¤t_line) + char_len(word) + 1) > WRAP_WIDTH {
189 new_line(&mut current_line, out);
190 break 'inner;
191 } else {
192 let _ = words.next();
194 current_line.push(' ');
195 current_line.push_str(word);
196 }
197 }
198 }
199 new_line(&mut current_line, out);
200}
201
202fn indent_description(line: &mut String) -> bool {
205 let cur_len = char_len(line);
206 if cur_len < DESCRIPTION_INDENT {
207 let num_spaces = DESCRIPTION_INDENT - cur_len;
208 line.extend(std::iter::repeat_n(' ', num_spaces));
209 true
210 } else {
211 false
212 }
213}
214
215fn char_len(s: &str) -> usize {
216 s.chars().count()
217}
218
219fn new_line(current_line: &mut String, out: &mut String) {
222 out.push('\n');
223 out.push_str(current_line);
224 current_line.truncate(0);
225}