Skip to content
Merged
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
119 changes: 70 additions & 49 deletions src/uu/tr/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum BadSequence {
CharRepeatInSet1,
InvalidRepeatCount(String),
EmptySet2WhenNotTruncatingSet1,
ClassExceptLowerUpperInSet2,
}

impl Display for BadSequence {
Expand All @@ -54,6 +55,9 @@ impl Display for BadSequence {
Self::EmptySet2WhenNotTruncatingSet1 => {
write!(f, "when not truncating set1, string2 must be non-empty")
}
Self::ClassExceptLowerUpperInSet2 => {
write!(f, "when translating, the only character classes that may appear in set2 are 'upper' and 'lower'")
}
}
}
}
Expand All @@ -62,11 +66,7 @@ impl Error for BadSequence {}
impl UError for BadSequence {}

#[derive(Debug, Clone, Copy)]
pub enum Sequence {
Char(u8),
CharRange(u8, u8),
CharStar(u8),
CharRepeat(u8, usize),
pub enum Class {
Alnum,
Alpha,
Blank,
Expand All @@ -81,44 +81,55 @@ pub enum Sequence {
Xdigit,
}

#[derive(Debug, Clone, Copy)]
pub enum Sequence {
Char(u8),
CharRange(u8, u8),
CharStar(u8),
CharRepeat(u8, usize),
Class(Class),
}

impl Sequence {
pub fn flatten(&self) -> Box<dyn Iterator<Item = u8>> {
match self {
Self::Char(c) => Box::new(std::iter::once(*c)),
Self::CharRange(l, r) => Box::new(*l..=*r),
Self::CharStar(c) => Box::new(std::iter::repeat(*c)),
Self::CharRepeat(c, n) => Box::new(std::iter::repeat(*c).take(*n)),
Self::Alnum => Box::new((b'0'..=b'9').chain(b'A'..=b'Z').chain(b'a'..=b'z')),
Self::Alpha => Box::new((b'A'..=b'Z').chain(b'a'..=b'z')),
Self::Blank => Box::new(unicode_table::BLANK.iter().cloned()),
Self::Control => Box::new((0..=31).chain(std::iter::once(127))),
Self::Digit => Box::new(b'0'..=b'9'),
Self::Graph => Box::new(
(48..=57) // digit
.chain(65..=90) // uppercase
.chain(97..=122) // lowercase
// punctuations
.chain(33..=47)
.chain(58..=64)
.chain(91..=96)
.chain(123..=126)
.chain(std::iter::once(32)), // space
),
Self::Lower => Box::new(b'a'..=b'z'),
Self::Print => Box::new(
(48..=57) // digit
.chain(65..=90) // uppercase
.chain(97..=122) // lowercase
// punctuations
.chain(33..=47)
.chain(58..=64)
.chain(91..=96)
.chain(123..=126),
),
Self::Punct => Box::new((33..=47).chain(58..=64).chain(91..=96).chain(123..=126)),
Self::Space => Box::new(unicode_table::SPACES.iter().cloned()),
Self::Upper => Box::new(b'A'..=b'Z'),
Self::Xdigit => Box::new((b'0'..=b'9').chain(b'A'..=b'F').chain(b'a'..=b'f')),
Self::Class(class) => match class {
Class::Alnum => Box::new((b'0'..=b'9').chain(b'A'..=b'Z').chain(b'a'..=b'z')),
Class::Alpha => Box::new((b'A'..=b'Z').chain(b'a'..=b'z')),
Class::Blank => Box::new(unicode_table::BLANK.iter().cloned()),
Class::Control => Box::new((0..=31).chain(std::iter::once(127))),
Class::Digit => Box::new(b'0'..=b'9'),
Class::Graph => Box::new(
(48..=57) // digit
.chain(65..=90) // uppercase
.chain(97..=122) // lowercase
// punctuations
.chain(33..=47)
.chain(58..=64)
.chain(91..=96)
.chain(123..=126)
.chain(std::iter::once(32)), // space
),
Class::Lower => Box::new(b'a'..=b'z'),
Class::Print => Box::new(
(48..=57) // digit
.chain(65..=90) // uppercase
.chain(97..=122) // lowercase
// punctuations
.chain(33..=47)
.chain(58..=64)
.chain(91..=96)
.chain(123..=126),
),
Class::Punct => Box::new((33..=47).chain(58..=64).chain(91..=96).chain(123..=126)),
Class::Space => Box::new(unicode_table::SPACES.iter().cloned()),
Class::Upper => Box::new(b'A'..=b'Z'),
Class::Xdigit => Box::new((b'0'..=b'9').chain(b'A'..=b'F').chain(b'a'..=b'f')),
},
}
}

Expand All @@ -128,13 +139,23 @@ impl Sequence {
set2_str: &[u8],
complement_flag: bool,
truncate_set1_flag: bool,
translating: bool,
) -> Result<(Vec<u8>, Vec<u8>), BadSequence> {
let set1 = Self::from_str(set1_str)?;

let is_char_star = |s: &&Self| -> bool { matches!(s, Self::CharStar(_)) };
let set1_star_count = set1.iter().filter(is_char_star).count();
if set1_star_count == 0 {
let set2 = Self::from_str(set2_str)?;

if translating
&& set2.iter().any(|&x| {
matches!(x, Self::Class(_))
&& !matches!(x, Self::Class(Class::Upper) | Self::Class(Class::Lower))
})
{
return Err(BadSequence::ClassExceptLowerUpperInSet2);
}

let set2_star_count = set2.iter().filter(is_char_star).count();
if set2_star_count < 2 {
let char_star = set2.iter().find_map(|s| match s {
Expand Down Expand Up @@ -305,18 +326,18 @@ impl Sequence {
alt((
map(
alt((
value(Self::Alnum, tag("alnum")),
value(Self::Alpha, tag("alpha")),
value(Self::Blank, tag("blank")),
value(Self::Control, tag("cntrl")),
value(Self::Digit, tag("digit")),
value(Self::Graph, tag("graph")),
value(Self::Lower, tag("lower")),
value(Self::Print, tag("print")),
value(Self::Punct, tag("punct")),
value(Self::Space, tag("space")),
value(Self::Upper, tag("upper")),
value(Self::Xdigit, tag("xdigit")),
value(Self::Class(Class::Alnum), tag("alnum")),
value(Self::Class(Class::Alpha), tag("alpha")),
value(Self::Class(Class::Blank), tag("blank")),
value(Self::Class(Class::Control), tag("cntrl")),
value(Self::Class(Class::Digit), tag("digit")),
value(Self::Class(Class::Graph), tag("graph")),
value(Self::Class(Class::Lower), tag("lower")),
value(Self::Class(Class::Print), tag("print")),
value(Self::Class(Class::Punct), tag("punct")),
value(Self::Class(Class::Space), tag("space")),
value(Self::Class(Class::Upper), tag("upper")),
value(Self::Class(Class::Xdigit), tag("xdigit")),
)),
Ok,
),
Expand Down
1 change: 1 addition & 0 deletions src/uu/tr/src/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
complement_flag,
// if we are not translating then we don't truncate set1
truncate_set1_flag && translating,
translating,
)?;

// '*_op' are the operations that need to be applied, in order.
Expand Down
5 changes: 5 additions & 0 deletions tests/by-util/test_tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,3 +1364,8 @@ fn check_ignore_truncate_when_squeezing() {
.no_stderr()
.stdout_only("asdfqwer\n");
}

#[test]
fn check_disallow_blank_in_set2_when_translating() {
new_ucmd!().args(&["-t", "1234", "[:blank:]"]).fails();
}