diff --git a/src/uu/tr/locales/en-US.ftl b/src/uu/tr/locales/en-US.ftl index 5203bc22623..64e6f5640b7 100644 --- a/src/uu/tr/locales/en-US.ftl +++ b/src/uu/tr/locales/en-US.ftl @@ -13,7 +13,7 @@ tr-error-missing-operand = missing operand tr-error-missing-operand-translating = missing operand after { $set } Two strings must be given when translating. tr-error-missing-operand-deleting-squeezing = missing operand after { $set } - Two strings must be given when deleting and squeezing. + Two strings must be given when both deleting and squeezing repeats. tr-error-extra-operand-deleting-without-squeezing = extra operand { $operand } Only one string may be given when deleting without squeezing repeats. tr-error-extra-operand-simple = extra operand { $operand } @@ -34,7 +34,7 @@ tr-error-char-repeat-in-set1 = the [c*] repeat construct may not appear in strin tr-error-invalid-repeat-count = invalid repeat count { $count } in [c*n] construct tr-error-empty-set2-when-not-truncating = when not truncating set1, string2 must be non-empty tr-error-class-except-lower-upper-in-set2 = when translating, the only character classes that may appear in set2 are 'upper' and 'lower' -tr-error-class-in-set2-not-matched = when translating, every 'upper'/'lower' in set2 must be matched by a 'upper'/'lower' in the same position in set1 +tr-error-class-in-set2-not-matched = misaligned [:upper:] and/or [:lower:] construct tr-error-set1-longer-set2-ends-in-class = when translating with string1 longer than string2, the latter string must not end with a character class tr-error-complement-more-than-one-unique = when translating with complemented character classes, diff --git a/src/uu/tr/locales/fr-FR.ftl b/src/uu/tr/locales/fr-FR.ftl index 31deed0ede3..591f002c366 100644 --- a/src/uu/tr/locales/fr-FR.ftl +++ b/src/uu/tr/locales/fr-FR.ftl @@ -13,7 +13,7 @@ tr-error-missing-operand = opérande manquant tr-error-missing-operand-translating = opérande manquant après { $set } Deux chaînes doivent être données lors de la traduction. tr-error-missing-operand-deleting-squeezing = opérande manquant après { $set } - Deux chaînes doivent être données lors de la suppression et compression. + Deux chaînes doivent être données lors de la suppression et de la compression des répétitions. tr-error-extra-operand-deleting-without-squeezing = opérande supplémentaire { $operand } Une seule chaîne peut être donnée lors de la suppression sans compression des répétitions. tr-error-extra-operand-simple = opérande supplémentaire { $operand } @@ -35,7 +35,7 @@ tr-error-char-repeat-in-set1 = la construction de répétition [c*] ne peut pas tr-error-invalid-repeat-count = nombre de répétitions invalide { $count } dans la construction [c*n] tr-error-empty-set2-when-not-truncating = quand on ne tronque pas set1, string2 doit être non-vide tr-error-class-except-lower-upper-in-set2 = lors de la traduction, les seules classes de caractères qui peuvent apparaître dans set2 sont 'upper' et 'lower' -tr-error-class-in-set2-not-matched = lors de la traduction, chaque 'upper'/'lower' dans set2 doit être associé à un 'upper'/'lower' à la même position dans set1 +tr-error-class-in-set2-not-matched = les structures [:upper:] ou [:lower:] ne sont pas alignées correctement tr-error-set1-longer-set2-ends-in-class = lors de la traduction avec string1 plus long que string2, cette dernière chaîne ne doit pas se terminer par une classe de caractères tr-error-complement-more-than-one-unique = lors de la traduction avec des classes de caractères complémentées, diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 8c503490ff2..3c12ea36768 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -318,9 +318,10 @@ impl Sequence { }) .collect(); - // For every upper/lower in set2, there must be an upper/lower in set1 at the same position. The position is calculated by expanding everything before the upper/lower in both sets + // For every upper/lower in set2, there must be an upper/lower in set1 at the same position. The position is calculated by expanding everything before the upper/lower in both sets. + // Only when translating: with -d, set2 is the squeeze set and lines up with nothing. for (set2_pos, set2_item) in set2.iter().enumerate() { - if matches!(set2_item, Self::Class(_)) { + if translating && matches!(set2_item, Self::Class(_)) { let mut set2_part_solved_len = 0; if set2_pos >= 1 { set2_part_solved_len = @@ -329,7 +330,10 @@ impl Sequence { let mut class_matches = false; for (set1_pos, set1_item) in set1.iter().enumerate() { - if matches!(set1_item, Self::Class(_)) { + // Only an 'upper'/'lower' in set1 can line up with one in + // set2: any other class there leaves the mapping + // undefined, so GNU rejects it. + if matches!(set1_item, Self::Class(Class::Upper | Class::Lower)) { let mut set1_part_solved_len = 0; if set1_pos >= 1 { set1_part_solved_len = @@ -401,10 +405,12 @@ impl Sequence { } else { set1_solved.truncate(set2_solved.len()); } - } else if matches!( - set2.last().copied(), - Some(Self::Class(Class::Upper | Class::Lower)) - ) { + } else if translating + && matches!( + set2.last().copied(), + Some(Self::Class(Class::Upper | Class::Lower)) + ) + { return Err(SequenceError::whole_set( BadSequence::Set1LongerSet2EndsInClass, 1, diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 493fb1916a8..94fcfaac581 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -299,13 +299,55 @@ fn test_translate_and_squeeze_multiple_lines() { .stdout_is("yaay\nyaay"); // spell-checker:disable-line } +/// An 'upper'/'lower' in SET2 must line up with an 'upper'/'lower' in SET1; +/// any other class there leaves the mapping undefined. GNU calls that a +/// misaligned construct. +#[test] +fn test_misaligned_upper_lower_construct() { + for sets in [ + ["[:alpha:]", "[:upper:]"], + ["[:digit:]", "[:upper:]"], + ["abc", "[:upper:]"], + ["a[:lower:]", "[:upper:]b"], + ["[:lower:]", "[:upper:][:lower:]"], + ] { + new_ucmd!() + .args(&sets) + .pipe_in("aZ1") + .fails_with_code(1) + .no_stdout() + .stderr_contains("tr: misaligned [:upper:] and/or [:lower:] construct\n"); + } +} + +/// The alignment rule is about translating only: with -d, SET2 is the squeeze +/// set and lines up with nothing. +#[test] +fn test_misaligned_construct_not_checked_when_deleting() { + new_ucmd!() + .args(&["-ds", "[:alpha:]", "[:upper:]"]) + .pipe_in("aZ1") + .succeeds() + .stdout_is("1"); +} + +/// SET1 longer than SET2 keeps its own message, which GNU also has. +#[test] +fn test_set1_longer_than_set2_ending_in_class() { + new_ucmd!() + .args(&["[:upper:][:lower:]", "[:lower:]"]) + .pipe_in("aZ1") + .fails_with_code(1) + .stderr_contains("when translating with string1 longer than string2,\n"); +} + #[test] fn test_delete_and_squeeze_one_set() { new_ucmd!() .args(&["-ds", "a-z"]) .fails() .stderr_contains("missing operand after 'a-z'") - .stderr_contains("Two strings must be given when deleting and squeezing."); + .stderr_contains("Two strings must be given when both deleting and squeezing repeats."); } #[test]