From f8b85824798498fc2f97375706c2e2c9ce1a60d1 Mon Sep 17 00:00:00 2001 From: Kanishk Sachan Date: Fri, 12 Jun 2026 17:57:42 +0100 Subject: [PATCH 1/2] feat(true,false): print Usage: line before the description in --help GNU's `true --help`/`false --help` start with a "Usage:" synopsis line, but uutils printed the long description first instead: $ LANG=C false --help | head -1 Returns false, an unsuccessful exit status. vs. GNU: $ LANG=C /usr/bin/false --help | head -1 Usage: /usr/bin/false [ignored command line arguments] Adds a `localized_help_template_usage_first()` helper in uucore that places the localized "Usage:" line before `{about-with-newline}` (the default ordering for every other utility is unchanged), and gives true/false their own `*-usage` strings ("true [ignored command line arguments] / or: true OPTION", and similarly for false) wired up via `override_usage`. --- src/uu/false/locales/en-US.ftl | 3 ++ src/uu/false/locales/fr-FR.ftl | 4 +++ src/uu/false/src/false.rs | 5 +-- src/uu/true/locales/en-US.ftl | 3 ++ src/uu/true/locales/fr-FR.ftl | 4 +++ src/uu/true/src/true.rs | 5 +-- src/uucore/src/lib/lib.rs | 63 +++++++++++++++++++++++++++------- tests/by-util/test_false.rs | 11 ++++++ tests/by-util/test_true.rs | 11 ++++++ 9 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/uu/false/locales/en-US.ftl b/src/uu/false/locales/en-US.ftl index b4bbe787bd9..f959c3f3a2d 100644 --- a/src/uu/false/locales/en-US.ftl +++ b/src/uu/false/locales/en-US.ftl @@ -4,5 +4,8 @@ false-about = Returns false, an unsuccessful exit status. will try to write the help or version text. Any IO error during this operation is diagnosed, yet the program will also return 1. +false-usage = false [ignored command line arguments] + or: false OPTION + false-help-text = Print help information false-version-text = Print version information diff --git a/src/uu/false/locales/fr-FR.ftl b/src/uu/false/locales/fr-FR.ftl index c97c8483cd3..87049376f24 100644 --- a/src/uu/false/locales/fr-FR.ftl +++ b/src/uu/false/locales/fr-FR.ftl @@ -3,5 +3,9 @@ false-about = Renvoie false, un code de sortie indiquant un échec. Retourne immédiatement avec le code de sortie 1. Lorsqu'il est invoqué avec l'une des options reconnues, il tente d'afficher l'aide ou la version. Toute erreur d'entrée/sortie pendant cette opération est signalée, mais le programme retourne également 1. + +false-usage = false [arguments de ligne de commande ignorés] + ou: false OPTION + false-help-text = Afficher l'aide false-version-text = Afficher les informations de version diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index d99d8ece4aa..d60fbf7821e 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use clap::{Arg, ArgAction, Command}; use std::io::Write; -use uucore::{crate_version, translate}; +use uucore::{crate_version, format_usage, translate}; // uucore::main does not support no-result pub fn uumain(mut args: impl uucore::Args) -> i32 { @@ -33,7 +33,8 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { pub fn uu_app() -> Command { Command::new("false") .version(crate_version!()) - .help_template(uucore::localized_help_template("false")) + .help_template(uucore::localized_help_template_usage_first("false")) + .override_usage(format_usage(&translate!("false-usage"))) .about(translate!("false-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) diff --git a/src/uu/true/locales/en-US.ftl b/src/uu/true/locales/en-US.ftl index c5806a10d32..89ddc588f40 100644 --- a/src/uu/true/locales/en-US.ftl +++ b/src/uu/true/locales/en-US.ftl @@ -4,5 +4,8 @@ true-about = Returns true, a successful exit status. options. In those cases it will try to write the help or version text. Any IO error during this operation causes the program to return 1 instead. +true-usage = true [ignored command line arguments] + or: true OPTION + true-help-text = Print help information true-version-text = Print version information diff --git a/src/uu/true/locales/fr-FR.ftl b/src/uu/true/locales/fr-FR.ftl index 491d11d35f5..61fb9b4582e 100644 --- a/src/uu/true/locales/fr-FR.ftl +++ b/src/uu/true/locales/fr-FR.ftl @@ -4,5 +4,9 @@ true-about = Renvoie true, un code de sortie indiquant le succès. Retourne immédiatement avec le code de sortie 0, sauf si l'une des options reconnues est utilisée. Dans ce cas, il essaiera d'afficher l'aide ou la version. Une erreur d'entrée/sortie pendant cette opération entraîne un retour avec le code de sortie 1. + +true-usage = true [arguments de ligne de commande ignorés] + ou: true OPTION + true-help-text = Afficher l'aide true-version-text = Afficher les informations de version diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 6e259d17f21..901875a47f2 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use clap::{Arg, ArgAction, Command}; use std::io::Write; -use uucore::{crate_version, translate}; +use uucore::{crate_version, format_usage, translate}; // uucore::main does not support no-result pub fn uumain(mut args: impl uucore::Args) -> i32 { @@ -38,7 +38,8 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { pub fn uu_app() -> Command { Command::new("true") .version(crate_version!()) - .help_template(uucore::localized_help_template("true")) + .help_template(uucore::localized_help_template_usage_first("true")) + .override_usage(format_usage(&translate!("true-usage"))) .about(translate!("true-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 4dfffed2995..082fb40eb15 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -282,19 +282,35 @@ pub fn format_usage(s: &str) -> String { /// .help_template(localized_help_template("myutil")); /// ``` pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr { + localized_help_template_with_colors(util_name, detect_colors_enabled()) +} + +/// Like [`localized_help_template`], but places the localized "Usage:" line +/// *before* the `--about` text instead of after it. +/// +/// GNU utilities print the usage synopsis first (e.g. `Usage: false +/// [ignored command line arguments]`) followed by the description. Most +/// uutils utilities keep the description first since their usage line is +/// less informative on its own, but for utilities like `true`/`false` where +/// the usage line itself communicates useful information, matching GNU's +/// ordering is clearer. +pub fn localized_help_template_usage_first(util_name: &str) -> clap::builder::StyledStr { + localized_help_template_with_order(util_name, detect_colors_enabled(), true) +} + +/// Determine whether colored output should be used, based on the same +/// environment variables and TTY checks as `configure_localized_command`. +fn detect_colors_enabled() -> bool { use std::io::IsTerminal; - // Determine if colors should be enabled - same logic as configure_localized_command - let colors_enabled = if std::env::var("NO_COLOR").is_ok() { + if std::env::var("NO_COLOR").is_ok() { false } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() { true } else { IsTerminal::is_terminal(&std::io::stdout()) && std::env::var("TERM").unwrap_or_default() != "dumb" - }; - - localized_help_template_with_colors(util_name, colors_enabled) + } } /// Create a localized help template with explicit color control @@ -302,6 +318,18 @@ pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr { pub fn localized_help_template_with_colors( util_name: &str, colors_enabled: bool, +) -> clap::builder::StyledStr { + localized_help_template_with_order(util_name, colors_enabled, false) +} + +/// Shared implementation behind [`localized_help_template_with_colors`] and +/// [`localized_help_template_usage_first`]. When `usage_first` is `true`, +/// the "Usage:" line is placed before `{about-with-newline}` rather than +/// after it. +fn localized_help_template_with_order( + util_name: &str, + colors_enabled: bool, + usage_first: bool, ) -> clap::builder::StyledStr { use std::fmt::Write; @@ -311,21 +339,30 @@ pub fn localized_help_template_with_colors( // Get the localized "Usage" label let usage_label = crate::locale::translate!("common-usage"); + // Build the styled usage header (bold + underline like clap's default) + let mut usage_line = String::new(); + if colors_enabled { + write!( + usage_line, + "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n" + ) + .unwrap(); + } else { + write!(usage_line, "{usage_label}: {{usage}}\n\n").unwrap(); + } + // Create a styled template let mut template = clap::builder::StyledStr::new(); - // Add the basic template parts - writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap(); - - // Add styled usage header (bold + underline like clap's default) - if colors_enabled { - write!( + if usage_first { + writeln!( template, - "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n" + "{{before-help}}{usage_line}{{about-with-newline}}" ) .unwrap(); } else { - write!(template, "{usage_label}: {{usage}}\n\n").unwrap(); + writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap(); + write!(template, "{usage_line}").unwrap(); } // Add the rest diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index cc0874256b1..9b412b36e0b 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -27,6 +27,17 @@ fn test_help() { .stdout_contains("false"); } +#[test] +fn test_help_starts_with_usage() { + // GNU's `false --help` starts with a "Usage:" line; ensure ours does too. + let result = new_ucmd!().args(&["--help"]).fails(); + assert!( + result + .stdout_str() + .starts_with("Usage: false [ignored command line arguments]") + ); +} + #[test] fn test_short_options() { for option in ["-h", "-V"] { diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index 23c947fb29c..5f83e0f11ba 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -30,6 +30,17 @@ fn test_help() { .stdout_contains("true"); } +#[test] +fn test_help_starts_with_usage() { + // GNU's `true --help` starts with a "Usage:" line; ensure ours does too. + let result = new_ucmd!().args(&["--help"]).succeeds(); + assert!( + result + .stdout_str() + .starts_with("Usage: true [ignored command line arguments]") + ); +} + #[test] fn test_short_options() { for option in ["-h", "-V"] { From 4dace349cef79c2cae66245bd75a7d438c99648b Mon Sep 17 00:00:00 2001 From: Kanishk Sachan Date: Sat, 13 Jun 2026 09:08:18 +0100 Subject: [PATCH 2/2] fix(true,false): address review feedback on usage-first help template - Set NO_COLOR=1 in the new --help tests so the starts_with("Usage: ...") assertion doesn't break when CLICOLOR_FORCE/FORCE_COLOR are set. - Deduplicate color-detection logic: detect_colors_enabled() now reuses clap_localization::should_use_color_for_stream() instead of re-implementing the same NO_COLOR/CLICOLOR_FORCE/FORCE_COLOR/TERM checks. --- src/uucore/src/lib/lib.rs | 11 +---------- src/uucore/src/lib/mods/clap_localization.rs | 2 +- tests/by-util/test_false.rs | 3 ++- tests/by-util/test_true.rs | 6 +++++- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 082fb40eb15..8ac7dce36a7 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -301,16 +301,7 @@ pub fn localized_help_template_usage_first(util_name: &str) -> clap::builder::St /// Determine whether colored output should be used, based on the same /// environment variables and TTY checks as `configure_localized_command`. fn detect_colors_enabled() -> bool { - use std::io::IsTerminal; - - if std::env::var("NO_COLOR").is_ok() { - false - } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() { - true - } else { - IsTerminal::is_terminal(&std::io::stdout()) - && std::env::var("TERM").unwrap_or_default() != "dumb" - } + clap_localization::should_use_color_for_stream(&std::io::stdout()) } /// Create a localized help template with explicit color control diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index fc4f838c216..8b0f00c5d46 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -53,7 +53,7 @@ fn get_color_choice() -> clap::ColorChoice { } /// Generic helper to check if colors should be enabled for a given stream -fn should_use_color_for_stream(stream: &S) -> bool { +pub(crate) fn should_use_color_for_stream(stream: &S) -> bool { match get_color_choice() { clap::ColorChoice::Always => true, clap::ColorChoice::Never => false, diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index 9b412b36e0b..37ea73c1eff 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -30,7 +30,8 @@ fn test_help() { #[test] fn test_help_starts_with_usage() { // GNU's `false --help` starts with a "Usage:" line; ensure ours does too. - let result = new_ucmd!().args(&["--help"]).fails(); + // NO_COLOR avoids ANSI styling so the raw stdout comparison is stable. + let result = new_ucmd!().env("NO_COLOR", "1").args(&["--help"]).fails(); assert!( result .stdout_str() diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index 5f83e0f11ba..cc6584f2d88 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -33,7 +33,11 @@ fn test_help() { #[test] fn test_help_starts_with_usage() { // GNU's `true --help` starts with a "Usage:" line; ensure ours does too. - let result = new_ucmd!().args(&["--help"]).succeeds(); + // NO_COLOR avoids ANSI styling so the raw stdout comparison is stable. + let result = new_ucmd!() + .env("NO_COLOR", "1") + .args(&["--help"]) + .succeeds(); assert!( result .stdout_str()