tr: implement translate and squeeze (-s) mode

Add translate and squeeze mode to the `tr` program. For example:

    $ printf xx | tr -s x y
    y

Fixes #2141.
This commit is contained in:
Jeffrey Finkelstein 2021-04-29 23:13:20 -04:00
parent 247de489f5
commit 0f3bc23739
2 changed files with 70 additions and 2 deletions

View file

@ -278,8 +278,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
translate_input(&mut locked_stdin, &mut buffered_stdout, op); translate_input(&mut locked_stdin, &mut buffered_stdout, op);
} }
} else if squeeze_flag { } else if squeeze_flag {
let op = SqueezeOperation::new(set1, complement_flag); if sets.len() < 2 {
translate_input(&mut locked_stdin, &mut buffered_stdout, op); let op = SqueezeOperation::new(set1, complement_flag);
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
} else {
// Define a closure that computes the translation using a hash map.
//
// The `unwrap()` should never panic because the
// `TranslateOperation.translate()` method always returns
// `Some`.
let mut set2 = ExpandSet::new(sets[1].as_ref());
let translator = TranslateOperation::new(set1, &mut set2, truncate_flag);
let translate = |c| translator.translate(c, 0 as char).unwrap();
// Prepare some variables to be used for the closure that
// computes the squeeze operation.
//
// The `squeeze()` closure needs to be defined anew for
// each line of input, but these variables do not change
// while reading the input so they can be defined before
// the `while` loop.
let set2 = ExpandSet::new(sets[1].as_ref());
let squeezer = SqueezeOperation::new(set2, complement_flag);
// Prepare some memory to read each line of the input (`buf`) and to write
let mut buf = String::with_capacity(BUFFER_LEN + 4);
// Loop over each line of stdin.
while let Ok(length) = locked_stdin.read_line(&mut buf) {
if length == 0 {
break;
}
// Define a closure that computes the squeeze operation.
//
// We keep track of the previously seen character on
// each call to `squeeze()`, but we need to reset the
// `prev_c` variable at the beginning of each line of
// the input. That's why we define the closure inside
// the `while` loop.
let mut prev_c = 0 as char;
let squeeze = |c| {
let result = squeezer.translate(c, prev_c);
prev_c = c;
result
};
// First translate, then squeeze each character of the input line.
let filtered: String = buf.chars().map(translate).filter_map(squeeze).collect();
buf.clear();
buffered_stdout.write_all(filtered.as_bytes()).unwrap();
}
}
} else { } else {
let mut set2 = ExpandSet::new(sets[1].as_ref()); let mut set2 = ExpandSet::new(sets[1].as_ref());
let op = TranslateOperation::new(set1, &mut set2, truncate_flag); let op = TranslateOperation::new(set1, &mut set2, truncate_flag);

View file

@ -63,6 +63,24 @@ fn test_squeeze_complement() {
.stdout_is("aaBcDcc"); .stdout_is("aaBcDcc");
} }
#[test]
fn test_translate_and_squeeze() {
new_ucmd!()
.args(&["-s", "x", "y"])
.pipe_in("xx")
.run()
.stdout_is("y");
}
#[test]
fn test_translate_and_squeeze_multiple_lines() {
new_ucmd!()
.args(&["-s", "x", "y"])
.pipe_in("xxaax\nxaaxx")
.run()
.stdout_is("yaay\nyaay");
}
#[test] #[test]
fn test_delete_and_squeeze() { fn test_delete_and_squeeze() {
new_ucmd!() new_ucmd!()