pub struct EchoConfig {
pub trailing_newline: bool,
pub interpret_escapes: bool,
}
impl Default for EchoConfig {
fn default() -> Self {
Self {
trailing_newline: true,
interpret_escapes: false,
}
}
}
pub fn parse_echo_args(args: &[String]) -> (EchoConfig, &[String]) {
if std::env::var_os("POSIXLY_CORRECT").is_some() {
let mut config = EchoConfig {
trailing_newline: true,
interpret_escapes: true,
};
if args.first().map(|s| s.as_str()) == Some("-n") {
config.trailing_newline = false;
let mut idx = 1;
for arg in &args[1..] {
let bytes = arg.as_bytes();
if bytes.len() < 2 || bytes[0] != b'-' {
break;
}
let all_flags = bytes[1..]
.iter()
.all(|&b| b == b'n' || b == b'e' || b == b'E');
if !all_flags {
break;
}
for &b in &bytes[1..] {
if b == b'n' {
config.trailing_newline = false;
}
}
idx += 1;
}
return (config, &args[idx..]);
}
return (config, args);
}
let mut config = EchoConfig::default();
let mut idx = 0;
for arg in args {
let bytes = arg.as_bytes();
if bytes.len() < 2 || bytes[0] != b'-' {
break;
}
let all_flags = bytes[1..]
.iter()
.all(|&b| b == b'n' || b == b'e' || b == b'E');
if !all_flags {
break;
}
for &b in &bytes[1..] {
match b {
b'n' => config.trailing_newline = false,
b'e' => config.interpret_escapes = true,
b'E' => config.interpret_escapes = false,
_ => unreachable!(),
}
}
idx += 1;
}
(config, &args[idx..])
}
pub fn echo_output(args: &[String], config: &EchoConfig) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
for (i, arg) in args.iter().enumerate() {
if i > 0 {
out.push(b' ');
}
if config.interpret_escapes {
if !expand_escapes(arg.as_bytes(), &mut out) {
return out;
}
} else {
out.extend_from_slice(arg.as_bytes());
}
}
if config.trailing_newline {
out.push(b'\n');
}
out
}
fn expand_escapes(src: &[u8], out: &mut Vec<u8>) -> bool {
let len = src.len();
let mut i = 0;
while i < len {
if src[i] != b'\\' {
out.push(src[i]);
i += 1;
continue;
}
i += 1;
if i >= len {
out.push(b'\\');
break;
}
match src[i] {
b'\\' => out.push(b'\\'),
b'a' => out.push(0x07),
b'b' => out.push(0x08),
b'c' => return false, b'e' => out.push(0x1B),
b'f' => out.push(0x0C),
b'n' => out.push(b'\n'),
b'r' => out.push(b'\r'),
b't' => out.push(b'\t'),
b'v' => out.push(0x0B),
b'0' => {
let mut val: u16 = 0;
let mut consumed = 0;
let mut j = i + 1;
while j < len && consumed < 3 && src[j] >= b'0' && src[j] <= b'7' {
val = val * 8 + (src[j] - b'0') as u16;
j += 1;
consumed += 1;
}
out.push(val as u8);
i = j - 1; }
b'1'..=b'7' => {
let first = src[i] - b'0';
let mut val = first as u16;
let mut consumed = 0;
let mut j = i + 1;
while j < len && consumed < 2 && src[j] >= b'0' && src[j] <= b'7' {
val = val * 8 + (src[j] - b'0') as u16;
j += 1;
consumed += 1;
}
out.push(val as u8);
i = j - 1; }
b'x' => {
let start = i + 1;
let mut end = start;
while end < len && end < start + 2 && is_hex_digit(src[end]) {
end += 1;
}
if start == end {
out.push(b'\\');
out.push(b'x');
} else {
out.push(parse_hex(&src[start..end]));
i = end - 1; }
}
other => {
out.push(b'\\');
out.push(other);
}
}
i += 1;
}
true
}
#[inline]
fn is_hex_digit(b: u8) -> bool {
b.is_ascii_hexdigit()
}
fn parse_hex(digits: &[u8]) -> u8 {
let mut val: u8 = 0;
for &d in digits {
let nibble = match d {
b'0'..=b'9' => d - b'0',
b'a'..=b'f' => d - b'a' + 10,
b'A'..=b'F' => d - b'A' + 10,
_ => unreachable!(),
};
val = val * 16 + nibble;
}
val
}