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::FunctionDef { name, body } => write!(f, "{name}() {{ {body}; }}"),
94 Cmd::For { var, items, body, redirs } => {
95 write!(f, "for {var}")?;
96 if !items.is_empty() {
97 f.write_str(" in")?;
98 for item in items {
99 write!(f, " {item}")?;
100 }
101 }
102 write_sep(f, None)?;
103 write!(f, " do ")?;
104 write_body(f, body)?;
105 f.write_str(" done")?;
106 write_redirs(f, redirs)
107 }
108 Cmd::While { cond, body, redirs } => {
109 write!(f, "while {cond}")?;
110 write_sep(f, cond.0.last().and_then(|s| s.op))?;
111 write!(f, " do ")?;
112 write_body(f, body)?;
113 f.write_str(" done")?;
114 write_redirs(f, redirs)
115 }
116 Cmd::Until { cond, body, redirs } => {
117 write!(f, "until {cond}")?;
118 write_sep(f, cond.0.last().and_then(|s| s.op))?;
119 write!(f, " do ")?;
120 write_body(f, body)?;
121 f.write_str(" done")?;
122 write_redirs(f, redirs)
123 }
124 Cmd::If { branches, else_body, redirs } => {
125 for (i, branch) in branches.iter().enumerate() {
126 if i == 0 {
127 write!(f, "if {}", branch.cond)?;
128 } else {
129 write!(f, " elif {}", branch.cond)?;
130 }
131 write_sep(f, branch.cond.0.last().and_then(|s| s.op))?;
132 write!(f, " then ")?;
133 write_body(f, &branch.body)?;
134 f.write_str("")?;
135 }
136 if let Some(eb) = else_body {
137 write!(f, " else ")?;
138 write_body(f, eb)?;
139 }
140 f.write_str(" fi")?;
141 write_redirs(f, redirs)
142 }
143 Cmd::DoubleBracket { words, redirs } => {
144 f.write_str("[[")?;
145 for w in words {
146 write!(f, " {w}")?;
147 }
148 f.write_str(" ]]")?;
149 for r in redirs {
150 write!(f, " {r}")?;
151 }
152 Ok(())
153 }
154 }
155 }
156}
157
158impl fmt::Display for SimpleCmd {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 let mut first = true;
161 for (name, val) in &self.env {
162 if !first { f.write_str(" ")?; }
163 first = false;
164 write!(f, "{name}={val}")?;
165 }
166 for w in &self.words {
167 if !first { f.write_str(" ")?; }
168 first = false;
169 write!(f, "{w}")?;
170 }
171 for r in &self.redirs {
172 if !first { f.write_str(" ")?; }
173 first = false;
174 write!(f, "{r}")?;
175 }
176 Ok(())
177 }
178}
179
180impl fmt::Display for Word {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 for part in &self.0 {
183 write!(f, "{part}")?;
184 }
185 Ok(())
186 }
187}
188
189impl fmt::Display for WordPart {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 match self {
192 WordPart::Lit(s) => f.write_str(s),
193 WordPart::Escape(c) => write!(f, "\\{c}"),
194 WordPart::SQuote(s) => write!(f, "'{s}'"),
195 WordPart::DQuote(w) => write!(f, "\"{w}\""),
196 WordPart::CmdSub(s) => {
197 let rendered = s.to_string();
198 if rendered.starts_with('(') {
199 write!(f, "$( {rendered})")
200 } else {
201 write!(f, "$({rendered})")
202 }
203 }
204 WordPart::ProcSub(s) => write!(f, "<({s})"),
205 WordPart::Backtick(s) => write!(f, "`{s}`"),
206 WordPart::Arith(s) => write!(f, "$(({s}))"),
207 }
208 }
209}
210
211impl fmt::Display for Redir {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 match self {
214 Redir::Write { fd, target, append } => {
215 if *fd != 1 { write!(f, "{fd}")?; }
216 if *append { write!(f, ">> {target}") } else { write!(f, "> {target}") }
217 }
218 Redir::Read { fd, target } => {
219 if *fd != 0 { write!(f, "{fd}")?; }
220 write!(f, "< {target}")
221 }
222 Redir::HereStr(w) => write!(f, "<<< {w}"),
223 Redir::HereDoc { delimiter, strip_tabs } => {
224 if *strip_tabs { write!(f, "<<-{delimiter}") } else { write!(f, "<<{delimiter}") }
225 }
226 Redir::DupFd { src, dst } => {
227 if *src != 1 { write!(f, "{src}")?; }
228 write!(f, ">&{dst}")
229 }
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use crate::cst::parse;
237
238 #[test]
239 fn display_simple() {
240 let s = parse("echo hello").unwrap();
241 assert_eq!(s.to_string(), "echo hello");
242 }
243
244 #[test]
245 fn display_pipeline() {
246 let s = parse("grep foo | head -5").unwrap();
247 assert_eq!(s.to_string(), "grep foo | head -5");
248 }
249
250 #[test]
251 fn display_sequence() {
252 let s = parse("ls && echo done").unwrap();
253 assert_eq!(s.to_string(), "ls && echo done");
254 }
255
256 #[test]
257 fn display_single_quoted() {
258 let s = parse("echo 'hello world'").unwrap();
259 assert_eq!(s.to_string(), "echo 'hello world'");
260 }
261
262 #[test]
263 fn display_double_quoted() {
264 let s = parse("echo \"hello world\"").unwrap();
265 assert_eq!(s.to_string(), "echo \"hello world\"");
266 }
267
268 #[test]
269 fn display_redirect() {
270 let s = parse("echo hello > /dev/null").unwrap();
271 assert_eq!(s.to_string(), "echo hello > /dev/null");
272 }
273
274 #[test]
275 fn display_fd_redirect() {
276 let s = parse("echo hello 2>&1").unwrap();
277 assert_eq!(s.to_string(), "echo hello 2>&1");
278 }
279
280 #[test]
281 fn display_cmd_sub() {
282 let s = parse("echo $(ls)").unwrap();
283 assert_eq!(s.to_string(), "echo $(ls)");
284 }
285
286 #[test]
287 fn display_for() {
288 let s = parse("for x in 1 2 3; do echo $x; done").unwrap();
289 assert_eq!(s.to_string(), "for x in 1 2 3; do echo $x; done");
290 }
291
292 #[test]
293 fn display_if() {
294 let s = parse("if true; then echo yes; else echo no; fi").unwrap();
295 assert_eq!(s.to_string(), "if true; then echo yes; else echo no; fi");
296 }
297
298 #[test]
299 fn display_for_with_redirect() {
300 let s = parse("for x in 1 2; do echo $x; done 2>/dev/null").unwrap();
301 assert_eq!(s.to_string(), "for x in 1 2; do echo $x; done 2> /dev/null");
302 }
303
304 #[test]
305 fn display_if_with_redirect() {
306 let s = parse("if true; then echo yes; fi 2>&1").unwrap();
307 assert_eq!(s.to_string(), "if true; then echo yes; fi 2>&1");
308 }
309
310 #[test]
311 fn display_env_prefix() {
312 let s = parse("FOO=bar ls").unwrap();
313 assert_eq!(s.to_string(), "FOO=bar ls");
314 }
315
316 #[test]
317 fn display_subshell() {
318 let s = parse("(echo hello)").unwrap();
319 assert_eq!(s.to_string(), "(echo hello)");
320 }
321
322 #[test]
323 fn display_negation() {
324 let s = parse("! echo hello").unwrap();
325 assert_eq!(s.to_string(), "! echo hello");
326 }
327}