From 0f3bc237393adef485182656b0edc986575c592d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 29 Apr 2021 23:13:20 -0400 Subject: [PATCH 1/2] 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. --- src/uu/tr/src/tr.rs | 54 ++++++++++++++++++++++++++++++++++++++-- tests/by-util/test_tr.rs | 18 ++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..09c4304a5 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -278,8 +278,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 { translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else if squeeze_flag { - let op = SqueezeOperation::new(set1, complement_flag); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + if sets.len() < 2 { + 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 { let mut set2 = ExpandSet::new(sets[1].as_ref()); let op = TranslateOperation::new(set1, &mut set2, truncate_flag); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..5d044a187 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -63,6 +63,24 @@ fn test_squeeze_complement() { .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] fn test_delete_and_squeeze() { new_ucmd!() From 5674d093275ea9ddc3be9ffbb3627eb5d6cae3a1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 1 May 2021 13:01:55 -0400 Subject: [PATCH 2/2] fixup! tr: implement translate and squeeze (-s) mode --- src/uu/tr/src/tr.rs | 89 +++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 09c4304a5..e04737a45 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -151,6 +151,35 @@ impl SymbolTranslator for TranslateOperation { } } +struct TranslateAndSqueezeOperation { + translate: TranslateOperation, + squeeze: SqueezeOperation, +} + +impl TranslateAndSqueezeOperation { + fn new( + set1: ExpandSet, + set2: &mut ExpandSet, + set2_: ExpandSet, + truncate: bool, + complement: bool, + ) -> TranslateAndSqueezeOperation { + TranslateAndSqueezeOperation { + translate: TranslateOperation::new(set1, set2, truncate), + squeeze: SqueezeOperation::new(set2_, complement), + } + } +} + +impl SymbolTranslator for TranslateAndSqueezeOperation { + fn translate(&self, c: char, prev_c: char) -> Option { + // `unwrap()` will never panic because `Translate.translate()` + // always returns `Some`. + self.squeeze + .translate(self.translate.translate(c, 0 as char).unwrap(), prev_c) + } +} + fn translate_input( input: &mut dyn BufRead, output: &mut dyn Write, @@ -168,8 +197,11 @@ fn translate_input( // isolation to make borrow checker happy let filtered = buf.chars().filter_map(|c| { let res = translator.translate(c, prev_c); + // Set `prev_c` to the post-translate character. This + // allows the squeeze operation to correctly function + // after the translate operation. if res.is_some() { - prev_c = c; + prev_c = res.unwrap(); } res }); @@ -282,53 +314,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 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(); - } + let set2_ = ExpandSet::new(sets[1].as_ref()); + let op = TranslateAndSqueezeOperation::new( + set1, + &mut set2, + set2_, + complement_flag, + truncate_flag, + ); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else { let mut set2 = ExpandSet::new(sets[1].as_ref());