1use super::*;
2use std::fmt;
3
4fn write_sep(f: &mut fmt::Formatter<'_>, trailing_op: Option<ListOp>) -> fmt::Result {
5 if !matches!(trailing_op, Some(ListOp::Semi)) {
6 f.write_str(";")?;
7 }
8 Ok(())
9}
10
11fn write_case_arm(f: &mut fmt::Formatter<'_>, arm: &CaseArm) -> fmt::Result {
12 f.write_str(" ")?;
13 for (i, p) in arm.patterns.iter().enumerate() {
14 if i > 0 {
15 f.write_str("|")?;
16 }
17 write!(f, "{p}")?;
18 }
19 f.write_str(") ")?;
20 write!(f, "{}", arm.body)?;
24 f.write_str(" ;;")
25}
26
27fn write_redirs(f: &mut fmt::Formatter<'_>, redirs: &[Redir]) -> fmt::Result {
28 for r in redirs {
29 write!(f, " {r}")?;
30 }
31 Ok(())
32}
33
34fn write_body(f: &mut fmt::Formatter<'_>, script: &Script) -> fmt::Result {
35 for (i, stmt) in script.0.iter().enumerate() {
36 if i > 0 {
37 f.write_str(" ")?;
38 }
39 write!(f, "{}", stmt.pipeline)?;
40 match &stmt.op {
41 Some(ListOp::Semi) | None => f.write_str(";")?,
42 Some(op) => write!(f, " {op}")?,
43 }
44 }
45 Ok(())
46}
47
48impl fmt::Display for Script {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 for (i, stmt) in self.0.iter().enumerate() {
51 if i > 0 {
52 f.write_str(" ")?;
53 }
54 write!(f, "{}", stmt.pipeline)?;
55 match &stmt.op {
56 Some(ListOp::Semi) => f.write_str(";")?,
57 Some(op) => write!(f, " {op}")?,
58 None => {}
59 }
60 }
61 Ok(())
62 }
63}
64
65impl fmt::Display for ListOp {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 ListOp::And => f.write_str("&&"),
69 ListOp::Or => f.write_str("||"),
70 ListOp::Semi => f.write_str(";"),
71 ListOp::Amp => f.write_str("&"),
72 }
73 }
74}
75
76impl fmt::Display for Pipeline {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 if self.bang {
79 f.write_str("! ")?;
80 }
81 for (i, cmd) in self.commands.iter().enumerate() {
82 if i > 0 {
83 f.write_str(" | ")?;
84 }
85 write!(f, "{cmd}")?;
86 }
87 Ok(())
88 }
89}
90
91impl fmt::Display for Cmd {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match self {
94 Cmd::Simple(s) => write!(f, "{s}"),
95 Cmd::Subshell { body, redirs } => {
96 write!(f, "({body})")?;
97 for r in redirs {
98 write!(f, " {r}")?;
99 }
100 Ok(())
101 }
102 Cmd::BraceGroup { body, redirs } => {
106 write!(f, "{{ {body}")?;
107 write_sep(f, body.0.last().and_then(|s| s.op))?;
108 f.write_str(" }")?;
109 for r in redirs {
110 write!(f, " {r}")?;
111 }
112 Ok(())
113 }
114 Cmd::FunctionDef { name, body } => {
115 write!(f, "{name}() {{ {body}")?;
116 write_sep(f, body.0.last().and_then(|s| s.op))?;
117 f.write_str(" }")
118 }
119 Cmd::For { var, items, body, redirs } => {
120 write!(f, "for {var}")?;
121 if !items.is_empty() {
122 f.write_str(" in")?;
123 for item in items {
124 write!(f, " {item}")?;
125 }
126 }
127 write_sep(f, None)?;
128 write!(f, " do ")?;
129 write_body(f, body)?;
130 f.write_str(" done")?;
131 write_redirs(f, redirs)
132 }
133 Cmd::While { cond, body, redirs } => {
134 write!(f, "while {cond}")?;
135 write_sep(f, cond.0.last().and_then(|s| s.op))?;
136 write!(f, " do ")?;
137 write_body(f, body)?;
138 f.write_str(" done")?;
139 write_redirs(f, redirs)
140 }
141 Cmd::Until { cond, body, redirs } => {
142 write!(f, "until {cond}")?;
143 write_sep(f, cond.0.last().and_then(|s| s.op))?;
144 write!(f, " do ")?;
145 write_body(f, body)?;
146 f.write_str(" done")?;
147 write_redirs(f, redirs)
148 }
149 Cmd::If { branches, else_body, redirs } => {
150 for (i, branch) in branches.iter().enumerate() {
151 if i == 0 {
152 write!(f, "if {}", branch.cond)?;
153 } else {
154 write!(f, " elif {}", branch.cond)?;
155 }
156 write_sep(f, branch.cond.0.last().and_then(|s| s.op))?;
157 write!(f, " then ")?;
158 write_body(f, &branch.body)?;
159 f.write_str("")?;
160 }
161 if let Some(eb) = else_body {
162 write!(f, " else ")?;
163 write_body(f, eb)?;
164 }
165 f.write_str(" fi")?;
166 write_redirs(f, redirs)
167 }
168 Cmd::Case { subject, arms, redirs } => {
169 write!(f, "case {subject} in")?;
170 for arm in arms {
171 write_case_arm(f, arm)?;
172 }
173 f.write_str(" esac")?;
174 write_redirs(f, redirs)
175 }
176 Cmd::DoubleBracket { words, redirs } => {
177 f.write_str("[[")?;
178 for w in words {
179 write!(f, " {w}")?;
180 }
181 f.write_str(" ]]")?;
182 for r in redirs {
183 write!(f, " {r}")?;
184 }
185 Ok(())
186 }
187 }
188 }
189}
190
191impl fmt::Display for SimpleCmd {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let mut first = true;
194 for (name, val) in &self.env {
195 if !first {
196 f.write_str(" ")?;
197 }
198 first = false;
199 write!(f, "{name}={val}")?;
200 }
201 for w in &self.words {
202 if !first {
203 f.write_str(" ")?;
204 }
205 first = false;
206 write!(f, "{w}")?;
207 }
208 for r in &self.redirs {
209 if !first {
210 f.write_str(" ")?;
211 }
212 first = false;
213 write!(f, "{r}")?;
214 }
215 Ok(())
216 }
217}
218
219impl fmt::Display for Word {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 for part in &self.0 {
222 write!(f, "{part}")?;
223 }
224 Ok(())
225 }
226}
227
228impl fmt::Display for WordPart {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 WordPart::Lit(s) => f.write_str(s),
232 WordPart::Escape(c) => write!(f, "\\{c}"),
233 WordPart::SQuote(s) => write!(f, "'{s}'"),
234 WordPart::AnsiC(s) => write!(f, "$'{s}'"),
235 WordPart::DQuote(w) => write!(f, "\"{w}\""),
236 WordPart::CmdSub(s) => {
237 let rendered = s.to_string();
238 if rendered.starts_with('(') { write!(f, "$( {rendered})") } else { write!(f, "$({rendered})") }
239 }
240 WordPart::ProcSub(s) => write!(f, "<({s})"),
241 WordPart::Backtick(s) => write!(f, "`{s}`"),
242 WordPart::Arith(w) => write!(f, "$(({w}))"),
243 }
244 }
245}
246
247impl fmt::Display for Redir {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 match self {
250 Redir::Write { fd, target, mode } => {
251 let both = matches!(mode, WriteMode::TruncateBoth | WriteMode::AppendBoth);
254 if *fd != 1 && !both {
255 write!(f, "{fd}")?;
256 }
257 let op = match mode {
258 WriteMode::Truncate => ">",
259 WriteMode::Append => ">>",
260 WriteMode::Clobber => ">|",
261 WriteMode::TruncateBoth => "&>",
262 WriteMode::AppendBoth => "&>>",
263 };
264 write!(f, "{op} {target}")
265 }
266 Redir::Read { fd, target } => {
267 if *fd != 0 {
268 write!(f, "{fd}")?;
269 }
270 write!(f, "< {target}")
271 }
272 Redir::ReadWrite { fd, target } => {
273 if *fd != 0 {
274 write!(f, "{fd}")?;
275 }
276 write!(f, "<> {target}")
277 }
278 Redir::HereStr(w) => write!(f, "<<< {w}"),
279 Redir::HereDoc { delimiter, strip_tabs, .. } => {
280 if *strip_tabs {
281 write!(f, "<<-{delimiter}")
282 } else {
283 write!(f, "<<{delimiter}")
284 }
285 }
286 Redir::DupFd { src, dst } => {
287 if *src != 1 {
288 write!(f, "{src}")?;
289 }
290 write!(f, ">&{dst}")
291 }
292 }
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use crate::cst::parse;
299
300 #[test]
301 fn display_simple() {
302 let s = parse("echo hello").unwrap();
303 assert_eq!(s.to_string(), "echo hello");
304 }
305
306 #[test]
307 fn display_pipeline() {
308 let s = parse("grep foo | head -5").unwrap();
309 assert_eq!(s.to_string(), "grep foo | head -5");
310 }
311
312 #[test]
313 fn display_sequence() {
314 let s = parse("ls && echo done").unwrap();
315 assert_eq!(s.to_string(), "ls && echo done");
316 }
317
318 #[test]
319 fn display_single_quoted() {
320 let s = parse("echo 'hello world'").unwrap();
321 assert_eq!(s.to_string(), "echo 'hello world'");
322 }
323
324 #[test]
325 fn display_double_quoted() {
326 let s = parse("echo \"hello world\"").unwrap();
327 assert_eq!(s.to_string(), "echo \"hello world\"");
328 }
329
330 #[test]
331 fn display_redirect() {
332 let s = parse("echo hello > /dev/null").unwrap();
333 assert_eq!(s.to_string(), "echo hello > /dev/null");
334 }
335
336 #[test]
337 fn display_fd_redirect() {
338 let s = parse("echo hello 2>&1").unwrap();
339 assert_eq!(s.to_string(), "echo hello 2>&1");
340 }
341
342 #[test]
343 fn display_cmd_sub() {
344 let s = parse("echo $(ls)").unwrap();
345 assert_eq!(s.to_string(), "echo $(ls)");
346 }
347
348 #[test]
349 fn display_for() {
350 let s = parse("for x in 1 2 3; do echo $x; done").unwrap();
351 assert_eq!(s.to_string(), "for x in 1 2 3; do echo $x; done");
352 }
353
354 #[test]
355 fn display_if() {
356 let s = parse("if true; then echo yes; else echo no; fi").unwrap();
357 assert_eq!(s.to_string(), "if true; then echo yes; else echo no; fi");
358 }
359
360 #[test]
361 fn display_for_with_redirect() {
362 let s = parse("for x in 1 2; do echo $x; done 2>/dev/null").unwrap();
363 assert_eq!(s.to_string(), "for x in 1 2; do echo $x; done 2> /dev/null");
364 }
365
366 #[test]
367 fn display_if_with_redirect() {
368 let s = parse("if true; then echo yes; fi 2>&1").unwrap();
369 assert_eq!(s.to_string(), "if true; then echo yes; fi 2>&1");
370 }
371
372 #[test]
373 fn display_env_prefix() {
374 let s = parse("FOO=bar ls").unwrap();
375 assert_eq!(s.to_string(), "FOO=bar ls");
376 }
377
378 #[test]
379 fn display_subshell() {
380 let s = parse("(echo hello)").unwrap();
381 assert_eq!(s.to_string(), "(echo hello)");
382 }
383
384 #[test]
385 fn display_negation() {
386 let s = parse("! echo hello").unwrap();
387 assert_eq!(s.to_string(), "! echo hello");
388 }
389}