Merge pull request #5204 from epage/usage-refactor

refactor(help): Clean up the usage code
This commit is contained in:
Ed Page 2023-11-09 13:18:05 -06:00 committed by GitHub
commit a920a7fe8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 126 deletions

View file

@ -45,10 +45,6 @@ impl StyledStr {
self.0.push_str(msg);
}
pub(crate) fn trim(&mut self) {
self.0 = self.0.trim().to_owned()
}
pub(crate) fn trim_start_lines(&mut self) {
if let Some(pos) = self.0.find('\n') {
let (leading, help) = self.0.split_at(pos + 1);

View file

@ -14,6 +14,7 @@ use crate::util::FlatSet;
use crate::util::Id;
static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
const USAGE_SEP: &str = "\n ";
pub(crate) struct Usage<'cmd> {
cmd: &'cmd Command,
@ -39,8 +40,6 @@ impl<'cmd> Usage<'cmd> {
// any subcommands have been parsed (so as to give subcommands their own usage recursively)
pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> {
debug!("Usage::create_usage_with_title");
let usage = some!(self.create_usage_no_title(used));
use std::fmt::Write as _;
let mut styled = StyledStr::new();
let _ = write!(
@ -49,28 +48,49 @@ impl<'cmd> Usage<'cmd> {
self.styles.get_usage().render(),
self.styles.get_usage().render_reset()
);
styled.push_styled(&usage);
if self.write_usage_no_title(&mut styled, used) {
styled.trim_end();
} else {
return None;
}
debug!("Usage::create_usage_with_title: usage={styled}");
Some(styled)
}
// Creates a usage string (*without title*) if one was not provided by the user manually.
pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> {
debug!("Usage::create_usage_no_title");
let mut styled = StyledStr::new();
if self.write_usage_no_title(&mut styled, used) {
styled.trim_end();
debug!("Usage::create_usage_no_title: usage={styled}");
Some(styled)
} else {
None
}
}
// Creates a usage string (*without title*) if one was not provided by the user manually.
fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool {
debug!("Usage::create_usage_no_title");
if let Some(u) = self.cmd.get_override_usage() {
Some(u.clone())
styled.push_styled(u);
true
} else {
#[cfg(feature = "usage")]
{
if used.is_empty() {
Some(self.create_help_usage(true))
self.write_help_usage(styled);
} else {
Some(self.create_smart_usage(used))
self.write_smart_usage(styled, used);
}
true
}
#[cfg(not(feature = "usage"))]
{
None
false
}
}
}
@ -79,111 +99,21 @@ impl<'cmd> Usage<'cmd> {
#[cfg(feature = "usage")]
impl<'cmd> Usage<'cmd> {
// Creates a usage string for display in help messages (i.e. not for errors)
fn create_help_usage(&self, incl_reqs: bool) -> StyledStr {
debug!("Usage::create_help_usage; incl_reqs={incl_reqs:?}");
use std::fmt::Write as _;
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let mut styled = StyledStr::new();
fn write_help_usage(&self, styled: &mut StyledStr) {
debug!("Usage::write_help_usage");
let name = self
.cmd
.get_usage_name()
.or_else(|| self.cmd.get_bin_name())
.unwrap_or_else(|| self.cmd.get_name());
if !name.is_empty() {
// the trim won't properly remove a leading space due to the formatting
let _ = write!(
styled,
"{}{name}{}",
literal.render(),
literal.render_reset()
);
}
if self.needs_options_tag() {
let _ = write!(
styled,
"{} [OPTIONS]{}",
placeholder.render(),
placeholder.render_reset()
);
}
self.write_args(&[], !incl_reqs, &mut styled);
// incl_reqs is only false when this function is called recursively
if self.cmd.has_visible_subcommands() && incl_reqs
|| self.cmd.is_allow_external_subcommands_set()
{
let value_name = self
.cmd
.get_subcommand_value_name()
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
if self.cmd.is_subcommand_negates_reqs_set()
|| self.cmd.is_args_conflicts_with_subcommands_set()
{
let _ = write!(styled, "\n ");
if self.cmd.is_args_conflicts_with_subcommands_set() {
// Short-circuit full usage creation since no args will be relevant
let _ = write!(
styled,
"{}{name}{}",
literal.render(),
literal.render_reset()
);
} else {
styled.push_styled(&self.create_help_usage(false));
}
let _ = write!(
styled,
" {}<{value_name}>{}",
placeholder.render(),
placeholder.render_reset()
);
} else if self.cmd.is_subcommand_required_set() {
let _ = write!(
styled,
" {}<{value_name}>{}",
placeholder.render(),
placeholder.render_reset()
);
} else {
let _ = write!(
styled,
" {}[{value_name}]{}",
placeholder.render(),
placeholder.render_reset()
);
}
}
styled.trim();
debug!("Usage::create_help_usage: usage={styled}");
styled
self.write_arg_usage(styled, &[], true);
self.write_subcommand_usage(styled);
}
// Creates a context aware usage string, or "smart usage" from currently used
// args, and requirements
fn create_smart_usage(&self, used: &[Id]) -> StyledStr {
fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) {
debug!("Usage::create_smart_usage");
use std::fmt::Write;
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let mut styled = StyledStr::new();
let bin_name = self
.cmd
.get_usage_name()
.or_else(|| self.cmd.get_bin_name())
.unwrap_or_else(|| self.cmd.get_name());
let _ = write!(
styled,
"{}{bin_name}{}",
literal.render(),
literal.render_reset()
);
self.write_args(used, false, &mut styled);
self.write_arg_usage(styled, used, true);
if self.cmd.is_subcommand_required_set() {
let value_name = self
@ -192,12 +122,100 @@ impl<'cmd> Usage<'cmd> {
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
let _ = write!(
styled,
" {}<{value_name}>{}",
"{}<{value_name}>{}",
placeholder.render(),
placeholder.render_reset()
);
}
styled
}
fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) {
debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}");
use std::fmt::Write as _;
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let bin_name = self.get_name();
if !bin_name.is_empty() {
// the trim won't properly remove a leading space due to the formatting
let _ = write!(
styled,
"{}{bin_name}{} ",
literal.render(),
literal.render_reset()
);
}
if used.is_empty() && self.needs_options_tag() {
let _ = write!(
styled,
"{}[OPTIONS]{} ",
placeholder.render(),
placeholder.render_reset()
);
}
self.write_args(styled, used, !incl_reqs);
}
fn write_subcommand_usage(&self, styled: &mut StyledStr) {
debug!("Usage::write_subcommand_usage");
use std::fmt::Write as _;
// incl_reqs is only false when this function is called recursively
if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() {
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let value_name = self
.cmd
.get_subcommand_value_name()
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
if self.cmd.is_subcommand_negates_reqs_set()
|| self.cmd.is_args_conflicts_with_subcommands_set()
{
styled.trim_end();
let _ = write!(styled, "{}", USAGE_SEP);
if self.cmd.is_args_conflicts_with_subcommands_set() {
let bin_name = self.get_name();
// Short-circuit full usage creation since no args will be relevant
let _ = write!(
styled,
"{}{bin_name}{} ",
literal.render(),
literal.render_reset()
);
} else {
self.write_arg_usage(styled, &[], false);
}
let _ = write!(
styled,
"{}<{value_name}>{}",
placeholder.render(),
placeholder.render_reset()
);
} else if self.cmd.is_subcommand_required_set() {
let _ = write!(
styled,
"{}<{value_name}>{}",
placeholder.render(),
placeholder.render_reset()
);
} else {
let _ = write!(
styled,
"{}[{value_name}]{}",
placeholder.render(),
placeholder.render_reset()
);
}
}
}
fn get_name(&self) -> &str {
self.cmd
.get_usage_name()
.or_else(|| self.cmd.get_bin_name())
.unwrap_or_else(|| self.cmd.get_name())
}
// Determines if we need the `[OPTIONS]` tag in the usage string
@ -251,15 +269,8 @@ impl<'cmd> Usage<'cmd> {
}
// Returns the required args in usage string form by fully unrolling all groups
pub(crate) fn write_args(&self, incls: &[Id], force_optional: bool, styled: &mut StyledStr) {
for required in self.get_args(incls, force_optional) {
styled.push_str(" ");
styled.push_styled(&required);
}
}
pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec<StyledStr> {
debug!("Usage::get_args: incls={incls:?}",);
pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) {
debug!("Usage::write_args: incls={incls:?}",);
use std::fmt::Write as _;
let literal = &self.styles.get_literal();
@ -366,17 +377,20 @@ impl<'cmd> Usage<'cmd> {
}
}
let mut ret_val = Vec::new();
if !force_optional {
ret_val.extend(required_opts);
ret_val.extend(required_groups);
for arg in required_opts {
styled.push_styled(&arg);
styled.push_str(" ");
}
for arg in required_groups {
styled.push_styled(&arg);
styled.push_str(" ");
}
}
for pos in required_positionals.into_iter().flatten() {
ret_val.push(pos);
for arg in required_positionals.into_iter().flatten() {
styled.push_styled(&arg);
styled.push_str(" ");
}
debug!("Usage::get_args: ret_val={ret_val:?}");
ret_val
}
pub(crate) fn get_required_usage_from(