clap/local: split the functions

This commit is contained in:
Sylvestre Ledru
2025-08-27 21:34:44 +02:00
parent 60b3aad5f9
commit 3d09ef88cb
+167 -167
View File
@@ -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