Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 55 additions & 31 deletions src/uu/ln/src/ln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
use uucore::error::{FromIo, UError, UResult};
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
use uucore::translate;
use uucore::{format_usage, prompt_yes, show_error};
use uucore::{prompt_yes, show_error};
use uucore::clap_localization::localize_command;

use std::borrow::Cow;
use std::collections::HashSet;
Expand Down Expand Up @@ -91,7 +92,36 @@

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let cmd = uu_app();
let matches = cmd.clone().try_get_matches_from(args);

let matches = match matches {
Ok(m) => {
if m.get_flag("help") {
localize_command(cmd).print_help().unwrap();
println!();
return Ok(());
}
m
}
Err(e) => {
use clap::error::ErrorKind;
match e.kind() {
ErrorKind::DisplayHelp => {
localize_command(cmd).print_help().unwrap();
println!();
return Ok(());
}
ErrorKind::DisplayVersion => {
print!("{}", e.render());
return Ok(());
}
_ => {
return Err(uucore::clap_localization::clap_error_to_uerror(e));

Check failure on line 120 in src/uu/ln/src/ln.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'uerror' (file:'src/uu/ln/src/ln.rs', line:120)
}
}
}
};

/* the list of files */

Expand Down Expand Up @@ -135,71 +165,65 @@
exec(&paths[..], &settings)
}

