Skip to main content

safe_chains/cst/
display.rs

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}