diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index c2b665830..a048fcda2 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -79,32 +79,14 @@ fn colorize(text: &str, color: Color) -> String { format!("\x1b[{}m{text}\x1b[0m", color.code()) } -pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { - // Ensure localization is initialized for this utility (always with common strings) - let _ = crate::locale::setup_localization(util_name); - - // Check if colors are enabled by examining clap's rendered output - let rendered_str = err.render().to_string(); - let colors_enabled = rendered_str.contains("\x1b["); - - // Helper function to conditionally colorize text - let maybe_colorize = |text: &str, color: Color| -> String { - if colors_enabled { - colorize(text, color) - } else { - text.to_string() - } - }; - +/// Handle DisplayHelp and DisplayVersion errors +fn handle_display_errors(err: Error) -> ! { match err.kind() { ErrorKind::DisplayHelp => { // For help messages, we use the localized help template // The template should already have the localized usage label, // but we also replace any remaining "Usage:" instances for fallback - // Ensure localization is initialized - let _ = crate::locale::setup_localization(util_name); - let help_text = err.render().to_string(); // Replace any remaining "Usage:" with localized version as fallback @@ -120,167 +102,185 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: print!("{}", err.render()); std::process::exit(0); } - ErrorKind::UnknownArgument => { - // Force localization initialization - ignore any previous failures - crate::locale::setup_localization(util_name).ok(); + _ => unreachable!("handle_display_errors called with non-display error"), + } +} - // Choose exit code based on utility name - let exit_code = match util_name { - // These utilities expect exit code 2 for invalid options - "ls" | "dir" | "vdir" | "sort" | "tty" | "printenv" => 2, - // Most utilities expect exit code 1 - _ => 1, - }; +/// Handle UnknownArgument errors with localization and suggestions +fn handle_unknown_argument_error( + err: Error, + util_name: &str, + maybe_colorize: impl Fn(&str, Color) -> String, +) -> ! { + if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { + let arg_str = invalid_arg.to_string(); + // Get localized error word with fallback + let error_word = translate!("common-error"); + let colored_arg = maybe_colorize(&arg_str, Color::Yellow); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + + // Print main error message with fallback + let error_msg = translate!( + "clap-error-unexpected-argument", + "arg" => colored_arg.clone(), + "error_word" => colored_error_word.clone() + ); + eprintln!("{error_msg}"); + eprintln!(); + + // Show suggestion if available + if let Some(suggested_arg) = err.get(ContextKind::SuggestedArg) { + let tip_word = translate!("common-tip"); + let colored_tip_word = maybe_colorize(&tip_word, Color::Green); + let colored_suggestion = maybe_colorize(&suggested_arg.to_string(), Color::Green); + let suggestion_msg = translate!( + "clap-error-similar-argument", + "tip_word" => colored_tip_word.clone(), + "suggestion" => colored_suggestion.clone() + ); + eprintln!("{suggestion_msg}"); + eprintln!(); + } else { // For UnknownArgument, we need to preserve clap's built-in tips (like using -- for values) // while still allowing localization of the main error message let rendered_str = err.render().to_string(); - let _lines: Vec<&str> = rendered_str.lines().collect(); - if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { - let arg_str = invalid_arg.to_string(); - - // Get localized error word with fallback - let error_word = translate!("common-error"); - - let colored_arg = maybe_colorize(&arg_str, Color::Yellow); - let colored_error_word = maybe_colorize(&error_word, Color::Red); - - // Print main error message with fallback - let error_msg = translate!( - "clap-error-unexpected-argument", - "arg" => colored_arg.clone(), - "error_word" => colored_error_word.clone() - ); - eprintln!("{error_msg}"); - eprintln!(); - - // Show suggestion if available - let suggestion = err.get(ContextKind::SuggestedArg); - if let Some(suggested_arg) = suggestion { - let tip_word = translate!("common-tip"); - let colored_tip_word = maybe_colorize(&tip_word, Color::Green); - let colored_suggestion = - maybe_colorize(&suggested_arg.to_string(), Color::Green); - let suggestion_msg = translate!( - "clap-error-similar-argument", - "tip_word" => colored_tip_word.clone(), - "suggestion" => colored_suggestion.clone() - ); - eprintln!("{suggestion_msg}"); + // Look for other clap tips (like "-- --file-with-dash") that aren't suggestions + // These usually start with " tip:" and contain useful information + for line in rendered_str.lines() { + if line.trim_start().starts_with("tip:") && !line.contains("similar argument") { + eprintln!("{line}"); eprintln!(); - } else { - // Look for other clap tips (like "-- --file-with-dash") that aren't suggestions - // These usually start with " tip:" and contain useful information - for line in _lines.iter() { - if line.trim().starts_with("tip:") && !line.contains("similar argument") { - eprintln!("{}", line); - eprintln!(); - } - } } - - // Show usage information for unknown arguments - let usage_key = format!("{util_name}-usage"); - let usage_text = translate!(&usage_key); - let formatted_usage = crate::format_usage(&usage_text); - let usage_label = translate!("common-usage"); - eprintln!("{}: {}", usage_label, formatted_usage); - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - - std::process::exit(exit_code); - } else { - // Generic fallback case - let error_word = translate!("common-error"); - let colored_error_word = maybe_colorize(&error_word, Color::Red); - eprintln!("{colored_error_word}: unexpected argument"); - std::process::exit(exit_code); } } + + // Show usage information for unknown arguments + let usage_key = format!("{util_name}-usage"); + let usage_text = translate!(&usage_key); + let formatted_usage = crate::format_usage(&usage_text); + let usage_label = translate!("common-usage"); + eprintln!("{usage_label}: {formatted_usage}"); + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + } else { + // Generic fallback case + let error_word = translate!("common-error"); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + eprintln!("{colored_error_word}: unexpected argument"); + } + // Choose exit code based on utility name + let exit_code = match util_name { + // These utilities expect exit code 2 for invalid options + "ls" | "dir" | "vdir" | "sort" | "tty" | "printenv" => 2, + // Most utilities expect exit code 1 + _ => 1, + }; + + std::process::exit(exit_code); +} + +/// Handle InvalidValue and ValueValidation errors with localization +fn handle_invalid_value_error(err: Error, maybe_colorize: impl Fn(&str, Color) -> String) -> ! { + // Extract value and option from error context using clap's context API + // This is much more robust than parsing the error string + let invalid_arg = err.get(ContextKind::InvalidArg); + let invalid_value = err.get(ContextKind::InvalidValue); + + if let (Some(arg), Some(value)) = (invalid_arg, invalid_value) { + let option = arg.to_string(); + let value = value.to_string(); + + // Check if this is actually a missing value (empty string) + if value.is_empty() { + // This is the case where no value was provided for an option that requires one + let error_word = translate!("common-error"); + eprintln!( + "{}", + translate!("clap-error-value-required", "error_word" => error_word, "option" => option) + ); + } else { + // Get localized error word and prepare message components outside conditionals + let error_word = translate!("common-error"); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + let colored_value = maybe_colorize(&value, Color::Yellow); + let colored_option = maybe_colorize(&option, Color::Green); + + let error_msg = translate!( + "clap-error-invalid-value", + "error_word" => colored_error_word, + "value" => colored_value, + "option" => colored_option + ); + + // For ValueValidation errors, include the validation error in the message + match err.source() { + Some(source) if matches!(err.kind(), ErrorKind::ValueValidation) => { + eprintln!("{error_msg}: {source}"); + } + _ => eprintln!("{error_msg}"), + } + } + + // For ValueValidation errors, include the validation error details + // Note: We don't print these separately anymore as they're part of the main message + + // Show possible values if available (for InvalidValue errors) + if matches!(err.kind(), ErrorKind::InvalidValue) { + if let Some(valid_values) = err.get(ContextKind::ValidValue) { + if !valid_values.to_string().is_empty() { + // Don't show possible values if they are empty + eprintln!(); + let possible_values_label = translate!("clap-error-possible-values"); + eprintln!(" [{possible_values_label}: {valid_values}]"); + } + } + } + + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + } else { + // Fallback if we can't extract context - use clap's default formatting + let rendered_str = err.render().to_string(); + let lines: Vec<&str> = rendered_str.lines().collect(); + if let Some(main_error_line) = lines.first() { + eprintln!("{main_error_line}"); + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + } else { + eprint!("{}", err.render()); + } + } + std::process::exit(1); +} + +pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { + // Check if colors are enabled by examining clap's rendered output + let rendered_str = err.render().to_string(); + let colors_enabled = rendered_str.contains("\x1b["); + + // Helper function to conditionally colorize text + let maybe_colorize = |text: &str, color: Color| -> String { + if colors_enabled { + colorize(text, color) + } else { + text.to_string() + } + }; + + match err.kind() { + ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { + handle_display_errors(err); + } + ErrorKind::UnknownArgument => { + handle_unknown_argument_error(err, util_name, maybe_colorize); + } // Check if this is a simple validation error that should show simple help kind if should_show_simple_help_for_clap_error(kind) => { // Special handling for InvalidValue and ValueValidation to provide localized error if matches!(kind, ErrorKind::InvalidValue | ErrorKind::ValueValidation) { - // Force localization initialization - crate::locale::setup_localization(util_name).ok(); - - // Extract value and option from error context using clap's context API - // This is much more robust than parsing the error string - let invalid_arg = err.get(ContextKind::InvalidArg); - let invalid_value = err.get(ContextKind::InvalidValue); - - if let (Some(arg), Some(value)) = (invalid_arg, invalid_value) { - let option = arg.to_string(); - let value = value.to_string(); - - // Check if this is actually a missing value (empty string) - if value.is_empty() { - // This is the case where no value was provided for an option that requires one - let error_word = translate!("common-error"); - eprintln!( - "{}", - translate!("clap-error-value-required", "error_word" => error_word, "option" => option) - ); - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - std::process::exit(1); - } else { - // Get localized error word and prepare message components outside conditionals - let error_word = translate!("common-error"); - let colored_error_word = maybe_colorize(&error_word, Color::Red); - let colored_value = maybe_colorize(&value, Color::Yellow); - let colored_option = maybe_colorize(&option, Color::Green); - - let error_msg = translate!( - "clap-error-invalid-value", - "error_word" => colored_error_word, - "value" => colored_value, - "option" => colored_option - ); - - // For ValueValidation errors, include the validation error in the message - if matches!(kind, ErrorKind::ValueValidation) { - if let Some(source) = err.source() { - // Print error with validation detail on same line - eprintln!("{error_msg}: {}", source); - } else { - // Print localized error message - eprintln!("{error_msg}"); - } - } else { - // Print localized error message - eprintln!("{error_msg}"); - } - } - - // For ValueValidation errors, include the validation error details - // Note: We don't print these separately anymore as they're part of the main message - - // Show possible values if available (for InvalidValue errors) - if matches!(kind, ErrorKind::InvalidValue) { - if let Some(valid_values) = err.get(ContextKind::ValidValue) { - eprintln!(); - let possible_values_label = translate!("clap-error-possible-values"); - eprintln!(" [{}: {}]", possible_values_label, valid_values); - } - } - - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - std::process::exit(1); - } else { - // Fallback if we can't extract context - use clap's default formatting - let lines: Vec<&str> = rendered_str.lines().collect(); - if let Some(main_error_line) = lines.first() { - eprintln!("{}", main_error_line); - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - } else { - eprint!("{}", err.render()); - } - std::process::exit(1); - } + handle_invalid_value_error(err, maybe_colorize); } // For other simple validation errors, use the same simple format as other errors