/// Build the clap Command with translation keys (not translated strings).
/// Translation only happens when help is actually displayed.
pub fn uu_app() -> Command {
let after_help = format!(
"{}\n\n{}",
translate!("ln-after-help"),
backup_control::BACKUP_CONTROL_LONG_HELP
);

Command::new(uucore::util_name())
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template(uucore::util_name()))
.about(translate!("ln-about"))
.override_usage(format_usage(&translate!("ln-usage")))
.disable_help_flag(true)
.about("ln-about") // key, not translated
.after_help("ln-after-help") // key
.infer_long_args(true)
.after_help(after_help)
.arg(
Arg::new("help")
.short('h')
.long("help")
.help("help-help") // key
.action(ArgAction::SetTrue),
)
.arg(backup_control::arguments::backup())
.arg(backup_control::arguments::backup_no_args())
/*.arg(
Arg::new(options::DIRECTORY)
.short('d')
.long(options::DIRECTORY)
.help("allow users with appropriate privileges to attempt to make hard links to directories")
)*/
.arg(
Arg::new(options::FORCE)
.short('f')
.long(options::FORCE)
.help(translate!("ln-help-force"))
.help("ln-help-force") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::INTERACTIVE)
.short('i')
.long(options::INTERACTIVE)
.help(translate!("ln-help-interactive"))
.help("ln-help-interactive") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::NO_DEREFERENCE)
.short('n')
.long(options::NO_DEREFERENCE)
.help(translate!("ln-help-no-dereference"))
.help("ln-help-no-dereference") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::LOGICAL)
.short('L')
.long(options::LOGICAL)
.help(translate!("ln-help-logical"))
.help("ln-help-logical") // key
.overrides_with(options::PHYSICAL)
.action(ArgAction::SetTrue),
)
.arg(
// Not implemented yet
Arg::new(options::PHYSICAL)
.short('P')
.long(options::PHYSICAL)
.help(translate!("ln-help-physical"))
.help("ln-help-physical") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SYMBOLIC)
.short('s')
.long(options::SYMBOLIC)
.help(translate!("ln-help-symbolic"))
// override added for https://github.com/uutils/coreutils/issues/2359
.help("ln-help-symbolic") // key
.overrides_with(options::SYMBOLIC)
.action(ArgAction::SetTrue),
)
Expand All @@ -208,7 +232,7 @@
Arg::new(options::TARGET_DIRECTORY)
.short('t')
.long(options::TARGET_DIRECTORY)
.help(translate!("ln-help-target-directory"))
.help("ln-help-target-directory") // key
.value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
.value_parser(clap::value_parser!(OsString))
Expand All @@ -218,30 +242,30 @@
Arg::new(options::NO_TARGET_DIRECTORY)
.short('T')
.long(options::NO_TARGET_DIRECTORY)
.help(translate!("ln-help-no-target-directory"))
.help("ln-help-no-target-directory") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::RELATIVE)
.short('r')
.long(options::RELATIVE)
.help(translate!("ln-help-relative"))
.help("ln-help-relative") // key
.requires(options::SYMBOLIC)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::VERBOSE)
.short('v')
.long(options::VERBOSE)
.help(translate!("ln-help-verbose"))
.help("ln-help-verbose") // key
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(ARG_FILES)
.action(ArgAction::Append)
.value_hint(clap::ValueHint::AnyPath)
.value_parser(clap::value_parser!(OsString))
.required(true)
.required_unless_present("help")
.num_args(1..),
)
}
Expand Down
17 changes: 5 additions & 12 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,13 @@ macro_rules! bin {
($util:ident) => {
pub fn main() {
use std::io::Write;
use uucore::locale;
// suppress extraneous error output for SIGPIPE failures/panics
uucore::panic::mute_sigpipe_panic();
locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
.unwrap_or_else(|err| {
match err {
uucore::locale::LocalizationError::ParseResource {
error: err_msg,
snippet,
} => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
other => eprintln!("Could not init the localization system: {other}"),
}
std::process::exit(99)
});

// Store util name for lazy localization initialization
uucore::locale::set_util_name_for_lazy_init(
uucore::get_canonical_util_name(stringify!($util)),
);

// execute utility code
let code = $util::uumain(uucore::args_os());
Expand Down
103 changes: 103 additions & 0 deletions src/uucore/src/lib/mods/clap_localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,109 @@
cmd
}

/// Localizes a clap `Command` by translating all keys to actual strings.
///
/// This function is the "layer between clap and translations". It takes a Command
/// that was built with translation keys (not translated strings) and converts
/// those keys to actual translated strings just before displaying help.
///
/// # Arguments
///
/// * `cmd` - The clap `Command` with translation keys in about/help fields
///
/// # Returns
///
/// A new `Command` with all keys translated and proper formatting applied.
///
/// # Example
///
/// ```no_run
/// use clap::{Arg, ArgAction, Command};
/// use uucore::clap_localization::localize_command;
///
/// // Build command with keys (not translated strings)
/// let cmd = Command::new("myutil")
/// .about("myutil-about") // key, not translated
/// .arg(Arg::new("verbose")
/// .short('v')
/// .help("myutil-help-verbose")); // key
///
/// // Only translate when actually displaying help
/// if user_requested_help {
/// localize_command(cmd).print_help().unwrap();
/// }
/// ```
pub fn localize_command(mut cmd: Command) -> Command {
let util_name = crate::util_name();

// Translate about text
if let Some(about) = cmd.get_about().map(|s| s.to_string()) {
cmd = cmd.about(translate!(&about));
}

// Translate after_help text
if let Some(after_help) = cmd.get_after_help().map(|s| s.to_string()) {
cmd = cmd.after_help(translate!(&after_help));
}

// Set localized usage
let usage_key = format!("{util_name}-usage");
cmd = cmd.override_usage(crate::format_usage(&translate!(&usage_key)));

// Translate arg help texts
let arg_ids: Vec<_> = cmd.get_arguments().map(|a| a.get_id().clone()).collect();
for id in arg_ids {
cmd = cmd.mut_arg(&id, |arg| {
let mut arg = arg;
if let Some(help_key) = arg.get_help().map(|s| s.to_string()) {
arg = arg.help(translate!(&help_key));
}
if let Some(long_help_key) = arg.get_long_help().map(|s| s.to_string()) {
arg = arg.long_help(translate!(&long_help_key));
}
arg
});
}

// Apply color and help template
configure_localized_command(cmd)
}

/// Converts a clap Error to a UError for use in UResult return types.
///
/// This is useful when manually handling clap parsing and needing to
/// return errors through the UResult system.
pub fn clap_error_to_uerror(err: Error) -> Box<dyn crate::error::UError> {

Check failure on line 636 in src/uucore/src/lib/mods/clap_localization.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'uerror' (file:'src/uucore/src/lib/mods/clap_localization.rs', line:636)
Box::new(ClapErrorWrapper(err))
}

/// Wrapper to convert clap::Error into UError
struct ClapErrorWrapper(Error);

impl std::fmt::Display for ClapErrorWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl std::fmt::Debug for ClapErrorWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl std::error::Error for ClapErrorWrapper {}

impl crate::error::UError for ClapErrorWrapper {
fn code(&self) -> i32 {
if self.0.exit_code() == 0 { 0 } else { 1 }
}

fn usage(&self) -> bool {
true
}
}

/* spell-checker: disable */
#[cfg(test)]
mod tests {
Expand Down
22 changes: 22 additions & 0 deletions src/uucore/src/lib/mods/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,27 @@ thread_local! {
static LOCALIZER: OnceLock<Localizer> = const { OnceLock::new() };
}

// Store util name for lazy initialization
static UTIL_NAME_FOR_LAZY_INIT: std::sync::OnceLock<String> = std::sync::OnceLock::new();

/// Store the utility name for lazy localization initialization.
/// Called from bin! macro before uumain.
pub fn set_util_name_for_lazy_init(name: &str) {
let _ = UTIL_NAME_FOR_LAZY_INIT.set(name.to_string());
}

/// Ensure localization is initialized (lazy initialization).
/// Called automatically by translate! macro when needed.
fn ensure_initialized() {
LOCALIZER.with(|lock| {
if lock.get().is_none() {
if let Some(util_name) = UTIL_NAME_FOR_LAZY_INIT.get() {
let _ = setup_localization(util_name);
}
}
});
}

/// Helper function to find the uucore locales directory from a utility's locales directory
fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option<PathBuf> {
// Normalize the path to get absolute path
Expand Down Expand Up @@ -262,6 +283,7 @@ fn create_english_bundle_from_embedded(
}

fn get_message_internal(id: &str, args: Option<FluentArgs>) -> String {
ensure_initialized();
LOCALIZER.with(|lock| {
lock.get()
.map(|loc| loc.format(id, args.as_ref()))
Expand Down
Loading