diff --git a/src/echo/echo.rs b/src/echo/echo.rs index ef6d9dae2..8d6e49257 100644 --- a/src/echo/echo.rs +++ b/src/echo/echo.rs @@ -4,6 +4,7 @@ * This file is part of the uutils coreutils package. * * (c) Derek Chiang + * (c) Christopher Brown * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,13 +13,12 @@ #[macro_use] extern crate uucore; -use std::io::{stdout, Write}; -use std::str::from_utf8; +use std::iter::Peekable; +use std::str::Chars; -#[allow(dead_code)] -static SYNTAX: &str = "[OPTIONS]... [STRING]..."; -static SUMMARY: &str = "display a line of text"; -static HELP: &str = r#" +const SYNTAX: &str = "[OPTIONS]... [STRING]..."; +const SUMMARY: &str = "display a line of text"; +const HELP: &str = r#" Echo the STRING(s) to standard output. If -e is in effect, the following sequences are recognized: @@ -36,43 +36,53 @@ static HELP: &str = r#" \\xHH byte with hexadecimal value HH (1 to 2 digits) "#; -enum Base { - B8 = 8, - B16 = 16, -} - -struct Opts { - newline: bool, - escape: bool, -} - -fn convert_str(string: &[u8], index: usize, base: Base) -> (char, usize) { - let (max_digits, is_legal_digit): (usize, fn(u8) -> bool) = match base { - Base::B8 => (3, |c| (c as char).is_digit(8)), - Base::B16 => (2, |c| (c as char).is_digit(16)), - }; - - let mut bytes = vec![]; - for offset in 0..max_digits { - if string.len() <= index + offset as usize { - break; - } - let c = string[index + offset as usize]; - if is_legal_digit(c) { - bytes.push(c as u8); - } else { - break; +fn parse_code(input: &mut Peekable, base: u32, max_digits: u32, bits_per_digit: u32) -> Option { + let mut ret = 0x80000000; + for _ in 0..max_digits { + match input.peek().and_then(|c| c.to_digit(base)) { + Some(n) => ret = (ret << bits_per_digit) | n, + None => break, } + input.next(); } + std::char::from_u32(ret) +} - if bytes.is_empty() { - (' ', 0) - } else { - ( - usize::from_str_radix(from_utf8(bytes.as_ref()).unwrap(), base as u32).unwrap() as u8 - as char, - bytes.len(), - ) +fn print_escaped(input: &str, should_stop: &mut bool) { + let mut iter = input.chars().peekable(); + while let Some(mut c) = iter.next() { + if c == '\\' { + if let Some(next) = iter.next() { + c = match next { + '\\' => '\\', + 'a' => '\x07', + 'b' => '\x08', + 'c' => { + *should_stop = true; + break + }, + 'e' => '\x1b', + 'f' => '\x0c', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\x0b', + 'x' => parse_code(&mut iter, 16, 2, 4).unwrap_or_else(|| { + print!("\\"); + next + }), + '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or_else(|| { + print!("\\"); + next + }), + _ => { + print!("\\"); + next + }, + }; + } + } + print!("{}", c); } } @@ -80,75 +90,29 @@ pub fn uumain(args: Vec) -> i32 { let matches = new_coreopts!(SYNTAX, SUMMARY, HELP) .optflag("n", "", "do not output the trailing newline") .optflag("e", "", "enable interpretation of backslash escapes") - .optflag( - "E", - "", - "disable interpretation of backslash escapes (default)", - ) + .optflag("E", "", "disable interpretation of backslash escapes (default)") .parse(args); - let options = Opts { - newline: matches.opt_present("n"), - escape: matches.opt_present("e"), - }; - let free = matches.free; - if !free.is_empty() { - let string = free.join(" "); - if options.escape { - let mut prev_was_slash = false; - let mut iter = string.chars().enumerate(); - while let Some((mut idx, c)) = iter.next() { - prev_was_slash = if !prev_was_slash { - if c != '\\' { - print!("{}", c); - false - } else { - true - } - } else { - match c { - '\\' => print!("\\"), - 'n' => print!("\n"), - 'r' => print!("\r"), - 't' => print!("\t"), - 'v' => print!("\x0B"), - 'a' => print!("\x07"), - 'b' => print!("\x08"), - 'c' => break, - 'e' => print!("\x1B"), - 'f' => print!("\x0C"), - ch => { - // 'x' or '0' or _ - idx = if ch == 'x' || ch == '0' { idx + 1 } else { idx }; - let base = if ch == 'x' { Base::B16 } else { Base::B8 }; - match convert_str(string.as_bytes(), idx, base) { - (_, 0) => match ch { - 'x' => print!("\\x"), - '0' => print!("\0"), - _ => print!("\\{}", c), - }, - (c, num_char_used) => { - print!("{}", c); - let beg = if ch == 'x' || ch == '0' { 0 } else { 1 }; - for _ in beg..num_char_used { - iter.next(); // consume used characters - } - } - } - } - } - false - } + let no_newline = matches.opt_present("n"); + let escaped = matches.opt_present("e"); + + for (i, input) in matches.free.iter().enumerate() { + if i > 0 { + print!(" "); + } + if escaped { + let mut should_stop = false; + print_escaped(&input, &mut should_stop); + if should_stop { + break; } } else { - print!("{}", string); + print!("{}", input); } } - if options.newline { - return_if_err!(1, stdout().flush()) - } else { - println!() + if !no_newline { + println!(); } 0 diff --git a/tests/test_echo.rs b/tests/test_echo.rs index 1c9e7417f..135964994 100644 --- a/tests/test_echo.rs +++ b/tests/test_echo.rs @@ -48,6 +48,21 @@ fn test_escape_hex() { new_ucmd!().args(&["-e", "\\x41"]).succeeds().stdout_only("A"); } +#[test] +fn test_escape_short_hex() { + new_ucmd!().args(&["-e", "foo\\xa bar"]).succeeds().stdout_only("foo\n bar"); +} + +#[test] +fn test_escape_no_hex() { + new_ucmd!().args(&["-e", "foo\\x bar"]).succeeds().stdout_only("foo\\x bar"); +} + +#[test] +fn test_escape_one_slash() { + new_ucmd!().args(&["-e", "foo\\ bar"]).succeeds().stdout_only("foo\\ bar"); +} + #[test] fn test_escape_newline() { new_ucmd!().args(&["-e", "\\na"]).succeeds().stdout_only("\na"); @@ -55,7 +70,7 @@ fn test_escape_newline() { #[test] fn test_escape_no_further_output() { - new_ucmd!().args(&["-e", "a\\cb"]).succeeds().stdout_only("a\n"); + new_ucmd!().args(&["-e", "a\\cb", "c"]).succeeds().stdout_only("a\n"); } #[test] @@ -63,6 +78,16 @@ fn test_escape_octal() { new_ucmd!().args(&["-e", "\\0100"]).succeeds().stdout_only("@"); } +#[test] +fn test_escape_short_octal() { + new_ucmd!().args(&["-e", "foo\\040bar"]).succeeds().stdout_only("foo bar"); +} + +#[test] +fn test_escape_no_octal() { + new_ucmd!().args(&["-e", "foo\\0 bar"]).succeeds().stdout_only("foo\\0 bar"); +} + #[test] fn test_escape_tab() { new_ucmd!().args(&["-e", "\\t"]).succeeds().stdout_only("\t\n");