use crate::ast::{CasePattern, Node, NodeKind};
use super::lists::format_list;
use super::redirects::{format_pipeline, format_redirect};
use super::words::{indent_str, process_word_value};
#[allow(clippy::too_many_lines)]
pub(super) fn format_node(node: &Node, out: &mut String, indent: usize) {
match &node.kind {
NodeKind::Word { value, .. } => out.push_str(value),
NodeKind::Command {
assignments,
words,
redirects,
} => {
format_command(assignments, words, redirects, out);
}
NodeKind::Pipeline { commands, .. } => format_pipeline(commands, out, indent),
NodeKind::List { items } => format_list(items, out, indent),
NodeKind::If {
condition,
then_body,
else_body,
..
} => format_if(condition, then_body, else_body.as_deref(), out, indent),
NodeKind::While {
condition,
body,
redirects,
..
} => format_while_until("while", condition, body, redirects, out, indent),
NodeKind::Until {
condition,
body,
redirects,
..
} => format_while_until("until", condition, body, redirects, out, indent),
NodeKind::For {
var, words, body, ..
} => format_for(var, words.as_deref(), body, out, indent),
NodeKind::ForArith {
init,
cond,
incr,
body,
..
} => format_for_arith(init, cond, incr, body, out, indent),
NodeKind::Case {
word,
patterns,
redirects,
..
} => format_case(word, patterns, redirects, out, indent),
NodeKind::Function { name, body } => {
out.push_str("function ");
out.push_str(name);
out.push_str(" () \n");
format_function_body(body, out, indent);
}
NodeKind::Subshell {
body, redirects, ..
} => {
out.push_str("( ");
format_node(body, out, indent);
out.push_str(" )");
for r in redirects {
out.push(' ');
format_redirect(r, out);
}
}
NodeKind::BraceGroup {
body, redirects, ..
} => {
out.push_str("{ ");
format_node(body, out, indent);
out.push_str("; }");
for r in redirects {
out.push(' ');
format_redirect(r, out);
}
}
NodeKind::Negation { pipeline } => {
out.push_str("! ");
format_node(pipeline, out, indent);
}
NodeKind::Time { pipeline, posix } => {
if *posix {
out.push_str("time -p ");
} else {
out.push_str("time ");
}
format_node(pipeline, out, indent);
}
NodeKind::Coproc { name, command } => {
out.push_str("coproc ");
if let Some(n) = name {
out.push_str(n);
out.push(' ');
}
format_node(command, out, indent);
}
NodeKind::ConditionalExpr { body, .. } => {
out.push_str("[[ ");
format_cond_node(body, out);
out.push_str(" ]]");
}
NodeKind::Empty => {}
_ => {
out.push_str(&node.to_string());
}
}
}
pub(super) fn format_command(
assignments: &[Node],
words: &[Node],
redirects: &[Node],
out: &mut String,
) {
format_command_words(assignments, words, out);
for (i, r) in redirects.iter().enumerate() {
if !assignments.is_empty() || !words.is_empty() || i > 0 {
out.push(' ');
}
format_redirect(r, out);
}
}
pub(super) fn format_command_words(assignments: &[Node], words: &[Node], out: &mut String) {
for (i, w) in assignments.iter().chain(words.iter()).enumerate() {
if i > 0 {
out.push(' ');
}
if let NodeKind::Word { value, spans, .. } = &w.kind {
out.push_str(&process_word_value(value, spans));
} else {
out.push_str(&w.to_string());
}
}
}
fn format_if(
condition: &Node,
then_body: &Node,
else_body: Option<&Node>,
out: &mut String,
indent: usize,
) {
out.push_str("if ");
format_node(condition, out, indent);
out.push_str("; then\n");
indent_str(out, indent + 4);
format_node(then_body, out, indent + 4);
out.push_str(";\n");
if let Some(eb) = else_body {
indent_str(out, indent);
out.push_str("else\n");
indent_str(out, indent + 4);
format_node(eb, out, indent + 4);
out.push_str(";\n");
}
indent_str(out, indent);
out.push_str("fi");
}
#[allow(clippy::too_many_arguments)]
fn format_while_until(
keyword: &str,
condition: &Node,
body: &Node,
redirects: &[Node],
out: &mut String,
indent: usize,
) {
out.push_str(keyword);
out.push(' ');
format_node(condition, out, indent);
out.push_str("; do\n");
indent_str(out, indent + 4);
format_node(body, out, indent + 4);
out.push_str(";\n");
indent_str(out, indent);
out.push_str("done");
for r in redirects {
out.push(' ');
format_redirect(r, out);
}
}
fn format_for(var: &str, words: Option<&[Node]>, body: &Node, out: &mut String, indent: usize) {
out.push_str("for ");
out.push_str(var);
if let Some(ws) = words {
out.push_str(" in");
for w in ws {
out.push(' ');
if let NodeKind::Word { value, .. } = &w.kind {
out.push_str(value);
}
}
}
out.push_str(";\n");
indent_str(out, indent);
out.push_str("do\n");
indent_str(out, indent + 4);
format_node(body, out, indent + 4);
out.push_str(";\n");
indent_str(out, indent);
out.push_str("done");
}
#[allow(clippy::too_many_arguments)]
fn format_for_arith(
init: &str,
cond: &str,
incr: &str,
body: &Node,
out: &mut String,
indent: usize,
) {
out.push_str("for ((");
out.push_str(init);
out.push_str("; ");
out.push_str(cond);
out.push_str("; ");
out.push_str(incr);
out.push_str("))\n");
indent_str(out, indent);
out.push_str("do\n");
indent_str(out, indent + 4);
format_node(body, out, indent + 4);
out.push_str(";\n");
indent_str(out, indent);
out.push_str("done");
}
fn format_case(
word: &Node,
patterns: &[CasePattern],
redirects: &[Node],
out: &mut String,
indent: usize,
) {
out.push_str("case ");
if let NodeKind::Word { value, .. } = &word.kind {
out.push_str(value);
}
out.push_str(" in ");
for (i, p) in patterns.iter().enumerate() {
if i > 0 {
out.push('\n');
indent_str(out, indent + 4);
}
for (j, pw) in p.patterns.iter().enumerate() {
if j > 0 {
out.push_str(" | ");
}
if let NodeKind::Word { value, .. } = &pw.kind {
out.push_str(value);
}
}
out.push_str(")\n");
indent_str(out, indent + 8);
if let Some(body) = &p.body {
format_node(body, out, indent + 8);
}
out.push('\n');
indent_str(out, indent + 4);
out.push_str(&p.terminator);
}
out.push('\n');
indent_str(out, indent);
out.push_str("esac");
for r in redirects {
out.push(' ');
format_redirect(r, out);
}
}
fn format_function_body(body: &Node, out: &mut String, indent: usize) {
if let NodeKind::BraceGroup { body: inner, .. } = &body.kind {
out.push_str("{ \n");
indent_str(out, indent + 4);
format_node(inner, out, indent + 4);
out.push('\n');
indent_str(out, indent);
out.push('}');
} else {
format_node(body, out, indent);
}
}
fn format_cond_node(node: &Node, out: &mut String) {
match &node.kind {
NodeKind::UnaryTest { op, operand } => {
out.push_str(op);
out.push(' ');
format_cond_node(operand, out);
}
NodeKind::BinaryTest { op, left, right } => {
format_cond_node(left, out);
out.push(' ');
out.push_str(op);
out.push(' ');
format_cond_node(right, out);
}
NodeKind::CondAnd { left, right } => {
format_cond_node(left, out);
out.push_str(" && ");
format_cond_node(right, out);
}
NodeKind::CondOr { left, right } => {
format_cond_node(left, out);
out.push_str(" || ");
format_cond_node(right, out);
}
NodeKind::CondNot { operand } => {
out.push_str("! ");
format_cond_node(operand, out);
}
NodeKind::CondTerm { value, .. } => {
out.push_str(value);
}
NodeKind::CondParen { inner } => {
out.push_str("( ");
format_cond_node(inner, out);
out.push_str(" )");
}
_ => {
out.push_str(&node.to_string());
}
}
}