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
32 changes: 17 additions & 15 deletions src/uu/tr/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,9 @@ 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
// With -c, set1 positions are those of the complement, so there is nothing to match.
for (set2_pos, set2_item) in set2.iter().enumerate() {
if matches!(set2_item, Self::Class(_)) {
if !complement_flag && matches!(set2_item, Self::Class(_)) {
let set2_part_solved_len = Self::expanded_len_of(&set2[..set2_pos]);

let mut class_matches = false;
Expand Down Expand Up @@ -416,20 +417,6 @@ impl Sequence {
let set2_uniques = Self::unique_chars(&set2_runs);

let set1_has_class = set1.iter().any(|x| matches!(x, Self::Class(_)));
// If the complement flag is used in translate mode, only one unique
// character may appear in set2. Validate this with the set of uniques
// in set2 that we just generated.
// Also, set2 must not overgrow set1, otherwise the mapping can't be 1:1.
if set1_has_class
&& translating
&& complement_flag
&& (set2_uniques.len() > 1 || set2_len > set1_len)
{
return Err(SequenceError::whole_set(
BadSequence::ComplementMoreThanOneUniqueInSet2,
2,
));
}

if set2_len < set1_len {
if truncate_set1_flag {
Expand Down Expand Up @@ -460,6 +447,21 @@ impl Sequence {
}
}

// If the complement flag is used in translate mode, only one unique
// character may appear in set2. Validate this with the set of uniques
// in set2 that we just generated.
// Also, set2 must not overgrow set1, otherwise the mapping can't be 1:1.
if set1_has_class
&& translating
&& complement_flag
&& (set2_uniques.len() > 1 || set2_len > set1_len)
{
return Err(SequenceError::whole_set(
BadSequence::ComplementMoreThanOneUniqueInSet2,
2,
));
}

// Line the two sets up position by position, one run at a time. A run
// of one character in set1 maps that character to every character it
// lines up with in set2, and the last mapping wins, so the pair at the
Expand Down
58 changes: 58 additions & 0 deletions tests/by-util/test_tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,64 @@
.stderr_is("tr: warning: an unescaped backslash at end of string is not portable\n");
}

#[test]
fn test_complement_set1_longer_than_set2_ending_in_class() {
let msg = "tr: when translating with string1 longer than string2,\nthe latter string must not end with a character class\n";
for (flags, set1, set2) in [
("-c", "[:upper:]", "[:lower:]"),
("-c", "[:lower:]", "[:upper:]"),
("-c", "[:upper:]", "x[:lower:]"),
("-c", "[:digit:]", "[:upper:]"),
("-c", "[:upper:]", "[:upper:]"),
("-c", "a-z", "[:upper:]"),
("-c", "a", "[:upper:]"),
("-cs", "[:upper:]", "[:lower:]"),
] {
new_ucmd!()
.args(&[flags, set1, set2])
.pipe_in("AbC1z\n")
.fails_with_code(1)
.stderr_only(msg);
}
}

#[test]
fn test_complement_class_in_set2_without_class_in_set1() {
// The complement of everything is empty, so nothing is translated.
new_ucmd!()
.args(&["-c", "\\000-\\377", "[:upper:]"])
.pipe_in("AbC1z\n")
.succeeds()
.stdout_only("AbC1z\n");

new_ucmd!()
.args(&["-c", "\\000-\\375", "[:upper:]"])
.pipe_in(b"\xfe\xffAa\n".as_slice())
.succeeds()
.stdout_only("ABAa\n");

new_ucmd!()
.args(&["-c", "a-z", "[:upper:]x"])
.pipe_in("AbC1z\n")
.succeeds()
.stdout_only("xbxxzK");

Check warning on line 1090 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 'xbxxz' (file:'tests/by-util/test_tr.rs', line:1090)
}

#[test]
fn test_complement_class_in_set2_unique_chars_error() {
let msg = "tr: when translating with complemented character classes,\nstring2 must map all characters in the domain to one\n";
for (flags, set1, set2) in [
("-c", "[:upper:]", "[:lower:]x"),
("-ct", "[:upper:]", "[:lower:]"),
] {
new_ucmd!()
.args(&[flags, set1, set2])
.pipe_in("AbC1z\n")
.fails_with_code(1)
.stderr_only(msg);
}
}

#[test]
fn tr_ross_delete_no_squeeze() {
// # From Ross
Expand Down Expand Up @@ -1598,7 +1656,7 @@
.args(&["-c", "[a*18446744073709551614]bc", "x"])
.pipe_in("abcd")
.succeeds()
.stdout_only("abcx");

Check warning on line 1659 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:1659)
new_ucmd!()
.args(&["[a*18446744073709551615]b", "[x*18446744073709551614][y*]z"])
.pipe_in("ab")
Expand All @@ -1619,12 +1677,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 1680 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:1680)
.succeeds()
.stdout_only("xy");
new_ucmd!()
.args(&["-s", "a", "xyz"])
.pipe_in("aazz")

Check warning on line 1685 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:1685)
.succeeds()
.stdout_only("xz");
}
Expand Down
Loading