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