use std::fmt;
use super::*;
fn write_sep(f: &mut fmt::Formatter<'_>, trailing_op: Option<ListOp>) -> fmt::Result {
if !matches!(trailing_op, Some(ListOp::Semi)) {
f.write_str(";")?;
}
Ok(())
}
fn write_case_arm(f: &mut fmt::Formatter<'_>, arm: &CaseArm) -> fmt::Result {
f.write_str(" ")?;
for (i, p) in arm.patterns.iter().enumerate() {
if i > 0 {
f.write_str("|")?;
}
write!(f, "{p}")?;
}
f.write_str(") ")?;
write!(f, "{}", arm.body)?;
f.write_str(" ;;")
}
fn write_redirs(f: &mut fmt::Formatter<'_>, redirs: &[Redir]) -> fmt::Result {
for r in redirs {
write!(f, " {r}")?;
}
Ok(())
}
fn write_body(f: &mut fmt::Formatter<'_>, script: &Script) -> fmt::Result {
for (i, stmt) in script.0.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{}", stmt.pipeline)?;
match &stmt.op {
Some(ListOp::Semi) | None => f.write_str(";")?,
Some(op) => write!(f, " {op}")?,
}
}
Ok(())
}
impl fmt::Display for Script {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, stmt) in self.0.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{}", stmt.pipeline)?;
match &stmt.op {
Some(ListOp::Semi) => f.write_str(";")?,
Some(op) => write!(f, " {op}")?,
None => {}
}
}
Ok(())
}
}
impl fmt::Display for ListOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ListOp::And => f.write_str("&&"),
ListOp::Or => f.write_str("||"),
ListOp::Semi => f.write_str(";"),
ListOp::Amp => f.write_str("&"),
}
}
}
impl fmt::Display for Pipeline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.bang {
f.write_str("! ")?;
}
for (i, cmd) in self.commands.iter().enumerate() {
if i > 0 {
f.write_str(" | ")?;
}
write!(f, "{cmd}")?;
}
Ok(())
}
}
impl fmt::Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Cmd::Simple(s) => write!(f, "{s}"),
Cmd::Subshell { body, redirs } => {
write!(f, "({body})")?;
for r in redirs {
write!(f, " {r}")?;
}
Ok(())
}
Cmd::BraceGroup { body, redirs } => {
write!(f, "{{ {body}")?;
write_sep(f, body.0.last().and_then(|s| s.op))?;
f.write_str(" }")?;
for r in redirs {
write!(f, " {r}")?;
}
Ok(())
}
Cmd::FunctionDef { name, body } => {
write!(f, "{name}() {{ {body}")?;
write_sep(f, body.0.last().and_then(|s| s.op))?;
f.write_str(" }")
}
Cmd::For { var, items, body, redirs } => {
write!(f, "for {var}")?;
if !items.is_empty() {
f.write_str(" in")?;
for item in items {
write!(f, " {item}")?;
}
}
write_sep(f, None)?;
write!(f, " do ")?;
write_body(f, body)?;
f.write_str(" done")?;
write_redirs(f, redirs)
}
Cmd::While { cond, body, redirs } => {
write!(f, "while {cond}")?;
write_sep(f, cond.0.last().and_then(|s| s.op))?;
write!(f, " do ")?;
write_body(f, body)?;
f.write_str(" done")?;
write_redirs(f, redirs)
}
Cmd::Until { cond, body, redirs } => {
write!(f, "until {cond}")?;
write_sep(f, cond.0.last().and_then(|s| s.op))?;
write!(f, " do ")?;
write_body(f, body)?;
f.write_str(" done")?;
write_redirs(f, redirs)
}
Cmd::If { branches, else_body, redirs } => {
for (i, branch) in branches.iter().enumerate() {
if i == 0 {
write!(f, "if {}", branch.cond)?;
} else {
write!(f, " elif {}", branch.cond)?;
}
write_sep(f, branch.cond.0.last().and_then(|s| s.op))?;
write!(f, " then ")?;
write_body(f, &branch.body)?;
f.write_str("")?;
}
if let Some(eb) = else_body {
write!(f, " else ")?;
write_body(f, eb)?;
}
f.write_str(" fi")?;
write_redirs(f, redirs)
}
Cmd::Case { subject, arms, redirs } => {
write!(f, "case {subject} in")?;
for arm in arms {
write_case_arm(f, arm)?;
}
f.write_str(" esac")?;
write_redirs(f, redirs)
}
Cmd::DoubleBracket { words, redirs } => {
f.write_str("[[")?;
for w in words {
write!(f, " {w}")?;
}
f.write_str(" ]]")?;
for r in redirs {
write!(f, " {r}")?;
}
Ok(())
}
}
}
}
impl fmt::Display for SimpleCmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (name, val) in &self.env {
if !first { f.write_str(" ")?; }
first = false;
write!(f, "{name}={val}")?;
}
for w in &self.words {
if !first { f.write_str(" ")?; }
first = false;
write!(f, "{w}")?;
}
for r in &self.redirs {
if !first { f.write_str(" ")?; }
first = false;
write!(f, "{r}")?;
}
Ok(())
}
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for part in &self.0 {
write!(f, "{part}")?;
}
Ok(())
}
}
impl fmt::Display for WordPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WordPart::Lit(s) => f.write_str(s),
WordPart::Escape(c) => write!(f, "\\{c}"),
WordPart::SQuote(s) => write!(f, "'{s}'"),
WordPart::DQuote(w) => write!(f, "\"{w}\""),
WordPart::CmdSub(s) => {
let rendered = s.to_string();
if rendered.starts_with('(') {
write!(f, "$( {rendered})")
} else {
write!(f, "$({rendered})")
}
}
WordPart::ProcSub(s) => write!(f, "<({s})"),
WordPart::Backtick(s) => write!(f, "`{s}`"),
WordPart::Arith(s) => write!(f, "$(({s}))"),
}
}
}
impl fmt::Display for Redir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Redir::Write { fd, target, mode } => {
let both = matches!(mode, WriteMode::TruncateBoth | WriteMode::AppendBoth);
if *fd != 1 && !both { write!(f, "{fd}")?; }
let op = match mode {
WriteMode::Truncate => ">",
WriteMode::Append => ">>",
WriteMode::Clobber => ">|",
WriteMode::TruncateBoth => "&>",
WriteMode::AppendBoth => "&>>",
};
write!(f, "{op} {target}")
}
Redir::Read { fd, target } => {
if *fd != 0 { write!(f, "{fd}")?; }
write!(f, "< {target}")
}
Redir::ReadWrite { fd, target } => {
if *fd != 0 { write!(f, "{fd}")?; }
write!(f, "<> {target}")
}
Redir::HereStr(w) => write!(f, "<<< {w}"),
Redir::HereDoc { delimiter, strip_tabs, .. } => {
if *strip_tabs { write!(f, "<<-{delimiter}") } else { write!(f, "<<{delimiter}") }
}
Redir::DupFd { src, dst } => {
if *src != 1 { write!(f, "{src}")?; }
write!(f, ">&{dst}")
}
}
}
}
#[cfg(test)]
mod tests {
use crate::cst::parse;
#[test]
fn display_simple() {
let s = parse("echo hello").unwrap();
assert_eq!(s.to_string(), "echo hello");
}
#[test]
fn display_pipeline() {
let s = parse("grep foo | head -5").unwrap();
assert_eq!(s.to_string(), "grep foo | head -5");
}
#[test]
fn display_sequence() {
let s = parse("ls && echo done").unwrap();
assert_eq!(s.to_string(), "ls && echo done");
}
#[test]
fn display_single_quoted() {
let s = parse("echo 'hello world'").unwrap();
assert_eq!(s.to_string(), "echo 'hello world'");
}
#[test]
fn display_double_quoted() {
let s = parse("echo \"hello world\"").unwrap();
assert_eq!(s.to_string(), "echo \"hello world\"");
}
#[test]
fn display_redirect() {
let s = parse("echo hello > /dev/null").unwrap();
assert_eq!(s.to_string(), "echo hello > /dev/null");
}
#[test]
fn display_fd_redirect() {
let s = parse("echo hello 2>&1").unwrap();
assert_eq!(s.to_string(), "echo hello 2>&1");
}
#[test]
fn display_cmd_sub() {
let s = parse("echo $(ls)").unwrap();
assert_eq!(s.to_string(), "echo $(ls)");
}
#[test]
fn display_for() {
let s = parse("for x in 1 2 3; do echo $x; done").unwrap();
assert_eq!(s.to_string(), "for x in 1 2 3; do echo $x; done");
}
#[test]
fn display_if() {
let s = parse("if true; then echo yes; else echo no; fi").unwrap();
assert_eq!(s.to_string(), "if true; then echo yes; else echo no; fi");
}
#[test]
fn display_for_with_redirect() {
let s = parse("for x in 1 2; do echo $x; done 2>/dev/null").unwrap();
assert_eq!(s.to_string(), "for x in 1 2; do echo $x; done 2> /dev/null");
}
#[test]
fn display_if_with_redirect() {
let s = parse("if true; then echo yes; fi 2>&1").unwrap();
assert_eq!(s.to_string(), "if true; then echo yes; fi 2>&1");
}
#[test]
fn display_env_prefix() {
let s = parse("FOO=bar ls").unwrap();
assert_eq!(s.to_string(), "FOO=bar ls");
}
#[test]
fn display_subshell() {
let s = parse("(echo hello)").unwrap();
assert_eq!(s.to_string(), "(echo hello)");
}
#[test]
fn display_negation() {
let s = parse("! echo hello").unwrap();
assert_eq!(s.to_string(), "! echo hello");
}
}