From 98d3dbad4ec2508d77aada6bf9e774b2f1993567 Mon Sep 17 00:00:00 2001 From: WaterWhisperer Date: Tue, 20 Jan 2026 21:24:30 +0800 Subject: [PATCH] join: consider locale collation in field comparison (#9982) Co-authored-by: Sylvestre Ledru --- src/uu/join/Cargo.toml | 2 +- src/uu/join/src/join.rs | 20 ++++++++++++++++++-- src/uucore/src/lib/features/i18n/collator.rs | 5 +++++ tests/by-util/test_join.rs | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 401cb3bb5..8599c5c6a 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -19,7 +19,7 @@ path = "src/join.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["i18n-collator"] } memchr = { workspace = true } thiserror = { workspace = true } fluent = { workspace = true } diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 1360e4a6a..5d2dd2cc9 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -19,6 +19,9 @@ use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; use uucore::format_usage; +use uucore::i18n::collator::{ + AlternateHandling, CollatorOptions, locale_cmp, should_use_locale_collation, try_init_collator, +}; use uucore::line_ending::LineEnding; use uucore::translate; @@ -311,14 +314,16 @@ struct Input { separator: Sep, ignore_case: bool, check_order: CheckOrder, + use_locale: bool, } impl Input { - fn new(separator: Sep, ignore_case: bool, check_order: CheckOrder) -> Self { + fn new(separator: Sep, ignore_case: bool, check_order: CheckOrder, use_locale: bool) -> Self { Self { separator, ignore_case, check_order, + use_locale, } } @@ -328,6 +333,8 @@ impl Input { let field1 = CaseInsensitiveSlice { v: field1 }; let field2 = CaseInsensitiveSlice { v: field2 }; field1.cmp(&field2) + } else if self.use_locale { + locale_cmp(field1, field2) } else { field1.cmp(field2) } @@ -823,6 +830,10 @@ fn parse_settings(matches: &clap::ArgMatches) -> UResult { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; + let mut opts = CollatorOptions::default(); + opts.alternate_handling = Some(AlternateHandling::Shifted); + let _ = try_init_collator(opts); + let settings = parse_settings(&matches)?; let file1 = matches.get_one::("file1").unwrap(); @@ -989,7 +1000,12 @@ fn exec( settings.print_unpaired2, )?; - let input = Input::new(sep.clone(), settings.ignore_case, settings.check_order); + let input = Input::new( + sep.clone(), + settings.ignore_case, + settings.check_order, + should_use_locale_collation(), + ); let format = if settings.autoformat { let mut format = vec![Spec::Key]; diff --git a/src/uucore/src/lib/features/i18n/collator.rs b/src/uucore/src/lib/features/i18n/collator.rs index f0a9e6b35..37868ed3b 100644 --- a/src/uucore/src/lib/features/i18n/collator.rs +++ b/src/uucore/src/lib/features/i18n/collator.rs @@ -30,6 +30,11 @@ pub fn init_collator(opts: CollatorOptions) { .expect("Collator already initialized"); } +/// Check if locale collation should be used. +pub fn should_use_locale_collation() -> bool { + get_collating_locale().0 != DEFAULT_LOCALE +} + /// Initialize the collator for locale-aware string comparison if needed. /// /// This function checks if the current locale requires locale-aware collation diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index 9041cb560..a0a061b6b 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -580,3 +580,22 @@ fn join_emoji_delim_inner_key() { .succeeds() .stdout_only("b🗿a🗿u\n"); } + +#[cfg(unix)] +#[test] +fn test_locale_collation() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("f1.sorted", "abc:d 2\nab:d 1\n"); + at.write("f2.sorted", "abc:d y\nab:d x\n"); + + ts.ucmd() + .env("LC_ALL", "en_US.UTF-8") + .arg("--check-order") + .arg("f1.sorted") + .arg("f2.sorted") + .succeeds() + .stdout_contains("abc:d 2 y") + .stdout_contains("ab:d 1 x"); +}