From ad98ad64dc22bc442360833988948cd1dee5da41 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 19:47:27 +0000 Subject: [PATCH 1/6] fix(usage_md): detect and reject visible aliases that duplicate their own flag name A visible_alias matching the argument's own --long name (or a visible_short_alias matching its -short flag) is a coding mistake that produces redundant output in USAGE.md. The new validation runs at the start of render_usage_md() and exits with code 1 if any such redundancy is found. https://claude.ai/code/session_01TSnvxN1HBwwzhxfFShWAQ5 --- src/usage_md.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/usage_md.rs b/src/usage_md.rs index 33a3fa0b..c6356d73 100644 --- a/src/usage_md.rs +++ b/src/usage_md.rs @@ -7,6 +7,7 @@ use std::borrow::Cow; /// Renders a Markdown reference page for `pdu`'s CLI. pub fn render_usage_md() -> String { let mut command: Command = Args::command(); + validate_no_redundant_visible_aliases(&command); let mut out = String::new(); let usage = command.render_usage().to_string(); @@ -232,3 +233,41 @@ fn ensure_ends_with_punctuation(line: &str) -> Cow<'_, str> { Cow::Owned(format!("{line}.")) } } + +/// Checks that no argument has a visible alias that 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. This function detects such mistakes and terminates the process. +fn validate_no_redundant_visible_aliases(command: &Command) { + let mut errors = Vec::::new(); + + 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 { + errors.push(format!( + "--{primary_long} has visible_alias \"{alias}\" that duplicates its own flag name", + )); + } + } + } + + if let Some(primary_short) = arg.get_short() { + for alias in arg.get_visible_short_aliases().unwrap_or_default() { + if alias == primary_short { + errors.push(format!( + "-{primary_short} has visible_short_alias '{alias}' that duplicates its own flag name", + )); + } + } + } + } + + if !errors.is_empty() { + for error in &errors { + eprintln!("error: {error}"); + } + std::process::exit(1); + } +} From 4038c04037fc96aa7ebf9fe458c24d5192160e55 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 19:56:05 +0000 Subject: [PATCH 2/6] refactor(usage_md): return custom error type instead of calling process::exit Define a non-exhaustive `RenderUsageMdError` enum with `derive_more::{Display, Error}` and make `render_usage_md` return `Result`. The callsites in cli/usage_md.rs and tests/sync_usage_md.rs now handle the error appropriately. https://claude.ai/code/session_01TSnvxN1HBwwzhxfFShWAQ5 --- cli/usage_md.rs | 8 +++++++- src/usage_md.rs | 35 +++++++++++++++++++---------------- tests/sync_usage_md.rs | 2 +- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/cli/usage_md.rs b/cli/usage_md.rs index e1bafbe1..4e2fe34e 100644 --- a/cli/usage_md.rs +++ b/cli/usage_md.rs @@ -1,5 +1,11 @@ use parallel_disk_usage::usage_md::render_usage_md; fn main() { - println!("{}", render_usage_md().trim_end()); + match render_usage_md() { + Ok(content) => println!("{}", content.trim_end()), + Err(error) => { + eprintln!("error: {error}"); + std::process::exit(1); + } + } } diff --git a/src/usage_md.rs b/src/usage_md.rs index c6356d73..dda12c63 100644 --- a/src/usage_md.rs +++ b/src/usage_md.rs @@ -1,13 +1,23 @@ use crate::args::Args; use clap::builder::PossibleValue; use clap::{Arg, ArgAction, Command, CommandFactory}; +use derive_more::{Display, Error}; use itertools::Itertools; use std::borrow::Cow; +/// Error produced when generating the usage Markdown. +#[derive(Debug, Display, Error)] +#[non_exhaustive] +pub enum RenderUsageMdError { + /// A `visible_alias` or `visible_short_alias` duplicates the argument's own flag name. + #[display("redundant visible alias: {_0}")] + RedundantVisibleAlias(#[error(not(source))] String), +} + /// 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(); - validate_no_redundant_visible_aliases(&command); + validate_no_redundant_visible_aliases(&command)?; let mut out = String::new(); let usage = command.render_usage().to_string(); @@ -60,7 +70,7 @@ pub fn render_usage_md() -> String { } } - out + Ok(out) } fn render_argument(out: &mut String, arg: &Arg) { @@ -239,16 +249,14 @@ fn ensure_ends_with_punctuation(line: &str) -> Cow<'_, str> { /// 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. This function detects such mistakes and terminates the process. -fn validate_no_redundant_visible_aliases(command: &Command) { - let mut errors = Vec::::new(); - +fn validate_no_redundant_visible_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 { - errors.push(format!( + return Err(RenderUsageMdError::RedundantVisibleAlias(format!( "--{primary_long} has visible_alias \"{alias}\" that duplicates its own flag name", - )); + ))); } } } @@ -256,18 +264,13 @@ fn validate_no_redundant_visible_aliases(command: &Command) { if let Some(primary_short) = arg.get_short() { for alias in arg.get_visible_short_aliases().unwrap_or_default() { if alias == primary_short { - errors.push(format!( + return Err(RenderUsageMdError::RedundantVisibleAlias(format!( "-{primary_short} has visible_short_alias '{alias}' that duplicates its own flag name", - )); + ))); } } } } - if !errors.is_empty() { - for error in &errors { - eprintln!("error: {error}"); - } - std::process::exit(1); - } + Ok(()) } diff --git a/tests/sync_usage_md.rs b/tests/sync_usage_md.rs index 6110b841..0da598f0 100644 --- a/tests/sync_usage_md.rs +++ b/tests/sync_usage_md.rs @@ -11,7 +11,7 @@ use parallel_disk_usage::usage_md::render_usage_md; #[test] fn usage_md() { - let actual = render_usage_md(); + let actual = render_usage_md().expect("render_usage_md failed"); let expected = include_str!("../USAGE.md"); assert!( actual.trim_end() == expected.trim_end(), From 40a1be081624bcecec78418b40045104eeebb5c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 20:10:49 +0000 Subject: [PATCH 3/6] refactor(usage_md): address review feedback on error type and callsites - Split RedundantVisibleAlias into RedundantVisibleLongAlias(String) and RedundantVisibleShortAlias(char), moving message into #[display] - Rename validate_no_redundant_visible_aliases to check_visible_aliases - Use if-let-Err pattern in cli/usage_md.rs to match lib.rs style - Simplify test to use .unwrap() instead of .expect() - Fix doc comment to use plain English instead of backtick-wrapped flags https://claude.ai/code/session_01TSnvxN1HBwwzhxfFShWAQ5 --- cli/usage_md.rs | 15 +++++++++------ src/usage_md.rs | 31 +++++++++++++++++-------------- tests/sync_usage_md.rs | 2 +- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cli/usage_md.rs b/cli/usage_md.rs index 4e2fe34e..8ae2dd62 100644 --- a/cli/usage_md.rs +++ b/cli/usage_md.rs @@ -1,11 +1,14 @@ use parallel_disk_usage::usage_md::render_usage_md; fn main() { - match render_usage_md() { - Ok(content) => println!("{}", content.trim_end()), - Err(error) => { - eprintln!("error: {error}"); - std::process::exit(1); - } + if let Err(error) = run() { + eprintln!("error: {error}"); + std::process::exit(1); } } + +fn run() -> Result<(), parallel_disk_usage::usage_md::RenderUsageMdError> { + let content = render_usage_md()?; + println!("{}", content.trim_end()); + Ok(()) +} diff --git a/src/usage_md.rs b/src/usage_md.rs index dda12c63..7dd91806 100644 --- a/src/usage_md.rs +++ b/src/usage_md.rs @@ -9,15 +9,18 @@ use std::borrow::Cow; #[derive(Debug, Display, Error)] #[non_exhaustive] pub enum RenderUsageMdError { - /// A `visible_alias` or `visible_short_alias` duplicates the argument's own flag name. - #[display("redundant visible alias: {_0}")] - RedundantVisibleAlias(#[error(not(source))] String), + /// 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() -> Result { let mut command: Command = Args::command(); - validate_no_redundant_visible_aliases(&command)?; + check_visible_aliases(&command)?; let mut out = String::new(); let usage = command.render_usage().to_string(); @@ -246,17 +249,17 @@ fn ensure_ends_with_punctuation(line: &str) -> Cow<'_, str> { /// Checks that no argument has a visible alias that 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. This function detects such mistakes and terminates the process. -fn validate_no_redundant_visible_aliases(command: &Command) -> Result<(), RenderUsageMdError> { +/// 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 check_visible_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 Err(RenderUsageMdError::RedundantVisibleAlias(format!( - "--{primary_long} has visible_alias \"{alias}\" that duplicates its own flag name", - ))); + return Err(RenderUsageMdError::RedundantVisibleLongAlias( + primary_long.to_owned(), + )); } } } @@ -264,9 +267,9 @@ fn validate_no_redundant_visible_aliases(command: &Command) -> Result<(), Render if let Some(primary_short) = arg.get_short() { for alias in arg.get_visible_short_aliases().unwrap_or_default() { if alias == primary_short { - return Err(RenderUsageMdError::RedundantVisibleAlias(format!( - "-{primary_short} has visible_short_alias '{alias}' that duplicates its own flag name", - ))); + return Err(RenderUsageMdError::RedundantVisibleShortAlias( + primary_short, + )); } } } diff --git a/tests/sync_usage_md.rs b/tests/sync_usage_md.rs index 0da598f0..7a0b58e2 100644 --- a/tests/sync_usage_md.rs +++ b/tests/sync_usage_md.rs @@ -11,7 +11,7 @@ use parallel_disk_usage::usage_md::render_usage_md; #[test] fn usage_md() { - let actual = render_usage_md().expect("render_usage_md failed"); + let actual = render_usage_md().unwrap(); let expected = include_str!("../USAGE.md"); assert!( actual.trim_end() == expected.trim_end(), From abec8c9ebe6087ba93b691d20aa979b6949229b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 20:31:44 +0000 Subject: [PATCH 4/6] refactor(usage_md): rename to reject_redundant_aliases, use pipe-trait, revert to match - Rename check_visible_aliases to reject_redundant_aliases - Use pipe-trait to flatten nested constructor and Err calls - Revert cli/usage_md.rs back to match pattern https://claude.ai/code/session_01TSnvxN1HBwwzhxfFShWAQ5 --- cli/usage_md.rs | 15 ++++++--------- src/usage_md.rs | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/cli/usage_md.rs b/cli/usage_md.rs index 8ae2dd62..4e2fe34e 100644 --- a/cli/usage_md.rs +++ b/cli/usage_md.rs @@ -1,14 +1,11 @@ use parallel_disk_usage::usage_md::render_usage_md; fn main() { - if let Err(error) = run() { - eprintln!("error: {error}"); - std::process::exit(1); + match render_usage_md() { + Ok(content) => println!("{}", content.trim_end()), + Err(error) => { + eprintln!("error: {error}"); + std::process::exit(1); + } } } - -fn run() -> Result<(), parallel_disk_usage::usage_md::RenderUsageMdError> { - let content = render_usage_md()?; - println!("{}", content.trim_end()); - Ok(()) -} diff --git a/src/usage_md.rs b/src/usage_md.rs index 7dd91806..b7032016 100644 --- a/src/usage_md.rs +++ b/src/usage_md.rs @@ -3,6 +3,7 @@ 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. @@ -20,7 +21,7 @@ pub enum RenderUsageMdError { /// Renders a Markdown reference page for `pdu`'s CLI. pub fn render_usage_md() -> Result { let mut command: Command = Args::command(); - check_visible_aliases(&command)?; + reject_redundant_aliases(&command)?; let mut out = String::new(); let usage = command.render_usage().to_string(); @@ -247,19 +248,20 @@ fn ensure_ends_with_punctuation(line: &str) -> Cow<'_, str> { } } -/// Checks that no argument has a visible alias that duplicates its own primary flag name. +/// 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 check_visible_aliases(command: &Command) -> Result<(), RenderUsageMdError> { +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 Err(RenderUsageMdError::RedundantVisibleLongAlias( - primary_long.to_owned(), - )); + return primary_long + .to_owned() + .pipe(RenderUsageMdError::RedundantVisibleLongAlias) + .pipe(Err); } } } @@ -267,9 +269,9 @@ fn check_visible_aliases(command: &Command) -> Result<(), RenderUsageMdError> { if let Some(primary_short) = arg.get_short() { for alias in arg.get_visible_short_aliases().unwrap_or_default() { if alias == primary_short { - return Err(RenderUsageMdError::RedundantVisibleShortAlias( - primary_short, - )); + return primary_short + .pipe(RenderUsageMdError::RedundantVisibleShortAlias) + .pipe(Err); } } } From 224c3ec2db71684cebbacee39810cc9d400fe2d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 21:06:04 +0000 Subject: [PATCH 5/6] refactor(usage_md): return ExitCode instead of calling process::exit https://claude.ai/code/session_01TSnvxN1HBwwzhxfFShWAQ5 --- cli/usage_md.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/usage_md.rs b/cli/usage_md.rs index 4e2fe34e..3c86766f 100644 --- a/cli/usage_md.rs +++ b/cli/usage_md.rs @@ -1,11 +1,15 @@ use parallel_disk_usage::usage_md::render_usage_md; +use std::process::ExitCode; -fn main() { +fn main() -> ExitCode { match render_usage_md() { - Ok(content) => println!("{}", content.trim_end()), + Ok(content) => { + println!("{}", content.trim_end()); + ExitCode::SUCCESS + } Err(error) => { eprintln!("error: {error}"); - std::process::exit(1); + ExitCode::FAILURE } } } From 82914f0669c3bb6ff23bf6ee18bbc103d2625513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kh=E1=BA=A3i?= Date: Mon, 23 Mar 2026 05:10:11 +0700 Subject: [PATCH 6/6] docs: improve error message Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/sync_usage_md.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/sync_usage_md.rs b/tests/sync_usage_md.rs index 7a0b58e2..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().unwrap(); + 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(),