Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/uu/tr/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/uu/tr/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
50 changes: 28 additions & 22 deletions src/uu/tr/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,26 +385,30 @@ 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 (set2_pos, set2_item) in set2.iter().enumerate() {
if matches!(set2_item, Self::Class(_)) {
let set2_part_solved_len = Self::expanded_len_of(&set2[..set2_pos]);

let mut class_matches = false;
for (set1_pos, set1_item) in set1.iter().enumerate() {
if matches!(set1_item, Self::Class(_))
&& Self::expanded_len_of(&set1[..set1_pos]) == set2_part_solved_len
{
class_matches = true;
break;
// 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.
if translating {
for (set2_pos, set2_item) in set2.iter().enumerate() {
if matches!(set2_item, Self::Class(_)) {
let set2_part_solved_len = Self::expanded_len_of(&set2[..set2_pos]);

let mut class_matches = false;
for (set1_pos, set1_item) in set1.iter().enumerate() {
// only upper/lower can pair with upper/lower
if matches!(set1_item, Self::Class(Class::Upper | Class::Lower))
&& Self::expanded_len_of(&set1[..set1_pos]) == set2_part_solved_len
{
class_matches = true;
break;
}
}
}

if !class_matches {
return Err(SequenceError::whole_set(
BadSequence::ClassInSet2NotMatchedBySet1,
2,
));
if !class_matches {
return Err(SequenceError::whole_set(
BadSequence::ClassInSet2NotMatchedBySet1,
2,
));
}
}
}
}
Expand Down Expand Up @@ -449,10 +453,12 @@ impl Sequence {
}
}
// Otherwise set1 is cut to the length of set2 while pairing below.
} 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,
Expand Down
69 changes: 68 additions & 1 deletion tests/by-util/test_tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,73 @@
.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)
// No input: the sets are validated before stdin is read, so tr exits
// while the harness is still writing and the write hits a broken pipe.
.pipe_in("")
.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() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please also test -s alone (still translating, so still rejected) and plain -d with a class in set2

new_ucmd!()
.args(&["-ds", "[:alpha:]", "[:upper:]"])
.pipe_in("aZ1")
.succeeds()
.stdout_is("1");
}

/// -s alone still translates, so the alignment rule still applies.
#[test]
fn test_misaligned_construct_checked_when_squeezing() {
new_ucmd!()
.args(&["-s", "[:alpha:]", "[:upper:]"])
.pipe_in("")
.fails_with_code(1)
.no_stdout()
.stderr_contains("tr: misaligned [:upper:] and/or [:lower:] construct\n");
}

/// Plain -d takes one set: a class in SET2 is an extra operand, not a
/// misaligned construct.
#[test]
fn test_delete_with_class_in_set2() {
new_ucmd!()
.args(&["-d", "[:alpha:]", "[:upper:]"])
.pipe_in("")
.fails_with_code(1)
.no_stdout()
.stderr_contains("extra operand '[:upper:]'");
}

/// 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!()
Expand Down Expand Up @@ -1598,7 +1665,7 @@
.args(&["-c", "[a*18446744073709551614]bc", "x"])
.pipe_in("abcd")
.succeeds()
.stdout_only("abcx");

Check warning on line 1668 in tests/by-util/test_tr.rs

View workflow job for this annotation

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

WARNING: `cspell`: Unknown word 'abcx' (file:'tests/by-util/test_tr.rs', line:1668)
new_ucmd!()
.args(&["[a*18446744073709551615]b", "[x*18446744073709551614][y*]z"])
.pipe_in("ab")
Expand All @@ -1610,7 +1677,7 @@
"[x*18446744073709551615][:upper:]",
])
.fails()
.stderr_contains("must be matched by");
.stderr_contains("misaligned [:upper:] and/or [:lower:] construct");
}

#[test]
Expand All @@ -1619,12 +1686,12 @@
// last one wins, but x is still part of set2 and so still squeezed.
new_ucmd!()
.args(&["-s", "[a*2]", "xy"])
.pipe_in("xxaa")

Check warning on line 1689 in tests/by-util/test_tr.rs

View workflow job for this annotation

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

WARNING: `cspell`: Unknown word 'xxaa' (file:'tests/by-util/test_tr.rs', line:1689)
.succeeds()
.stdout_only("xy");
new_ucmd!()
.args(&["-s", "a", "xyz"])
.pipe_in("aazz")

Check warning on line 1694 in tests/by-util/test_tr.rs

View workflow job for this annotation

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

WARNING: `cspell`: Unknown word 'aazz' (file:'tests/by-util/test_tr.rs', line:1694)
.succeeds()
.stdout_only("xz");
}
Expand Down
Loading