mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #1354 from ccbrown/rewrite-echo
echo: rewrite for readability and tests
This commit is contained in:
commit
100f6df2bf
2 changed files with 92 additions and 103 deletions
168
src/echo/echo.rs
168
src/echo/echo.rs
|
@ -4,6 +4,7 @@
|
||||||
* This file is part of the uutils coreutils package.
|
* This file is part of the uutils coreutils package.
|
||||||
*
|
*
|
||||||
* (c) Derek Chiang <derekchiang93@gmail.com>
|
* (c) Derek Chiang <derekchiang93@gmail.com>
|
||||||
|
* (c) Christopher Brown <ccbrown112@gmail.com>
|
||||||
*
|
*
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
|
@ -12,13 +13,12 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use std::io::{stdout, Write};
|
use std::iter::Peekable;
|
||||||
use std::str::from_utf8;
|
use std::str::Chars;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
const SYNTAX: &str = "[OPTIONS]... [STRING]...";
|
||||||
static SYNTAX: &str = "[OPTIONS]... [STRING]...";
|
const SUMMARY: &str = "display a line of text";
|
||||||
static SUMMARY: &str = "display a line of text";
|
const HELP: &str = r#"
|
||||||
static HELP: &str = r#"
|
|
||||||
Echo the STRING(s) to standard output.
|
Echo the STRING(s) to standard output.
|
||||||
If -e is in effect, the following sequences are recognized:
|
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)
|
\\xHH byte with hexadecimal value HH (1 to 2 digits)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
enum Base {
|
fn parse_code(input: &mut Peekable<Chars>, base: u32, max_digits: u32, bits_per_digit: u32) -> Option<char> {
|
||||||
B8 = 8,
|
let mut ret = 0x80000000;
|
||||||
B16 = 16,
|
for _ in 0..max_digits {
|
||||||
}
|
match input.peek().and_then(|c| c.to_digit(base)) {
|
||||||
|
Some(n) => ret = (ret << bits_per_digit) | n,
|
||||||
struct Opts {
|
None => break,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
input.next();
|
||||||
}
|
}
|
||||||
|
std::char::from_u32(ret)
|
||||||
|
}
|
||||||
|
|
||||||
if bytes.is_empty() {
|
fn print_escaped(input: &str, should_stop: &mut bool) {
|
||||||
(' ', 0)
|
let mut iter = input.chars().peekable();
|
||||||
} else {
|
while let Some(mut c) = iter.next() {
|
||||||
(
|
if c == '\\' {
|
||||||
usize::from_str_radix(from_utf8(bytes.as_ref()).unwrap(), base as u32).unwrap() as u8
|
if let Some(next) = iter.next() {
|
||||||
as char,
|
c = match next {
|
||||||
bytes.len(),
|
'\\' => '\\',
|
||||||
)
|
'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<String>) -> i32 {
|
||||||
let matches = new_coreopts!(SYNTAX, SUMMARY, HELP)
|
let matches = new_coreopts!(SYNTAX, SUMMARY, HELP)
|
||||||
.optflag("n", "", "do not output the trailing newline")
|
.optflag("n", "", "do not output the trailing newline")
|
||||||
.optflag("e", "", "enable interpretation of backslash escapes")
|
.optflag("e", "", "enable interpretation of backslash escapes")
|
||||||
.optflag(
|
.optflag("E", "", "disable interpretation of backslash escapes (default)")
|
||||||
"E",
|
|
||||||
"",
|
|
||||||
"disable interpretation of backslash escapes (default)",
|
|
||||||
)
|
|
||||||
.parse(args);
|
.parse(args);
|
||||||
|
|
||||||
let options = Opts {
|
let no_newline = matches.opt_present("n");
|
||||||
newline: matches.opt_present("n"),
|
let escaped = matches.opt_present("e");
|
||||||
escape: matches.opt_present("e"),
|
|
||||||
};
|
for (i, input) in matches.free.iter().enumerate() {
|
||||||
let free = matches.free;
|
if i > 0 {
|
||||||
if !free.is_empty() {
|
print!(" ");
|
||||||
let string = free.join(" ");
|
}
|
||||||
if options.escape {
|
if escaped {
|
||||||
let mut prev_was_slash = false;
|
let mut should_stop = false;
|
||||||
let mut iter = string.chars().enumerate();
|
print_escaped(&input, &mut should_stop);
|
||||||
while let Some((mut idx, c)) = iter.next() {
|
if should_stop {
|
||||||
prev_was_slash = if !prev_was_slash {
|
break;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print!("{}", string);
|
print!("{}", input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.newline {
|
if !no_newline {
|
||||||
return_if_err!(1, stdout().flush())
|
println!();
|
||||||
} else {
|
|
||||||
println!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
0
|
||||||
|
|
|
@ -48,6 +48,21 @@ fn test_escape_hex() {
|
||||||
new_ucmd!().args(&["-e", "\\x41"]).succeeds().stdout_only("A");
|
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]
|
#[test]
|
||||||
fn test_escape_newline() {
|
fn test_escape_newline() {
|
||||||
new_ucmd!().args(&["-e", "\\na"]).succeeds().stdout_only("\na");
|
new_ucmd!().args(&["-e", "\\na"]).succeeds().stdout_only("\na");
|
||||||
|
@ -55,7 +70,7 @@ fn test_escape_newline() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_escape_no_further_output() {
|
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]
|
#[test]
|
||||||
|
@ -63,6 +78,16 @@ fn test_escape_octal() {
|
||||||
new_ucmd!().args(&["-e", "\\0100"]).succeeds().stdout_only("@");
|
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]
|
#[test]
|
||||||
fn test_escape_tab() {
|
fn test_escape_tab() {
|
||||||
new_ucmd!().args(&["-e", "\\t"]).succeeds().stdout_only("\t\n");
|
new_ucmd!().args(&["-e", "\\t"]).succeeds().stdout_only("\t\n");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue