1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

l10n: port sort for translation + add french

This commit is contained in:
Sylvestre Ledru 2025-06-22 17:35:16 +02:00
parent 05eb7ee374
commit 5be0db5e8c
4 changed files with 361 additions and 96 deletions

View file

@ -9,3 +9,75 @@ sort-after-help = The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS
If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position. If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position.
Valid options are: MbdfhnRrV. They override the global options for this key. Valid options are: MbdfhnRrV. They override the global options for this key.
# Error messages
sort-open-failed = open failed: {$path}: {$error}
sort-parse-key-error = failed to parse key {$key}: {$msg}
sort-cannot-read = cannot read: {$path}: {$error}
sort-open-tmp-file-failed = failed to open temporary file: {$error}
sort-compress-prog-execution-failed = couldn't execute compress program: errno {$code}
sort-compress-prog-terminated-abnormally = {$prog} terminated abnormally
sort-cannot-create-tmp-file = cannot create temporary file in '{$path}':
sort-file-operands-combined = extra operand '{$file}'
file operands cannot be combined with --files0-from
Try '{$help} --help' for more information.
sort-multiple-output-files = multiple output files specified
sort-minus-in-stdin = when reading file names from stdin, no file name of '-' allowed
sort-no-input-from = no input from '{$file}'
sort-invalid-zero-length-filename = {$file}:{$line_num}: invalid zero-length file name
sort-options-incompatible = options '-{$opt1}{$opt2}' are incompatible
sort-invalid-key = invalid key {$key}
sort-failed-parse-field-index = failed to parse field index {$field} {$error}
sort-field-index-cannot-be-zero = field index can not be 0
sort-failed-parse-char-index = failed to parse character index {$char}: {$error}
sort-invalid-option = invalid option: '{$option}'
sort-invalid-char-index-zero-start = invalid character index 0 for the start position of a field
sort-invalid-batch-size-arg = invalid --batch-size argument '{$arg}'
sort-minimum-batch-size-two = minimum --batch-size argument is '2'
sort-batch-size-too-large = --batch-size argument {$arg} too large
sort-maximum-batch-size-rlimit = maximum --batch-size argument with current rlimit is {$rlimit}
sort-extra-operand-not-allowed-with-c = extra operand {$operand} not allowed with -c
sort-separator-not-valid-unicode = separator is not valid unicode: {$arg}
sort-separator-must-be-one-char = separator must be exactly one character long: {$separator}
sort-only-one-file-allowed-with-c = only one file allowed with -c
sort-failed-fetch-rlimit = Failed to fetch rlimit
sort-invalid-suffix-in-option-arg = invalid suffix in --{$option} argument {$arg}
sort-invalid-option-arg = invalid --{$option} argument {$arg}
sort-option-arg-too-large = --{$option} argument {$arg} too large
sort-error-disorder = {$file}:{$line_number}: disorder: {$line}
sort-error-buffer-size-too-big = Buffer size {$size} does not fit in address space
sort-error-no-match-for-key = ^ no match for key
sort-error-write-failed = write failed: {$output}
sort-failed-to-delete-temporary-directory = failed to delete temporary directory: {$error}
sort-failed-to-set-up-signal-handler = failed to set up signal handler: {$error}
# Help messages
sort-help-help = Print help information.
sort-help-version = Print version information.
sort-help-human-numeric = compare according to human readable sizes, eg 1M > 100k
sort-help-month = compare according to month name abbreviation
sort-help-numeric = compare according to string numerical value
sort-help-general-numeric = compare according to string general numerical value
sort-help-version-sort = Sort by SemVer version number, eg 1.12.2 > 1.1.2
sort-help-random = shuffle in random order
sort-help-dictionary-order = consider only blanks and alphanumeric characters
sort-help-merge = merge already sorted files; do not sort
sort-help-check = check for sorted input; do not sort
sort-help-check-silent = exit successfully if the given file is already sorted, and exit with status 1 otherwise.
sort-help-ignore-case = fold lower case to upper case characters
sort-help-ignore-nonprinting = ignore nonprinting characters
sort-help-ignore-leading-blanks = ignore leading blanks when finding sort keys in each line
sort-help-output = write output to FILENAME instead of stdout
sort-help-reverse = reverse the output
sort-help-stable = stabilize sort by disabling last-resort comparison
sort-help-unique = output only the first of an equal run
sort-help-key = sort by a key
sort-help-separator = custom separator for -k
sort-help-zero-terminated = line delimiter is NUL, not newline
sort-help-parallel = change the number of threads running concurrently to NUM_THREADS
sort-help-buf-size = sets the maximum SIZE of each segment in number of sorted items
sort-help-tmp-dir = use DIR for temporaries, not $TMPDIR or /tmp
sort-help-compress-prog = compress temporary files with PROG, decompress with PROG -d; PROG has to take input from stdin and output to stdout
sort-help-batch-size = Merge at most N_MERGE inputs at once.
sort-help-files0-from = read input from the files specified by NUL-terminated NUL_FILE
sort-help-debug = underline the parts of the line that are actually used for sorting

View file

