use super::SimpleCommand;
use crate::shell::tokens::basename;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Piped {
Text(String),
Unknown(String),
Output(String),
}
impl Piped {
pub(super) fn text(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text),
Self::Unknown(_) | Self::Output(_) => None,
}
}
pub(super) fn unknown(&self) -> Option<&str> {
match self {
Self::Unknown(program) => Some(program),
Self::Text(_) | Self::Output(_) => None,
}
}
pub(super) fn is_hidden(&self) -> bool {
!matches!(self, Self::Text(_))
}
}
pub(super) fn data(cmd: &SimpleCommand) -> Option<Piped> {
let argv: Vec<&str> = cmd.words.iter().map(|w| w.text.as_str()).collect();
match argv.as_slice() {
[cat] | [cat, "-"] if basename(cat) == "cat" && cmd.reads.is_empty() => Some(Piped::Text(
cmd.stdin_texts().collect::<Vec<_>>().join("\n"),
)),
[program, operands @ ..] if matches!(basename(program), "echo" | "printf") => {
let printed: Vec<&str> = operands
.iter()
.copied()
.skip_while(|a| matches!(*a, "-n" | "-e" | "-E" | "--"))
.collect();
let format = basename(program) == "printf" && printed.iter().any(|a| a.contains('%'));
let text = expand_newlines(&printed.join(" ")).filter(|_| !format);
Some(text.map_or_else(|| Piped::Unknown(basename(program).to_owned()), Piped::Text))
}
[program, ..] => Some(Piped::Output(basename(program).to_owned())),
[] => None,
}
}
fn expand_newlines(text: &str) -> Option<String> {
let mut out = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
match c {
'\\' if chars.next()? == 'n' => out.push('\n'),
'\\' => return None,
_ => out.push(c),
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::expand_newlines;
#[test]
fn only_newline_escapes_are_read() {
assert_eq!(expand_newlines("ls\\npwd").as_deref(), Some("ls\npwd"));
assert_eq!(expand_newlines("plain").as_deref(), Some("plain"));
for text in ["\\x63at .env", "\\\\n", "\\101", "\\t", "end\\"] {
assert_eq!(expand_newlines(text), None, "{text}");
}
}
}