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::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}