Skip to content

Commit 85bdb1d

Browse files
committed
Prototype out a help trait
1 parent b2854ad commit 85bdb1d

File tree

3 files changed

+495
-4
lines changed

3 files changed

+495
-4
lines changed

argh/src/help.rs

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// Copyright (c) 2022 Google LLC All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//! TODO
6+
7+
use {
8+
argh_shared::{write_description, CommandInfo, INDENT},
9+
std::fmt,
10+
};
11+
12+
const SECTION_SEPARATOR: &str = "\n\n";
13+
14+
const HELP_FLAG: HelpFlagInfo = HelpFlagInfo {
15+
short: None,
16+
long: "--help",
17+
description: "display usage information",
18+
optionality: HelpOptionality::Optional,
19+
kind: HelpFieldKind::Switch,
20+
};
21+
22+
/// TODO
23+
pub trait Help {
24+
/// TODO
25+
const HELP_INFO: HelpInfo<'static>;
26+
}
27+
28+
/// TODO
29+
pub trait HelpSubCommands {
30+
/// TODO
31+
const HELP_INFO: HelpSubCommandsInfo<'static>;
32+
}
33+
34+
/// TODO
35+
pub trait HelpSubCommand {
36+
/// TODO
37+
const HELP_INFO: HelpSubCommandInfo<'static>;
38+
}
39+
40+
impl<T: HelpSubCommand> HelpSubCommands for T {
41+
/// TODO
42+
const HELP_INFO: HelpSubCommandsInfo<'static> =
43+
HelpSubCommandsInfo { optional: false, commands: &[<T as HelpSubCommand>::HELP_INFO] };
44+
}
45+
46+
/// TODO
47+
pub struct HelpInfo<'a> {
48+
/// TODO
49+
pub description: &'a str,
50+
/// TODO
51+
pub examples: &'a [fn(&[&str]) -> String],
52+
/// TODO
53+
pub notes: &'a [fn(&[&str]) -> String],
54+
/// TODO
55+
pub flags: &'a [HelpFlagInfo<'a>],
56+
/// TODO
57+
pub positionals: &'a [HelpPositionalInfo<'a>],
58+
/// TODO
59+
pub subcommand: Option<HelpSubCommandsInfo<'a>>,
60+
/// TODO
61+
pub error_codes: &'a [(isize, &'a str)],
62+
}
63+
64+
fn help_section(
65+
out: &mut String,
66+
command_name: &[&str],
67+
heading: &str,
68+
sections: &[fn(&[&str]) -> String],
69+
) {
70+
if !sections.is_empty() {
71+
out.push_str(SECTION_SEPARATOR);
72+
for section_fn in sections {
73+
let section = section_fn(command_name);
74+
75+
out.push_str(heading);
76+
for line in section.split('\n') {
77+
out.push('\n');
78+
out.push_str(INDENT);
79+
out.push_str(line);
80+
}
81+
}
82+
}
83+
}
84+
85+
impl<'a> HelpInfo<'a> {
86+
/// TODO
87+
pub fn help(&self, command_name: &[&str]) -> String {
88+
let mut out = format!("Usage: {}", command_name.join(" "));
89+
90+
for positional in self.positionals {
91+
out.push(' ');
92+
positional.help_usage(&mut out);
93+
}
94+
95+
for flag in self.flags {
96+
out.push(' ');
97+
flag.help_usage(&mut out);
98+
}
99+
100+
if let Some(subcommand) = &self.subcommand {
101+
out.push(' ');
102+
if subcommand.optional {
103+
out.push('[');
104+
}
105+
out.push_str("<command>");
106+
if subcommand.optional {
107+
out.push(']');
108+
}
109+
out.push_str(" [<args>]");
110+
}
111+
112+
out.push_str(SECTION_SEPARATOR);
113+
114+
out.push_str(self.description);
115+
116+
if !self.positionals.is_empty() {
117+
out.push_str(SECTION_SEPARATOR);
118+
out.push_str("Positional Arguments:");
119+
for positional in self.positionals {
120+
positional.help_description(&mut out);
121+
}
122+
}
123+
124+
out.push_str(SECTION_SEPARATOR);
125+
out.push_str("Options:");
126+
for flag in self.flags {
127+
flag.help_description(&mut out);
128+
}
129+
130+
// Also include "help"
131+
HELP_FLAG.help_description(&mut out);
132+
133+
if let Some(subcommand) = &self.subcommand {
134+
out.push_str(SECTION_SEPARATOR);
135+
out.push_str("Commands:");
136+
for cmd in subcommand.commands {
137+
let info = CommandInfo { name: cmd.name, description: cmd.info.description };
138+
write_description(&mut out, &info);
139+
}
140+
}
141+
142+
help_section(&mut out, command_name, "Examples:", self.examples);
143+
144+
help_section(&mut out, command_name, "Notes:", self.notes);
145+
146+
if !self.error_codes.is_empty() {
147+
out.push_str(SECTION_SEPARATOR);
148+
out.push_str("Error codes:");
149+
write_error_codes(&mut out, self.error_codes);
150+
}
151+
152+
out.push('\n');
153+
154+
out
155+
}
156+
}
157+
158+
fn write_error_codes(out: &mut String, error_codes: &[(isize, &str)]) {
159+
for (code, text) in error_codes {
160+
out.push('\n');
161+
out.push_str(INDENT);
162+
out.push_str(&format!("{} {}", code, text));
163+
}
164+
}
165+
166+
impl<'a> fmt::Debug for HelpInfo<'a> {
167+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168+
let examples = self.examples.iter().map(|f| f(&["{command_name}"])).collect::<Vec<_>>();
169+
let notes = self.notes.iter().map(|f| f(&["{command_name}"])).collect::<Vec<_>>();
170+
f.debug_struct("HelpInfo")
171+
.field("description", &self.description)
172+
.field("examples", &examples)
173+
.field("notes", &notes)
174+
.field("flags", &self.flags)
175+
.field("positionals", &self.positionals)
176+
.field("subcommand", &self.subcommand)
177+
.field("error_codes", &self.error_codes)
178+
.finish()
179+
}
180+
}
181+
182+
/// TODO
183+
#[derive(Debug)]
184+
pub struct HelpSubCommandsInfo<'a> {
185+
/// TODO
186+
pub optional: bool,
187+
/// TODO
188+
pub commands: &'a [HelpSubCommandInfo<'a>],
189+
}
190+
191+
/// TODO
192+
#[derive(Debug)]
193+
pub struct HelpSubCommandInfo<'a> {
194+
/// TODO
195+
pub name: &'a str,
196+
/// TODO
197+
pub info: HelpInfo<'a>,
198+
}
199+
200+
/// TODO
201+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
202+
pub enum HelpOptionality {
203+
/// TODO
204+
None,
205+
/// TODO
206+
Optional,
207+
/// TODO
208+
Repeating,
209+
}
210+
211+
impl HelpOptionality {
212+
/// TODO
213+
fn is_required(&self) -> bool {
214+
matches!(self, HelpOptionality::None)
215+
}
216+
}
217+
218+
/// TODO
219+
#[derive(Debug)]
220+
pub struct HelpPositionalInfo<'a> {
221+
/// TODO
222+
pub name: &'a str,
223+
/// TODO
224+
pub description: &'a str,
225+
/// TODO
226+
pub optionality: HelpOptionality,
227+
}
228+
229+
impl<'a> HelpPositionalInfo<'a> {
230+
/// TODO
231+
pub fn help_usage(&self, out: &mut String) {
232+
if !self.optionality.is_required() {
233+
out.push('[');
234+
}
235+
236+
out.push('<');
237+
out.push_str(self.name);
238+
239+
if self.optionality == HelpOptionality::Repeating {
240+
out.push_str("...");
241+
}
242+
243+
out.push('>');
244+
245+
if !self.optionality.is_required() {
246+
out.push(']');
247+
}
248+
}
249+
250+
/// TODO
251+
pub fn help_description(&self, out: &mut String) {
252+
let info = CommandInfo { name: self.name, description: self.description };
253+
write_description(out, &info);
254+
}
255+
}
256+
257+
/// TODO
258+
#[derive(Debug)]
259+
pub struct HelpFlagInfo<'a> {
260+
/// TODO
261+
pub short: Option<char>,
262+
/// TODO
263+
pub long: &'a str,
264+
/// TODO
265+
pub description: &'a str,
266+
/// TODO
267+
pub optionality: HelpOptionality,
268+
/// TODO
269+
pub kind: HelpFieldKind<'a>,
270+
}
271+
272+
/// TODO
273+
#[derive(Debug)]
274+
pub enum HelpFieldKind<'a> {
275+
/// TODO
276+
Switch,
277+
/// TODO
278+
Option {
279+
/// TODO
280+
arg_name: &'a str,
281+
},
282+
}
283+
284+
impl<'a> HelpFlagInfo<'a> {
285+
/// TODO
286+
pub fn help_usage(&self, out: &mut String) {
287+
if !self.optionality.is_required() {
288+
out.push('[');
289+
}
290+
291+
if let Some(short) = self.short {
292+
out.push('-');
293+
out.push(short);
294+
} else {
295+
out.push_str(self.long);
296+
}
297+
298+
match self.kind {
299+
HelpFieldKind::Switch => {}
300+
HelpFieldKind::Option { arg_name } => {
301+
out.push_str(" <");
302+
out.push_str(arg_name);
303+
304+
if self.optionality == HelpOptionality::Repeating {
305+
out.push_str("...");
306+
}
307+
308+
out.push('>');
309+
}
310+
}
311+
312+
if !self.optionality.is_required() {
313+
out.push(']');
314+
}
315+
}
316+
317+
/// TODO
318+
pub fn help_description(&self, out: &mut String) {
319+
let mut name = String::new();
320+
if let Some(short) = self.short {
321+
name.push('-');
322+
name.push(short);
323+
name.push_str(", ");
324+
}
325+
name.push_str(self.long);
326+
327+
let info = CommandInfo { name: &name, description: self.description };
328+
write_description(out, &info);
329+
}
330+
}

argh/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,15 @@
171171

172172
use std::str::FromStr;
173173

174-
pub use argh_derive::FromArgs;
174+
mod help;
175+
176+
pub use {
177+
crate::help::{
178+
Help, HelpFieldKind, HelpFlagInfo, HelpInfo, HelpOptionality, HelpPositionalInfo,
179+
HelpSubCommand, HelpSubCommandInfo, HelpSubCommands, HelpSubCommandsInfo,
180+
},
181+
argh_derive::FromArgs,
182+
};
175183

176184
/// Information about a particular command used for output.
177185
pub type CommandInfo = argh_shared::CommandInfo<'static>;

0 commit comments

Comments
 (0)