diff --git a/clap_builder/src/builder/possible_value.rs b/clap_builder/src/builder/possible_value.rs index de7e543fe816..2f4d5c7d765e 100644 --- a/clap_builder/src/builder/possible_value.rs +++ b/clap_builder/src/builder/possible_value.rs @@ -1,3 +1,5 @@ +use anstyle::Style; + use crate::builder::IntoResettable; use crate::builder::Str; use crate::builder::StyledStr; @@ -37,6 +39,7 @@ pub struct PossibleValue { name: Str, help: Option, aliases: Vec, // (name, visible) + visible_aliases: Vec, hide: bool, } @@ -130,6 +133,20 @@ impl PossibleValue { self } + /// Add a _visible_ alias to this [PossibleValue]. + /// + /// Unlike [PossibleValue::alias], these aliases will show in help output. + #[must_use] + pub fn visible_alias(mut self, name: impl IntoResettable) -> Self { + if let Some(name) = name.into_resettable().into_option() { + self.visible_aliases.push(name); + } else { + self.visible_aliases.clear(); + } + + self + } + /// Sets multiple *hidden* aliases for this argument value. /// /// # Examples @@ -146,6 +163,16 @@ impl PossibleValue { self.aliases.extend(names.into_iter().map(|a| a.into())); self } + + /// Add multiple _visible_ aliases to this [PossibleValue]. + /// + /// Unlike [PossibleValue::aliases], these aliases will show in help output. + #[must_use] + pub fn visible_aliases(mut self, names: impl IntoIterator>) -> Self { + self.visible_aliases + .extend(names.into_iter().map(|a| a.into())); + self + } } /// Reflection @@ -156,6 +183,38 @@ impl PossibleValue { self.name.as_str() } + /// Get the visible aliases for this argument + pub fn get_visible_aliases(&self) -> &Vec { + &self.visible_aliases + } + + /// Render help text for this [PossibleValue] with all of its visible aliases included. + /// + /// If there are no visible aliases, this will simply emit the formatted name, if there are visible aliases, these + /// will be appended like this: `name [aliases: a, b, c]`. + pub fn render_help_prefix_long(&self, literal: &Style) -> String { + let name = self.get_name(); + let aliases = if self.get_visible_aliases().is_empty() { + "".to_string() + } else { + // [aliases: a, b, c] + format!( + "[aliases: {}]", + self.get_visible_aliases() + .iter() + .map(|s| { format!("{}{s}{}", literal.render(), literal.render_reset()) }) + .collect::>() + .join(", ") + ) + }; + + format!( + "{}{name}{}{aliases}", + literal.render(), + literal.render_reset() + ) + } + /// Get the help specified for this argument, if any #[inline] pub fn get_help(&self) -> Option<&StyledStr> { @@ -173,26 +232,13 @@ impl PossibleValue { !self.hide && self.help.is_some() } - /// Get the name if argument value is not hidden, `None` otherwise, - /// but wrapped in quotes if it contains whitespace - #[cfg(feature = "help")] - pub(crate) fn get_visible_quoted_name(&self) -> Option> { - if !self.hide { - Some(if self.name.contains(char::is_whitespace) { - format!("{:?}", self.name).into() - } else { - self.name.as_str().into() - }) - } else { - None - } - } - /// Returns all valid values of the argument value. /// /// Namely the name and all aliases. pub fn get_name_and_aliases(&self) -> impl Iterator + '_ { - std::iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str())) + std::iter::once(self.get_name()) + .chain(self.aliases.iter().map(|s| s.as_str())) + .chain(self.visible_aliases.iter().map(|s| s.as_str())) } /// Tests if the value is valid for this argument value diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index dff15ff12794..506f35a394df 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -8,12 +8,12 @@ use std::borrow::Cow; use std::cmp; use std::usize; +use crate::builder::{Arg, Command}; // Internal use crate::builder::PossibleValue; use crate::builder::Str; use crate::builder::StyledStr; use crate::builder::Styles; -use crate::builder::{Arg, Command}; use crate::output::display_width; use crate::output::wrap; use crate::output::Usage; @@ -689,10 +689,12 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { let possible_vals = arg.get_possible_values(); if !possible_vals.is_empty() { debug!("HelpTemplate::help: Found possible vals...{possible_vals:?}"); + + // FIXME let longest = possible_vals .iter() .filter(|f| !f.is_hide_set()) - .map(|f| display_width(f.get_name())) + .map(|f| display_width(f.render_help_prefix_long(literal).as_str())) .max() .expect("Only called with possible value"); @@ -705,19 +707,15 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } self.writer.push_str("Possible values:"); for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) { - let name = pv.get_name(); + let prefix = pv.render_help_prefix_long(literal); let mut descr = StyledStr::new(); - let _ = write!( - &mut descr, - "{}{name}{}", - literal.render(), - literal.render_reset() - ); + + let _ = write!(&mut descr, "{prefix}",); if let Some(help) = pv.get_help() { debug!("HelpTemplate::help: Possible Value help"); // To align help messages - let padding = longest - display_width(name); + let padding = longest - display_width(prefix.as_str()); let _ = write!(&mut descr, ": {:padding$}", ""); descr.push_styled(help); } @@ -848,9 +846,15 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if !possible_vals.is_empty() { debug!("HelpTemplate::spec_vals: Found possible vals...{possible_vals:?}"); + // FIXME here is where possible values are formatted in short form, should be a list with no difference + // between possible value names and visible aliases let pvs = possible_vals .iter() - .filter_map(PossibleValue::get_visible_quoted_name) + .filter(|v| !v.is_hide_set()) + .flat_map(|v| { + std::iter::once(v.get_name()) + .chain(v.get_visible_aliases().iter().map(|s| s.as_str())) + }) .collect::>() .join(", ");