@ -0,0 +1,83 @@
sort-about = Affiche la concaténation triée de tous les FICHIER(s). Sans FICHIER, ou quand FICHIER est -, lit l'entrée standard.
sort-usage = sort [OPTION]... [FICHIER]...
sort-after-help = Le format de clé est CHAMP[.CAR][OPTIONS][,CHAMP[.CAR]][OPTIONS].
Les champs sont séparés par défaut par le premier espace blanc après un caractère non-espace. Utilisez -t pour spécifier un séparateur personnalisé.
Dans le cas par défaut, les espaces blancs sont ajoutés au début de chaque champ. Les séparateurs personnalisés ne sont cependant pas inclus dans les champs.
CHAMP et CAR commencent tous deux à 1 (c'est-à-dire qu'ils sont indexés à partir de 1). S'il n'y a pas de fin spécifiée après une virgule, la fin sera la fin de la ligne.
Si CAR est défini à 0, cela signifie la fin du champ. CAR par défaut à 1 pour la position de début et à 0 pour la position de fin.
Les options valides sont : MbdfhnRrV. Elles remplacent les options globales pour cette clé.
# Messages d'erreur
sort-open-failed = échec d'ouverture : {$path} : {$error}
sort-parse-key-error = échec d'analyse de la clé {$key} : {$msg}
sort-cannot-read = impossible de lire : {$path} : {$error}
sort-open-tmp-file-failed = échec d'ouverture du fichier temporaire : {$error}
sort-compress-prog-execution-failed = impossible d'exécuter le programme de compression : errno {$code}
sort-compress-prog-terminated-abnormally = {$prog} s'est terminé anormalement
sort-cannot-create-tmp-file = impossible de créer un fichier temporaire dans '{$path}' :
sort-file-operands-combined = opérande supplémentaire '{$file}'
les opérandes de fichier ne peuvent pas être combinées avec --files0-from
Essayez '{$help} --help' pour plus d'informations.
sort-multiple-output-files = plusieurs fichiers de sortie spécifiés
sort-minus-in-stdin = lors de la lecture des noms de fichiers depuis stdin, aucun nom de fichier '-' n'est autorisé
sort-no-input-from = aucune entrée depuis '{$file}'
sort-invalid-zero-length-filename = {$file}:{$line_num} : nom de fichier de longueur zéro invalide
sort-options-incompatible = les options '-{$opt1}{$opt2}' sont incompatibles
sort-invalid-key = clé invalide {$key}
sort-failed-parse-field-index = échec d'analyse de l'index de champ {$field} {$error}
sort-field-index-cannot-be-zero = l'index de champ ne peut pas être 0
sort-failed-parse-char-index = échec d'analyse de l'index de caractère {$char} : {$error}
sort-invalid-option = option invalide : '{$option}'
sort-invalid-char-index-zero-start = index de caractère 0 invalide pour la position de début d'un champ
sort-invalid-batch-size-arg = argument --batch-size invalide '{$arg}'
sort-minimum-batch-size-two = l'argument --batch-size minimum est '2'
sort-batch-size-too-large = argument --batch-size {$arg} trop grand
sort-maximum-batch-size-rlimit = argument --batch-size maximum avec la rlimit actuelle est {$rlimit}
sort-extra-operand-not-allowed-with-c = opérande supplémentaire {$operand} non autorisée avec -c
sort-separator-not-valid-unicode = le séparateur n'est pas un unicode valide : {$arg}
sort-separator-must-be-one-char = le séparateur doit faire exactement un caractère de long : {$separator}
sort-only-one-file-allowed-with-c = un seul fichier autorisé avec -c
sort-failed-fetch-rlimit = Échec de récupération de rlimit
sort-invalid-suffix-in-option-arg = suffixe invalide dans l'argument --{$option} {$arg}
sort-invalid-option-arg = argument --{$option} invalide {$arg}
sort-option-arg-too-large = argument --{$option} {$arg} trop grand
sort-error-disorder = {$file}:{$line_number}: désordre : {$line}
sort-error-buffer-size-too-big = La taille du tampon {$size} ne rentre pas dans l'espace d'adressage
sort-error-no-match-for-key = ^ aucune correspondance pour la clé
sort-error-write-failed = échec d'écriture : {$output}
sort-failed-to-delete-temporary-directory = échec de suppression du répertoire temporaire : {$error}
sort-failed-to-set-up-signal-handler = échec de configuration du gestionnaire de signal : {$error}
# Messages d'aide
sort-help-help = Affiche les informations d'aide.
sort-help-version = Affiche les informations de version.
sort-help-human-numeric = compare selon les tailles lisibles par l'humain, par ex. 1M > 100k
sort-help-month = compare selon l'abréviation du nom du mois
sort-help-numeric = compare selon la valeur numérique de la chaîne
sort-help-general-numeric = compare selon la valeur numérique générale de la chaîne
sort-help-version-sort = Trie par numéro de version SemVer, par ex. 1.12.2 > 1.1.2
sort-help-random = mélange dans un ordre aléatoire
sort-help-dictionary-order = considère seulement les espaces et les caractères alphanumériques
sort-help-merge = fusionne les fichiers déjà triés ; ne trie pas
sort-help-check = vérifie l'entrée triée ; ne trie pas
sort-help-check-silent = réussit si le fichier donné est déjà trié, et sort avec le statut 1 sinon.
sort-help-ignore-case = convertit les caractères minuscules en majuscules
sort-help-ignore-nonprinting = ignore les caractères non-imprimables
sort-help-ignore-leading-blanks = ignore les espaces de début lors de la recherche de clés de tri dans chaque ligne
sort-help-output = écrit la sortie vers NOMFICHIER au lieu de stdout
sort-help-reverse = inverse la sortie
sort-help-stable = stabilise le tri en désactivant la comparaison de dernier recours
sort-help-unique = affiche seulement le premier d'une série égale
sort-help-key = trie par une clé
sort-help-separator = séparateur personnalisé pour -k
sort-help-zero-terminated = le délimiteur de ligne est NUL, pas nouvelle ligne
sort-help-parallel = change le nombre de threads s'exécutant simultanément vers NUM_THREADS
sort-help-buf-size = définit la TAILLE maximale de chaque segment en nombre d'éléments triés
sort-help-tmp-dir = utilise RÉP pour les temporaires, pas $TMPDIR ou /tmp
sort-help-compress-prog = compresse les fichiers temporaires avec PROG, décompresse avec PROG -d ; PROG doit prendre l'entrée depuis stdin et sortir vers stdout
sort-help-batch-size = Fusionne au maximum N_MERGE entrées à la fois.
sort-help-files0-from = lit l'entrée depuis les fichiers spécifiés par FICHIER_NUL terminé par NUL
sort-help-debug = souligne les parties de la ligne qui sont réellement utilisées pour le tri

