From 46c060f0a7be5cf8ac6336eef04764e9c9c91e7f Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sat, 15 Nov 2025 18:52:10 +1100 Subject: [PATCH] Partially use standard formatter alignment for aligning option help text --- jaarg/src/help.rs | 87 +++++++++++++++++++++++++++++++-------------- jaarg/src/option.rs | 1 + 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/jaarg/src/help.rs b/jaarg/src/help.rs index 2a970b2..51db722 100644 --- a/jaarg/src/help.rs +++ b/jaarg/src/help.rs @@ -74,17 +74,16 @@ impl core::fmt::Display for StandardFullHelpWriter<'_, ID> { writeln!(f, "{description}")?; } - fn calculate_left_pad(option: &Opt) -> usize { + // Determine the alignment width from the longest option parameter + fn calculate_option_line_length(option: &Opt) -> usize { (match option.names { OptIdentifier::Single(name) => name.chars().count(), OptIdentifier::Multi(names) => (names.len() - 1) * 3 + names.iter() .fold(0, |accum, name| accum + name.chars().count()), }) + option.value_name.map_or(0, |v| v.len() + 3) } - - // Determine the alignment width from the longest option parameter - let align_width = 2 + self.0.options.iter() - .map(|o| calculate_left_pad(o)).max().unwrap_or(0); + let align_width = 3 + self.0.options.iter() + .map(|o| calculate_option_line_length(o)).max().unwrap_or(0); // Write positional argument descriptions let mut first = true; @@ -97,15 +96,64 @@ impl core::fmt::Display for StandardFullHelpWriter<'_, ID> { first = false; } - // Write positional argument line - write!(f, " {name}", name = option.first_name())?; + // Write positional argument line (name + optional aligned help text) + let name = option.first_name(); + write!(f, " {name}")?; if let Some(help_text) = option.help_string { write!(f, " {:.(&'a Opt); + impl core::fmt::Display for OptionUsageLine<'_, ID> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use core::fmt::Write; + let mut length = 0; + + // Write option flag name(s) + match self.0.names { + OptIdentifier::Single(name) => { + write!(f, "{name}")?; + length = name.chars().count(); + } + OptIdentifier::Multi(names) => { + for (i, name) in names.iter().enumerate() { + if i == 0 { + write!(f, "{name}")?; + length += name.chars().count(); + } else { + write!(f, " | {name}")?; + length += 3 + name.chars().count(); + } + } + } + } + + // Write value argument for value options parameters + if let Some(value_name) = self.0.value_name { + write!(f, " <{value_name}>")?; + length += 2 + value_name.chars().count() + 1; + } + + // Write padding if requested + match (f.align(), f.width().unwrap_or(0).checked_sub(length)) { + (Some(core::fmt::Alignment::Left), Some(width)) if width > 0 => { + let fill = f.fill(); + // First padding char is *always* a space + f.write_char(' ')?; + for _ in 1..width { + f.write_char(fill)?; + } + Ok(()) + } + _ => Ok(()), + } + } + } + // Write option parameter argument descriptions first = true; for option in self.0.options.iter() @@ -117,25 +165,12 @@ impl core::fmt::Display for StandardFullHelpWriter<'_, ID> { first = false; } - // Write option flag name(s) - match option.names { - OptIdentifier::Single(name) => { - write!(f, " {name}")?; - } - OptIdentifier::Multi(names) => for (i, name) in names.iter().enumerate() { - write!(f, "{prefix}{name}", prefix = if i == 0 { " " } else { " | " })?; - } - } - - // Write value argument for value options parameters - if let Some(value_name) = option.value_name { - write!(f, " <{value_name}>")?; - } - - // Write padding and help text + // Write line for option, with aligned help text if needed + let line = OptionUsageLine(&option); if let Some(help_text) = option.help_string { - write!(f, " {:. Opt { } } +#[allow(dead_code)] impl Opt { /// Get the first name of the option. pub const fn first_name(&self) -> &str {