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_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::HereDoc { delimiter, strip_tabs } => {
179                if *strip_tabs { write!(f, "<<-{delimiter}") } else { write!(f, "<<{delimiter}") }
180            }
181            Redir::DupFd { src, dst } => {
182                if *src != 1 { write!(f, "{src}")?; }
183                write!(f, ">&{dst}")
184            }
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::cst::parse;
192
193    #[test]
194    fn display_simple() {
195        let s = parse("echo hello").unwrap();
196        assert_eq!(s.to_string(), "echo hello");
197    }
198
199    #[test]
200    fn display_pipeline() {
201        let s = parse("grep foo | head -5").unwrap();
202        assert_eq!(s.to_string(), "grep foo | head -5");
203    }
204
205    #[test]
206    fn display_sequence() {
207        let s = parse("ls && echo done").unwrap();
208        assert_eq!(s.to_string(), "ls && echo done");
209    }
210
211    #[test]
212    fn display_single_quoted() {
213        let s = parse("echo 'hello world'").unwrap();
214        assert_eq!(s.to_string(), "echo 'hello world'");
215    }
216
217    #[test]
218    fn display_double_quoted() {
219        let s = parse("echo \"hello world\"").unwrap();
220        assert_eq!(s.to_string(), "echo \"hello world\"");
221    }
222
223    #[test]
224    fn display_redirect() {
225        let s = parse("echo hello > /dev/null").unwrap();
226        assert_eq!(s.to_string(), "echo hello > /dev/null");
227    }
228
229    #[test]
230    fn display_fd_redirect() {
231        let s = parse("echo hello 2>&1").unwrap();
232        assert_eq!(s.to_string(), "echo hello 2>&1");
233    }
234
235    #[test]
236    fn display_cmd_sub() {
237        let s = parse("echo $(ls)").unwrap();
238        assert_eq!(s.to_string(), "echo $(ls)");
239    }
240
241    #[test]
242    fn display_for() {
243        let s = parse("for x in 1 2 3; do echo $x; done").unwrap();
244        assert_eq!(s.to_string(), "for x in 1 2 3; do echo $x; done");
245    }
246
247    #[test]
248    fn display_if() {
249        let s = parse("if true; then echo yes; else echo no; fi").unwrap();
250        assert_eq!(s.to_string(), "if true; then echo yes; else echo no; fi");
251    }
252
253    #[test]
254    fn display_env_prefix() {
255        let s = parse("FOO=bar ls").unwrap();
256        assert_eq!(s.to_string(), "FOO=bar ls");
257    }
258
259    #[test]
260    fn display_subshell() {
261        let s = parse("(echo hello)").unwrap();
262        assert_eq!(s.to_string(), "(echo hello)");
263    }
264
265    #[test]
266    fn display_negation() {
267        let s = parse("! echo hello").unwrap();
268        assert_eq!(s.to_string(), "! echo hello");
269    }
270}