use crate::{Builtin, ShellEnvironment};
pub struct Print;
impl Builtin for Print {
fn name(&self) -> &str {
"print"
}
fn execute(&self, args: &[&str], _env: &mut dyn ShellEnvironment) -> i32 {
let mut newline = true;
let mut raw = false;
let mut one_per_line = false;
let mut text_start = 0;
for (i, arg) in args.iter().enumerate() {
if !arg.starts_with('-') || arg.len() < 2 || *arg == "--" {
if *arg == "--" {
text_start = i + 1;
}
break;
}
let flag_bytes = &arg.as_bytes()[1..];
let all_known = flag_bytes
.iter()
.all(|b| matches!(b, b'n' | b'r' | b'l' | b'R'));
if all_known {
for &b in flag_bytes {
match b {
b'n' => newline = false,
b'r' | b'R' => raw = true,
b'l' => one_per_line = true,
_ => {}
}
}
text_start = i + 1;
} else {
break;
}
}
let text_args = &args[text_start..];
if one_per_line {
for arg in text_args {
println!("{arg}");
}
} else {
let joined = text_args.join(" ");
if raw {
print!("{joined}");
} else {
print!("{}", expand_escapes(&joined));
}
if newline {
println!();
}
}
0
}
}
fn expand_escapes(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next() {
Some('n') => out.push('\n'),
Some('t') => out.push('\t'),
Some('r') => out.push('\r'),
Some('a') => out.push('\x07'),
Some('b') => out.push('\x08'),
Some('\\') => out.push('\\'),
Some('e') | Some('E') => out.push('\x1B'),
Some('c') => break,
Some(other) => {
out.push('\\');
out.push(other);
}
None => out.push('\\'),
}
} else {
out.push(c);
}
}
out
}