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