View file

@ -30,6 +30,7 @@ use numeric_str_cmp::{NumInfo, NumInfoParseSettings, human_numeric_str_cmp, nume
use rand::{Rng, rng}; use rand::{Rng, rng};
use rayon::prelude::*; use rayon::prelude::*;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap;
use std::env; use std::env;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
@ -55,7 +56,7 @@ use uucore::{format_usage, show_error};
use crate::tmp_dir::TmpDirWrapper; use crate::tmp_dir::TmpDirWrapper;
use uucore::locale::get_message; use uucore::locale::{get_message, get_message_with_args};
mod options { mod options {
pub mod modes { pub mod modes {
@ -131,49 +132,49 @@ pub enum SortError {
silent: bool, silent: bool,
}, },
#[error("open failed: {}: {}", .path.maybe_quote(), strip_errno(.error))] #[error("{}", get_message_with_args("sort-open-failed", HashMap::from([("path".to_string(), format!("{}", .path.maybe_quote())), ("error".to_string(), strip_errno(.error))])))]
OpenFailed { OpenFailed {
path: PathBuf, path: PathBuf,
error: std::io::Error, error: std::io::Error,
}, },
#[error("failed to parse key {}: {}", .key.quote(), .msg)] #[error("{}", get_message_with_args("sort-parse-key-error", HashMap::from([("key".to_string(), .key.quote().to_string()), ("msg".to_string(), .msg.clone())])))]
ParseKeyError { key: String, msg: String }, ParseKeyError { key: String, msg: String },
#[error("cannot read: {}: {}", .path.maybe_quote(), strip_errno(.error))] #[error("{}", get_message_with_args("sort-cannot-read", HashMap::from([("path".to_string(), format!("{}", .path.maybe_quote())), ("error".to_string(), strip_errno(.error))])))]
ReadFailed { ReadFailed {
path: PathBuf, path: PathBuf,
error: std::io::Error, error: std::io::Error,
}, },
#[error("failed to open temporary file: {}", strip_errno(.error))] #[error("{}", get_message_with_args("sort-open-tmp-file-failed", HashMap::from([("error".to_string(), strip_errno(.error))])))]
OpenTmpFileFailed { error: std::io::Error }, OpenTmpFileFailed { error: std::io::Error },
#[error("couldn't execute compress program: errno {code}")] #[error("{}", get_message_with_args("sort-compress-prog-execution-failed", HashMap::from([("code".to_string(), .code.to_string())])))]
CompressProgExecutionFailed { code: i32 }, CompressProgExecutionFailed { code: i32 },
#[error("{} terminated abnormally", .prog.quote())] #[error("{}", get_message_with_args("sort-compress-prog-terminated-abnormally", HashMap::from([("prog".to_string(), .prog.quote().to_string())])))]
CompressProgTerminatedAbnormally { prog: String }, CompressProgTerminatedAbnormally { prog: String },
#[error("cannot create temporary file in '{}':", .path.display())] #[error("{}", get_message_with_args("sort-cannot-create-tmp-file", HashMap::from([("path".to_string(), format!("{}", .path.display()))])))]
TmpFileCreationFailed { path: PathBuf }, TmpFileCreationFailed { path: PathBuf },
#[error("extra operand '{}'\nfile operands cannot be combined with --files0-from\nTry '{} --help' for more information.", .file.display(), uucore::execution_phrase())] #[error("{}", get_message_with_args("sort-file-operands-combined", HashMap::from([("file".to_string(), format!("{}", .file.display())), ("help".to_string(), uucore::execution_phrase().to_string())])))]
FileOperandsCombined { file: PathBuf }, FileOperandsCombined { file: PathBuf },
#[error("{error}")] #[error("{error}")]
Uft8Error { error: Utf8Error }, Uft8Error { error: Utf8Error },
#[error("multiple output files specified")] #[error("{}", get_message("sort-multiple-output-files"))]
MultipleOutputFiles, MultipleOutputFiles,
#[error("when reading file names from stdin, no file name of '-' allowed")] #[error("{}", get_message("sort-minus-in-stdin"))]
MinusInStdIn, MinusInStdIn,
#[error("no input from '{}'", .file.display())] #[error("{}", get_message_with_args("sort-no-input-from", HashMap::from([("file".to_string(), format!("{}", .file.display()))])))]
EmptyInputFile { file: PathBuf }, EmptyInputFile { file: PathBuf },
#[error("{}:{}: invalid zero-length file name", .file.display(), .line_num)] #[error("{}", get_message_with_args("sort-invalid-zero-length-filename", HashMap::from([("file".to_string(), format!("{}", .file.display())), ("line_num".to_string(), .line_num.to_string())])))]
ZeroLengthFileName { file: PathBuf, line_num: usize }, ZeroLengthFileName { file: PathBuf, line_num: usize },
} }
@ -190,7 +191,14 @@ fn format_disorder(file: &OsString, line_number: &usize, line: &String, silent:
if *silent { if *silent {
String::new() String::new()
} else { } else {
format!("{}:{}: disorder: {line}", file.maybe_quote(), line_number) get_message_with_args(
"sort-error-disorder",
HashMap::from([
("file".to_string(), file.maybe_quote().to_string()),
("line_number".to_string(), line_number.to_string()),
("line".to_string(), line.to_owned()),
]),
)
} }
} }
@ -316,7 +324,10 @@ impl GlobalSettings {
.parse(input.trim())?; .parse(input.trim())?;
usize::try_from(size).map_err(|_| { usize::try_from(size).map_err(|_| {
ParseSizeError::SizeTooBig(format!("Buffer size {size} does not fit in address space")) ParseSizeError::SizeTooBig(get_message_with_args(
"sort-error-buffer-size-too-big",
HashMap::from([("size".to_string(), size.to_string())]),
))
}) })
} }
@ -390,16 +401,26 @@ impl KeySettings {
SortMode::Numeric | SortMode::HumanNumeric | SortMode::GeneralNumeric | SortMode::Month SortMode::Numeric | SortMode::HumanNumeric | SortMode::GeneralNumeric | SortMode::Month
) { ) {
if dictionary_order { if dictionary_order {
return Err(format!( return Err(get_message_with_args(
"options '-{}{}' are incompatible", "sort-options-incompatible",
'd', HashMap::from([
mode.get_short_name().unwrap() ("opt1".to_string(), "d".to_string()),
(
"opt2".to_string(),
mode.get_short_name().unwrap().to_string(),
),
]),
)); ));
} else if ignore_non_printing { } else if ignore_non_printing {
return Err(format!( return Err(get_message_with_args(
"options '-{}{}' are incompatible", "sort-options-incompatible",
'i', HashMap::from([
mode.get_short_name().unwrap() ("opt1".to_string(), "i".to_string()),
(
"opt2".to_string(),
mode.get_short_name().unwrap().to_string(),
),
]),
)); ));
} }
} }
@ -408,10 +429,18 @@ impl KeySettings {
fn set_sort_mode(&mut self, mode: SortMode) -> Result<(), String> { fn set_sort_mode(&mut self, mode: SortMode) -> Result<(), String> {
if self.mode != SortMode::Default && self.mode != mode { if self.mode != SortMode::Default && self.mode != mode {
return Err(format!( return Err(get_message_with_args(
"options '-{}{}' are incompatible", "sort-options-incompatible",
self.mode.get_short_name().unwrap(), HashMap::from([
mode.get_short_name().unwrap() (
"opt1".to_string(),
self.mode.get_short_name().unwrap().to_string(),
),
(
"opt2".to_string(),
mode.get_short_name().unwrap().to_string(),
),
]),
)); ));
} }
Self::check_compatibility(mode, self.ignore_non_printing, self.dictionary_order)?; Self::check_compatibility(mode, self.ignore_non_printing, self.dictionary_order)?;
@ -624,7 +653,7 @@ impl<'a> Line<'a> {
)?; )?;
if selection.is_empty() { if selection.is_empty() {
writeln!(writer, "^ no match for key")?; writeln!(writer, "{}", get_message("sort-error-no-match-for-key"))?;
} else { } else {
writeln!( writeln!(
writer, writer,
@ -649,7 +678,7 @@ impl<'a> Line<'a> {
{ {
// A last resort comparator is in use, underline the whole line. // A last resort comparator is in use, underline the whole line.
if self.line.is_empty() { if self.line.is_empty() {
writeln!(writer, "^ no match for key")?; writeln!(writer, "{}", get_message("sort-error-no-match-for-key"))?;
} else { } else {
writeln!( writeln!(
writer, writer,
@ -722,25 +751,41 @@ impl KeyPosition {
fn new(key: &str, default_char_index: usize, ignore_blanks: bool) -> Result<Self, String> { fn new(key: &str, default_char_index: usize, ignore_blanks: bool) -> Result<Self, String> {
let mut field_and_char = key.split('.'); let mut field_and_char = key.split('.');
let field = field_and_char let field = field_and_char.next().ok_or_else(|| {
.next() get_message_with_args(
.ok_or_else(|| format!("invalid key {}", key.quote()))?; "sort-invalid-key",
HashMap::from([("key".to_string(), key.quote().to_string())]),
)
})?;
let char = field_and_char.next(); let char = field_and_char.next();
let field = match field.parse::<usize>() { let field = match field.parse::<usize>() {
Ok(f) => f, Ok(f) => f,
Err(e) if *e.kind() == IntErrorKind::PosOverflow => usize::MAX, Err(e) if *e.kind() == IntErrorKind::PosOverflow => usize::MAX,
Err(e) => { Err(e) => {
return Err(format!("failed to parse field index {} {e}", field.quote(),)); return Err(get_message_with_args(
"sort-failed-parse-field-index",
HashMap::from([
("field".to_string(), field.quote().to_string()),
("error".to_string(), e.to_string()),
]),
));
} }
}; };
if field == 0 { if field == 0 {
return Err("field index can not be 0".to_string()); return Err(get_message("sort-field-index-cannot-be-zero"));
} }
let char = char.map_or(Ok(default_char_index), |char| { let char = char.map_or(Ok(default_char_index), |char| {
char.parse() char.parse().map_err(|e: std::num::ParseIntError| {
.map_err(|e| format!("failed to parse character index {}: {e}", char.quote())) get_message_with_args(
"sort-failed-parse-char-index",
HashMap::from([
("char".to_string(), char.quote().to_string()),
("error".to_string(), e.to_string()),
]),
)
})
})?; })?;
Ok(Self { Ok(Self {
@ -839,7 +884,12 @@ impl FieldSelector {
'R' => key_settings.set_sort_mode(SortMode::Random)?, 'R' => key_settings.set_sort_mode(SortMode::Random)?,
'r' => key_settings.reverse = true, 'r' => key_settings.reverse = true,
'V' => key_settings.set_sort_mode(SortMode::Version)?, 'V' => key_settings.set_sort_mode(SortMode::Version)?,
c => return Err(format!("invalid option: '{c}'")), c => {
return Err(get_message_with_args(
"sort-invalid-option",
HashMap::from([("option".to_string(), c.to_string())]),
));
}
} }
} }
Ok(ignore_blanks) Ok(ignore_blanks)
@ -865,7 +915,7 @@ impl FieldSelector {
settings: KeySettings, settings: KeySettings,
) -> Result<Self, String> { ) -> Result<Self, String> {
if from.char == 0 { if from.char == 0 {
Err("invalid character index 0 for the start position of a field".to_string()) Err(get_message("sort-invalid-char-index-zero-start"))
} else { } else {
Ok(Self { Ok(Self {
needs_selection: (from.field != 1 needs_selection: (from.field != 1
@ -1007,7 +1057,7 @@ impl FieldSelector {
} }
/// Creates an `Arg` that conflicts with all other sort modes. /// Creates an `Arg` that conflicts with all other sort modes.
fn make_sort_mode_arg(mode: &'static str, short: char, help: &'static str) -> Arg { fn make_sort_mode_arg(mode: &'static str, short: char, help: String) -> Arg {
let mut arg = Arg::new(mode) let mut arg = Arg::new(mode)
.short(short) .short(short)
.long(mode) .long(mode)
@ -1029,7 +1079,7 @@ fn get_rlimit() -> UResult<usize> {
}; };
match unsafe { getrlimit(RLIMIT_NOFILE, &mut limit) } { match unsafe { getrlimit(RLIMIT_NOFILE, &mut limit) } {
0 => Ok(limit.rlim_cur as usize), 0 => Ok(limit.rlim_cur as usize),
_ => Err(UUsageError::new(2, "Failed to fetch rlimit")), _ => Err(UUsageError::new(2, get_message("sort-failed-fetch-rlimit"))),
} }
} }
@ -1200,8 +1250,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
match n_merge.parse::<usize>() { match n_merge.parse::<usize>() {
Ok(parsed_value) => { Ok(parsed_value) => {
if parsed_value < 2 { if parsed_value < 2 {
show_error!("invalid --batch-size argument '{n_merge}'"); show_error!(
return Err(UUsageError::new(2, "minimum --batch-size argument is '2'")); "{}",
get_message_with_args(
"sort-invalid-batch-size-arg",
HashMap::from([("arg".to_string(), n_merge.to_string())])
)
);
return Err(UUsageError::new(
2,
get_message("sort-minimum-batch-size-two"),
));
} }
settings.merge_batch_size = parsed_value; settings.merge_batch_size = parsed_value;
} }
@ -1209,19 +1268,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let error_message = if *e.kind() == IntErrorKind::PosOverflow { let error_message = if *e.kind() == IntErrorKind::PosOverflow {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
show_error!("--batch-size argument {} too large", n_merge.quote()); show_error!(
"{}",
get_message_with_args(
"sort-batch-size-too-large",
HashMap::from([("arg".to_string(), n_merge.quote().to_string())])
)
);
format!( get_message_with_args(
"maximum --batch-size argument with current rlimit is {}", "sort-maximum-batch-size-rlimit",
get_rlimit()? HashMap::from([("rlimit".to_string(), get_rlimit()?.to_string())]),
) )
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
format!("--batch-size argument {} too large", n_merge.quote()) get_message_with_args(
"sort-batch-size-too-large",
HashMap::from([("arg".to_string(), n_merge.quote().to_string())]),
)
} }
} else { } else {
format!("invalid --batch-size argument {}", n_merge.quote()) get_message_with_args(
"sort-invalid-batch-size-arg",
HashMap::from([("arg".to_string(), n_merge.to_string())]),
)
}; };
return Err(UUsageError::new(2, error_message)); return Err(UUsageError::new(2, error_message));
@ -1259,7 +1330,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} else if settings.check && files.len() != 1 { } else if settings.check && files.len() != 1 {
return Err(UUsageError::new( return Err(UUsageError::new(
2, 2,
format!("extra operand {} not allowed with -c", files[1].quote()), get_message_with_args(
"sort-extra-operand-not-allowed-with-c",
HashMap::from([("operand".to_string(), files[1].quote().to_string())]),
),
)); ));
} }
@ -1267,7 +1341,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut separator = arg.to_str().ok_or_else(|| { let mut separator = arg.to_str().ok_or_else(|| {
UUsageError::new( UUsageError::new(
2, 2,
format!("separator is not valid unicode: {}", arg.quote()), get_message_with_args(
"sort-separator-not-valid-unicode",
HashMap::from([("arg".to_string(), arg.quote().to_string())]),
),
) )
})?; })?;
if separator == "\\0" { if separator == "\\0" {
@ -1279,9 +1356,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if separator.len() != 1 { if separator.len() != 1 {
return Err(UUsageError::new( return Err(UUsageError::new(
2, 2,
format!( get_message_with_args(
"separator must be exactly one character long: {}", "sort-separator-must-be-one-char",
separator.quote() HashMap::from([("separator".to_string(), separator.quote().to_string())]),
), ),
)); ));
} }
@ -1351,13 +1428,13 @@ pub fn uu_app() -> Command {
.arg( .arg(
Arg::new(options::HELP) Arg::new(options::HELP)
.long(options::HELP) .long(options::HELP)
.help("Print help information.") .help(get_message("sort-help-help"))
.action(ArgAction::Help), .action(ArgAction::Help),
) )
.arg( .arg(
Arg::new(options::VERSION) Arg::new(options::VERSION)
.long(options::VERSION) .long(options::VERSION)
.help("Print version information.") .help(get_message("sort-help-version"))
.action(ArgAction::Version), .action(ArgAction::Version),
) )
.arg( .arg(
@ -1376,38 +1453,38 @@ pub fn uu_app() -> Command {
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::HUMAN_NUMERIC, options::modes::HUMAN_NUMERIC,
'h', 'h',
"compare according to human readable sizes, eg 1M > 100k", get_message("sort-help-human-numeric"),
)) ))
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::MONTH, options::modes::MONTH,
'M', 'M',
"compare according to month name abbreviation", get_message("sort-help-month"),
)) ))
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::NUMERIC, options::modes::NUMERIC,
'n', 'n',
"compare according to string numerical value", get_message("sort-help-numeric"),
)) ))
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::GENERAL_NUMERIC, options::modes::GENERAL_NUMERIC,
'g', 'g',
"compare according to string general numerical value", get_message("sort-help-general-numeric"),
)) ))
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::VERSION, options::modes::VERSION,
'V', 'V',
"Sort by SemVer version number, eg 1.12.2 > 1.1.2", get_message("sort-help-version-sort"),
)) ))
.arg(make_sort_mode_arg( .arg(make_sort_mode_arg(
options::modes::RANDOM, options::modes::RANDOM,
'R', 'R',
"shuffle in random order", get_message("sort-help-random"),
)) ))
.arg( .arg(
Arg::new(options::DICTIONARY_ORDER) Arg::new(options::DICTIONARY_ORDER)
.short('d') .short('d')
.long(options::DICTIONARY_ORDER) .long(options::DICTIONARY_ORDER)
.help("consider only blanks and alphanumeric characters") .help(get_message("sort-help-dictionary-order"))
.conflicts_with_all([ .conflicts_with_all([
options::modes::NUMERIC, options::modes::NUMERIC,
options::modes::GENERAL_NUMERIC, options::modes::GENERAL_NUMERIC,
@ -1420,7 +1497,7 @@ pub fn uu_app() -> Command {
Arg::new(options::MERGE) Arg::new(options::MERGE)
.short('m') .short('m')
.long(options::MERGE) .long(options::MERGE)
.help("merge already sorted files; do not sort") .help(get_message("sort-help-merge"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
@ -1435,31 +1512,28 @@ pub fn uu_app() -> Command {
options::check::DIAGNOSE_FIRST, options::check::DIAGNOSE_FIRST,
])) ]))
.conflicts_with_all([options::OUTPUT, options::check::CHECK_SILENT]) .conflicts_with_all([options::OUTPUT, options::check::CHECK_SILENT])
.help("check for sorted input; do not sort"), .help(get_message("sort-help-check")),
) )
.arg( .arg(
Arg::new(options::check::CHECK_SILENT) Arg::new(options::check::CHECK_SILENT)
.short('C') .short('C')
.long(options::check::CHECK_SILENT) .long(options::check::CHECK_SILENT)
.conflicts_with_all([options::OUTPUT, options::check::CHECK]) .conflicts_with_all([options::OUTPUT, options::check::CHECK])
.help( .help(get_message("sort-help-check-silent"))
"exit successfully if the given file is already sorted, \
and exit with status 1 otherwise.",
)
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::IGNORE_CASE) Arg::new(options::IGNORE_CASE)
.short('f') .short('f')
.long(options::IGNORE_CASE) .long(options::IGNORE_CASE)
.help("fold lower case to upper case characters") .help(get_message("sort-help-ignore-case"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::IGNORE_NONPRINTING) Arg::new(options::IGNORE_NONPRINTING)
.short('i') .short('i')
.long(options::IGNORE_NONPRINTING) .long(options::IGNORE_NONPRINTING)
.help("ignore nonprinting characters") .help(get_message("sort-help-ignore-nonprinting"))
.conflicts_with_all([ .conflicts_with_all([
options::modes::NUMERIC, options::modes::NUMERIC,
options::modes::GENERAL_NUMERIC, options::modes::GENERAL_NUMERIC,
@ -1472,14 +1546,14 @@ pub fn uu_app() -> Command {
Arg::new(options::IGNORE_LEADING_BLANKS) Arg::new(options::IGNORE_LEADING_BLANKS)
.short('b') .short('b')
.long(options::IGNORE_LEADING_BLANKS) .long(options::IGNORE_LEADING_BLANKS)
.help("ignore leading blanks when finding sort keys in each line") .help(get_message("sort-help-ignore-leading-blanks"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::OUTPUT) Arg::new(options::OUTPUT)
.short('o') .short('o')
.long(options::OUTPUT) .long(options::OUTPUT)
.help("write output to FILENAME instead of stdout") .help(get_message("sort-help-output"))
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.value_name("FILENAME") .value_name("FILENAME")
.value_hint(clap::ValueHint::FilePath) .value_hint(clap::ValueHint::FilePath)
@ -1490,28 +1564,28 @@ pub fn uu_app() -> Command {
Arg::new(options::REVERSE) Arg::new(options::REVERSE)
.short('r') .short('r')
.long(options::REVERSE) .long(options::REVERSE)
.help("reverse the output") .help(get_message("sort-help-reverse"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::STABLE) Arg::new(options::STABLE)
.short('s') .short('s')
.long(options::STABLE) .long(options::STABLE)
.help("stabilize sort by disabling last-resort comparison") .help(get_message("sort-help-stable"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::UNIQUE) Arg::new(options::UNIQUE)
.short('u') .short('u')
.long(options::UNIQUE) .long(options::UNIQUE)
.help("output only the first of an equal run") .help(get_message("sort-help-unique"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::KEY) Arg::new(options::KEY)
.short('k') .short('k')
.long(options::KEY) .long(options::KEY)
.help("sort by a key") .help(get_message("sort-help-key"))
.action(ArgAction::Append) .action(ArgAction::Append)
.num_args(1), .num_args(1),
) )
@ -1519,54 +1593,54 @@ pub fn uu_app() -> Command {
Arg::new(options::SEPARATOR) Arg::new(options::SEPARATOR)
.short('t') .short('t')
.long(options::SEPARATOR) .long(options::SEPARATOR)
.help("custom separator for -k") .help(get_message("sort-help-separator"))
.value_parser(ValueParser::os_string()), .value_parser(ValueParser::os_string()),
) )
.arg( .arg(
Arg::new(options::ZERO_TERMINATED) Arg::new(options::ZERO_TERMINATED)
.short('z') .short('z')
.long(options::ZERO_TERMINATED) .long(options::ZERO_TERMINATED)
.help("line delimiter is NUL, not newline") .help(get_message("sort-help-zero-terminated"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new(options::PARALLEL) Arg::new(options::PARALLEL)
.long(options::PARALLEL) .long(options::PARALLEL)
.help("change the number of threads running concurrently to NUM_THREADS") .help(get_message("sort-help-parallel"))
.value_name("NUM_THREADS"), .value_name("NUM_THREADS"),
) )
.arg( .arg(
Arg::new(options::BUF_SIZE) Arg::new(options::BUF_SIZE)
.short('S') .short('S')
.long(options::BUF_SIZE) .long(options::BUF_SIZE)
.help("sets the maximum SIZE of each segment in number of sorted items") .help(get_message("sort-help-buf-size"))
.value_name("SIZE"), .value_name("SIZE"),
) )
.arg( .arg(
Arg::new(options::TMP_DIR) Arg::new(options::TMP_DIR)
.short('T') .short('T')
.long(options::TMP_DIR) .long(options::TMP_DIR)
.help("use DIR for temporaries, not $TMPDIR or /tmp") .help(get_message("sort-help-tmp-dir"))
.value_name("DIR") .value_name("DIR")
.value_hint(clap::ValueHint::DirPath), .value_hint(clap::ValueHint::DirPath),
) )
.arg( .arg(
Arg::new(options::COMPRESS_PROG) Arg::new(options::COMPRESS_PROG)
.long(options::COMPRESS_PROG) .long(options::COMPRESS_PROG)
.help("compress temporary files with PROG, decompress with PROG -d; PROG has to take input from stdin and output to stdout") .help(get_message("sort-help-compress-prog"))
.value_name("PROG") .value_name("PROG")
.value_hint(clap::ValueHint::CommandName), .value_hint(clap::ValueHint::CommandName),
) )
.arg( .arg(
Arg::new(options::BATCH_SIZE) Arg::new(options::BATCH_SIZE)
.long(options::BATCH_SIZE) .long(options::BATCH_SIZE)
.help("Merge at most N_MERGE inputs at once.") .help(get_message("sort-help-batch-size"))
.value_name("N_MERGE"), .value_name("N_MERGE"),
) )
.arg( .arg(
Arg::new(options::FILES0_FROM) Arg::new(options::FILES0_FROM)
.long(options::FILES0_FROM) .long(options::FILES0_FROM)
.help("read input from the files specified by NUL-terminated NUL_FILE") .help(get_message("sort-help-files0-from"))
.value_name("NUL_FILE") .value_name("NUL_FILE")
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::FilePath), .value_hint(clap::ValueHint::FilePath),
@ -1574,7 +1648,7 @@ pub fn uu_app() -> Command {
.arg( .arg(
Arg::new(options::DEBUG) Arg::new(options::DEBUG)
.long(options::DEBUG) .long(options::DEBUG)
.help("underline the parts of the line that are actually used for sorting") .help(get_message("sort-help-debug"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
@ -1595,7 +1669,10 @@ fn exec(
merge::merge(files, settings, output, tmp_dir) merge::merge(files, settings, output, tmp_dir)
} else if settings.check { } else if settings.check {
if files.len() > 1 { if files.len() > 1 {
Err(UUsageError::new(2, "only one file allowed with -c")) Err(UUsageError::new(
2,
get_message("sort-only-one-file-allowed-with-c"),
))
} else { } else {
check::check(files.first().unwrap(), settings) check::check(files.first().unwrap(), settings)
} }
@ -1922,7 +1999,12 @@ fn print_sorted<'a, T: Iterator<Item = &'a Line<'a>>>(
.as_output_name() .as_output_name()
.unwrap_or(OsStr::new("standard output")) .unwrap_or(OsStr::new("standard output"))
.to_owned(); .to_owned();
let ctx = || format!("write failed: {}", output_name.maybe_quote()); let ctx = || {
get_message_with_args(
"sort-error-write-failed",
HashMap::from([("output".to_string(), output_name.maybe_quote().to_string())]),
)
};
let mut writer = output.into_write(); let mut writer = output.into_write();
for line in iter { for line in iter {
@ -1973,13 +2055,27 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String
// NOTE: // NOTE:
// GNU's sort echos affected flag, -S or --buffer-size, depending on user's selection // GNU's sort echos affected flag, -S or --buffer-size, depending on user's selection
match error { match error {
ParseSizeError::InvalidSuffix(_) => { ParseSizeError::InvalidSuffix(_) => get_message_with_args(
format!("invalid suffix in --{option} argument {}", s.quote()) "sort-invalid-suffix-in-option-arg",
} HashMap::from([
ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => { ("option".to_string(), option.to_string()),
format!("invalid --{option} argument {}", s.quote()) ("arg".to_string(), s.quote().to_string()),
} ]),
ParseSizeError::SizeTooBig(_) => format!("--{option} argument {} too large", s.quote()), ),
ParseSizeError::ParseFailure(_) | ParseSizeError::PhysicalMem(_) => get_message_with_args(
"sort-invalid-option-arg",
HashMap::from([
("option".to_string(), option.to_string()),
("arg".to_string(), s.quote().to_string()),
]),
),
ParseSizeError::SizeTooBig(_) => get_message_with_args(
"sort-option-arg-too-large",
HashMap::from([
("option".to_string(), option.to_string()),
("arg".to_string(), s.quote().to_string()),
]),
),
} }
} }

View file

@ -8,9 +8,11 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use std::collections::HashMap;
use tempfile::TempDir; use tempfile::TempDir;
use uucore::{ use uucore::{
error::{UResult, USimpleError}, error::{UResult, USimpleError},
locale::get_message_with_args,
show_error, show_error,
}; };
@ -58,11 +60,23 @@ impl TmpDirWrapper {
// and the program doesn't terminate before the handler has finished // and the program doesn't terminate before the handler has finished
let _lock = lock.lock().unwrap(); let _lock = lock.lock().unwrap();
if let Err(e) = remove_tmp_dir(&path) { if let Err(e) = remove_tmp_dir(&path) {
show_error!("failed to delete temporary directory: {e}"); let mut args = HashMap::new();
args.insert("error".to_string(), e.to_string());
show_error!(
"{}",
get_message_with_args("sort-failed-to-delete-temporary-directory", args)
);
} }
std::process::exit(2) std::process::exit(2)
}) })
.map_err(|e| USimpleError::new(2, format!("failed to set up signal handler: {e}"))) .map_err(|e| {
let mut args = HashMap::new();
args.insert("error".to_string(), e.to_string());
USimpleError::new(
2,
get_message_with_args("sort-failed-to-set-up-signal-handler", args),
)
})
} }
pub fn next_file(&mut self) -> UResult<(File, PathBuf)> { pub fn next_file(&mut self) -> UResult<(File, PathBuf)> {