Skip to content

Commit ea1562b

Browse files
authored
Merge pull request #7316 from jfinkels/echo-parse-escape-only
echo: use uucore::format::parse_escape_only()
2 parents 80fb4f3 + f979f97 commit ea1562b

File tree

2 files changed

+7
-236
lines changed

2 files changed

+7
-236
lines changed

src/uu/echo/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ path = "src/echo.rs"
1818

1919
[dependencies]
2020
clap = { workspace = true }
21-
uucore = { workspace = true }
21+
uucore = { workspace = true, features = ["format"] }
2222

2323
[[bin]]
2424
name = "echo"

src/uu/echo/src/echo.rs

Lines changed: 6 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
88
use std::env;
99
use std::ffi::{OsStr, OsString};
1010
use std::io::{self, StdoutLock, Write};
11-
use std::iter::Peekable;
12-
use std::ops::ControlFlow;
13-
use std::slice::Iter;
1411
use uucore::error::{UResult, USimpleError};
12+
use uucore::format::{parse_escape_only, EscapedChar, FormatChar};
1513
use uucore::{format_usage, help_about, help_section, help_usage};
1614

1715
const ABOUT: &str = help_about!("echo.md");
@@ -25,236 +23,6 @@ mod options {
2523
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
2624
}
2725

28-
enum BackslashNumberType {
29-
OctalStartingWithNonZero(u8),
30-
OctalStartingWithZero,
31-
Hexadecimal,
32-
}
33-
34-
impl BackslashNumberType {
35-
fn base(&self) -> Base {
36-
match self {
37-
BackslashNumberType::OctalStartingWithZero
38-
| BackslashNumberType::OctalStartingWithNonZero(_) => Base::Octal,
39-
BackslashNumberType::Hexadecimal => Base::Hexadecimal,
40-
}
41-
}
42-
}
43-
44-
enum Base {
45-
Octal,
46-
Hexadecimal,
47-
}
48-
49-
impl Base {
50-
fn ascii_to_number(&self, digit: u8) -> Option<u8> {
51-
fn octal_ascii_digit_to_number(digit: u8) -> Option<u8> {
52-
let number = match digit {
53-
b'0' => 0,
54-
b'1' => 1,
55-
b'2' => 2,
56-
b'3' => 3,
57-
b'4' => 4,
58-
b'5' => 5,
59-
b'6' => 6,
60-
b'7' => 7,
61-
_ => {
62-
return None;
63-
}
64-
};
65-
66-
Some(number)
67-
}
68-
69-
fn hexadecimal_ascii_digit_to_number(digit: u8) -> Option<u8> {
70-
let number = match digit {
71-
b'0' => 0,
72-
b'1' => 1,
73-
b'2' => 2,
74-
b'3' => 3,
75-
b'4' => 4,
76-
b'5' => 5,
77-
b'6' => 6,
78-
b'7' => 7,
79-
b'8' => 8,
80-
b'9' => 9,
81-
b'A' | b'a' => 10,
82-
b'B' | b'b' => 11,
83-
b'C' | b'c' => 12,
84-
b'D' | b'd' => 13,
85-
b'E' | b'e' => 14,
86-
b'F' | b'f' => 15,
87-
_ => {
88-
return None;
89-
}
90-
};
91-
92-
Some(number)
93-
}
94-
95-
match self {
96-
Self::Octal => octal_ascii_digit_to_number(digit),
97-
Self::Hexadecimal => hexadecimal_ascii_digit_to_number(digit),
98-
}
99-
}
100-
101-
fn maximum_number_of_digits(&self) -> u8 {
102-
match self {
103-
Self::Octal => 3,
104-
Self::Hexadecimal => 2,
105-
}
106-
}
107-
108-
fn radix(&self) -> u8 {
109-
match self {
110-
Self::Octal => 8,
111-
Self::Hexadecimal => 16,
112-
}
113-
}
114-
}
115-
116-
/// Parse the numeric part of `\xHHH`, `\0NNN`, and `\NNN` escape sequences
117-
fn parse_backslash_number(
118-
input: &mut Peekable<Iter<u8>>,
119-
backslash_number_type: BackslashNumberType,
120-
) -> Option<u8> {
121-
let first_digit_ascii = match backslash_number_type {
122-
BackslashNumberType::OctalStartingWithZero | BackslashNumberType::Hexadecimal => {
123-
match input.peek() {
124-
Some(&&digit_ascii) => digit_ascii,
125-
None => {
126-
// One of the following cases: argument ends with "\0" or "\x"
127-
// If "\0" (octal): caller will print not ASCII '0', 0x30, but ASCII '\0' (NUL), 0x00
128-
// If "\x" (hexadecimal): caller will print literal "\x"
129-
return None;
130-
}
131-
}
132-
}
133-
// Never returns early when backslash number starts with "\1" through "\7", because caller provides the
134-
// first digit
135-
BackslashNumberType::OctalStartingWithNonZero(digit_ascii) => digit_ascii,
136-
};
137-
138-
let base = backslash_number_type.base();
139-
140-
let first_digit_number = match base.ascii_to_number(first_digit_ascii) {
141-
Some(digit_number) => {
142-
// Move past byte, since it was successfully parsed
143-
let _ = input.next();
144-
145-
digit_number
146-
}
147-
None => {
148-
// The first digit was not a valid octal or hexadecimal digit
149-
// This should never be the case when the backslash number starts with "\1" through "\7"
150-
// (caller unwraps to verify this)
151-
return None;
152-
}
153-
};
154-
155-
let radix = base.radix();
156-
157-
let mut sum = first_digit_number;
158-
159-
for _ in 1..(base.maximum_number_of_digits()) {
160-
match input
161-
.peek()
162-
.and_then(|&&digit_ascii| base.ascii_to_number(digit_ascii))
163-
{
164-
Some(digit_number) => {
165-
// Move past byte, since it was successfully parsed
166-
let _ = input.next();
167-
168-
// All arithmetic on `sum` needs to be wrapping, because octal input can
169-
// take 3 digits, which is 9 bits, and therefore more than what fits in a
170-
// `u8`.
171-
//
172-
// GNU Core Utilities: "if nnn is a nine-bit value, the ninth bit is ignored"
173-
// https://www.gnu.org/software/coreutils/manual/html_node/echo-invocation.html
174-
sum = sum.wrapping_mul(radix).wrapping_add(digit_number);
175-
}
176-
None => {
177-
break;
178-
}
179-
}
180-
}
181-
182-
Some(sum)
183-
}
184-
185-
fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result<ControlFlow<()>> {
186-
let mut iter = input.iter().peekable();
187-
188-
while let Some(&current_byte) = iter.next() {
189-
if current_byte != b'\\' {
190-
output.write_all(&[current_byte])?;
191-
192-
continue;
193-
}
194-
195-
// This is for the \NNN syntax for octal sequences
196-
// Note that '0' is intentionally omitted, because the \0NNN syntax is handled below
197-
if let Some(&&first_digit @ b'1'..=b'7') = iter.peek() {
198-
// Unwrap because anything starting with "\1" through "\7" can be successfully parsed
199-
let parsed_octal_number = parse_backslash_number(
200-
&mut iter,
201-
BackslashNumberType::OctalStartingWithNonZero(first_digit),
202-
)
203-
.unwrap();
204-
205-
output.write_all(&[parsed_octal_number])?;
206-
207-
continue;
208-
}
209-
210-
if let Some(next) = iter.next() {
211-
let unescaped: &[u8] = match *next {
212-
b'\\' => br"\",
213-
b'a' => b"\x07",
214-
b'b' => b"\x08",
215-
b'c' => return Ok(ControlFlow::Break(())),
216-
b'e' => b"\x1B",
217-
b'f' => b"\x0C",
218-
b'n' => b"\n",
219-
b'r' => b"\r",
220-
b't' => b"\t",
221-
b'v' => b"\x0B",
222-
b'x' => {
223-
if let Some(parsed_hexadecimal_number) =
224-
parse_backslash_number(&mut iter, BackslashNumberType::Hexadecimal)
225-
{
226-
&[parsed_hexadecimal_number]
227-
} else {
228-
// "\x" with any non-hexadecimal digit after means "\x" is treated literally
229-
br"\x"
230-
}
231-
}
232-
b'0' => {
233-
if let Some(parsed_octal_number) = parse_backslash_number(
234-
&mut iter,
235-
BackslashNumberType::OctalStartingWithZero,
236-
) {
237-
&[parsed_octal_number]
238-
} else {
239-
// "\0" with any non-octal digit after it means "\0" is treated as ASCII '\0' (NUL), 0x00
240-
b"\0"
241-
}
242-
}
243-
other_byte => {
244-
// Backslash and the following byte are treated literally
245-
&[b'\\', other_byte]
246-
}
247-
};
248-
249-
output.write_all(unescaped)?;
250-
} else {
251-
output.write_all(br"\")?;
252-
}
253-
}
254-
255-
Ok(ControlFlow::Continue(()))
256-
}
257-
25826
// A workaround because clap interprets the first '--' as a marker that a value
25927
// follows. In order to use '--' as a value, we have to inject an additional '--'
26028
fn handle_double_hyphens(args: impl uucore::Args) -> impl uucore::Args {
@@ -367,8 +135,11 @@ fn execute(
367135
}
368136

369137
if escaped {
370-
if print_escaped(bytes, stdout_lock)?.is_break() {
371-
return Ok(());
138+
for item in parse_escape_only(bytes) {
139+
match item {
140+
EscapedChar::End => return Ok(()),
141+
c => c.write(&mut *stdout_lock)?,
142+
};
372143
}
373144
} else {
374145
stdout_lock.write_all(bytes)?;

0 commit comments

Comments
 (0)