diff --git a/cli/usage_md.rs b/cli/usage_md.rs index e1bafbe1..3c86766f 100644 --- a/cli/usage_md.rs +++ b/cli/usage_md.rs @@ -1,5 +1,15 @@ use parallel_disk_usage::usage_md::render_usage_md; +use std::process::ExitCode; -fn main() { - println!("{}", render_usage_md().trim_end()); +fn main() -> ExitCode { + match render_usage_md() { + Ok(content) => { + println!("{}", content.trim_end()); + ExitCode::SUCCESS + } + Err(error) => { + eprintln!("error: {error}"); + ExitCode::FAILURE + } + } } diff --git a/src/usage_md.rs b/src/usage_md.rs index 33a3fa0b..b7032016 100644 --- a/src/usage_md.rs +++ b/src/usage_md.rs @@ -1,12 +1,27 @@ use crate::args::Args; use clap::builder::PossibleValue; use clap::{Arg, ArgAction, Command, CommandFactory}; +use derive_more::{Display, Error}; use itertools::Itertools; +use pipe_trait::Pipe; use std::borrow::Cow; +/// Error produced when generating the usage Markdown. +#[derive(Debug, Display, Error)] +#[non_exhaustive] +pub enum RenderUsageMdError { + /// A `visible_alias` duplicates the argument's own long flag name. + #[display("--{_0} has a visible_alias that duplicates its own flag name")] + RedundantVisibleLongAlias(#[error(not(source))] String), + /// A `visible_short_alias` duplicates the argument's own short flag name. + #[display("-{_0} has a visible_short_alias that duplicates its own flag name")] + RedundantVisibleShortAlias(#[error(not(source))] char), +} + /// Renders a Markdown reference page for `pdu`'s CLI. -pub fn render_usage_md() -> String { +pub fn render_usage_md() -> Result { let mut command: Command = Args::command(); + reject_redundant_aliases(&command)?; let mut out = String::new(); let usage = command.render_usage().to_string(); @@ -59,7 +74,7 @@ pub fn render_usage_md() -> String { } } - out + Ok(out) } fn render_argument(out: &mut String, arg: &Arg) { @@ -232,3 +247,35 @@ fn ensure_ends_with_punctuation(line: &str) -> Cow<'_, str> { Cow::Owned(format!("{line}.")) } } + +/// Rejects any argument whose visible alias duplicates its own primary flag name. +/// +/// A visible alias matching the argument's own long name, or a visible short alias +/// matching its own short flag, is a coding mistake that produces redundant output in +/// USAGE.md. +fn reject_redundant_aliases(command: &Command) -> Result<(), RenderUsageMdError> { + for arg in command.get_arguments() { + if let Some(primary_long) = arg.get_long() { + for alias in arg.get_visible_aliases().unwrap_or_default() { + if alias == primary_long { + return primary_long + .to_owned() + .pipe(RenderUsageMdError::RedundantVisibleLongAlias) + .pipe(Err); + } + } + } + + if let Some(primary_short) = arg.get_short() { + for alias in arg.get_visible_short_aliases().unwrap_or_default() { + if alias == primary_short { + return primary_short + .pipe(RenderUsageMdError::RedundantVisibleShortAlias) + .pipe(Err); + } + } + } + } + + Ok(()) +} diff --git a/tests/sync_usage_md.rs b/tests/sync_usage_md.rs index 6110b841..80184578 100644 --- a/tests/sync_usage_md.rs +++ b/tests/sync_usage_md.rs @@ -11,7 +11,9 @@ use parallel_disk_usage::usage_md::render_usage_md; #[test] fn usage_md() { - let actual = render_usage_md(); + let actual = render_usage_md().unwrap_or_else(|error| { + panic!("failed to render usage markdown: {error}"); + }); let expected = include_str!("../USAGE.md"); assert!( actual.trim_end() == expected.trim_end(),