yash_syntax/syntax/
impl_display.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use itertools::Itertools as _;
19use std::fmt;
20use std::fmt::Write as _;
21
22impl fmt::Display for SpecialParam {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        self.as_char().fmt(f)
25    }
26}
27
28impl fmt::Display for Param {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        self.id.fmt(f)
31    }
32}
33
34impl fmt::Display for SwitchType {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        use SwitchType::*;
37        let c = match self {
38            Alter => '+',
39            Default => '-',
40            Assign => '=',
41            Error => '?',
42        };
43        f.write_char(c)
44    }
45}
46
47impl fmt::Display for SwitchCondition {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        use SwitchCondition::*;
50        match self {
51            Unset => Ok(()),
52            UnsetOrEmpty => f.write_char(':'),
53        }
54    }
55}
56
57impl fmt::Display for Switch {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}{}{}", self.condition, self.r#type, self.word)
60    }
61}
62
63impl fmt::Display for TrimSide {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        use TrimSide::*;
66        let c = match self {
67            Prefix => '#',
68            Suffix => '%',
69        };
70        f.write_char(c)
71    }
72}
73
74impl fmt::Display for Trim {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        self.side.fmt(f)?;
77        match self.length {
78            TrimLength::Shortest => (),
79            TrimLength::Longest => self.side.fmt(f)?,
80        }
81        self.pattern.fmt(f)
82    }
83}
84
85impl fmt::Display for BracedParam {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        use Modifier::*;
88        match self.modifier {
89            None => write!(f, "${{{}}}", self.param),
90            Length => write!(f, "${{#{}}}", self.param),
91            Switch(ref switch) => write!(f, "${{{}{}}}", self.param, switch),
92            Trim(ref trim) => write!(f, "${{{}{}}}", self.param, trim),
93        }
94    }
95}
96
97impl fmt::Display for BackquoteUnit {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            BackquoteUnit::Literal(c) => write!(f, "{c}"),
101            BackquoteUnit::Backslashed(c) => write!(f, "\\{c}"),
102        }
103    }
104}
105
106impl fmt::Display for TextUnit {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Literal(c) => write!(f, "{c}"),
110            Backslashed(c) => write!(f, "\\{c}"),
111            RawParam { param, .. } => write!(f, "${param}"),
112            BracedParam(param) => param.fmt(f),
113            CommandSubst { content, .. } => write!(f, "$({content})"),
114            Backquote { content, .. } => {
115                f.write_char('`')?;
116                content.iter().try_for_each(|unit| unit.fmt(f))?;
117                f.write_char('`')
118            }
119            Arith { content, .. } => write!(f, "$(({content}))"),
120        }
121    }
122}
123
124impl fmt::Display for Text {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        self.0.iter().try_for_each(|unit| unit.fmt(f))
127    }
128}
129
130impl fmt::Display for EscapeUnit {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            Self::Literal(c) => c.fmt(f),
134            Self::DoubleQuote => f.write_str("\\\""),
135            Self::SingleQuote => f.write_str("\\'"),
136            Self::Backslash => f.write_str("\\\\"),
137            Self::Question => f.write_str("\\?"),
138            Self::Alert => f.write_str("\\a"),
139            Self::Backspace => f.write_str("\\b"),
140            Self::Escape => f.write_str("\\e"),
141            Self::FormFeed => f.write_str("\\f"),
142            Self::Newline => f.write_str("\\n"),
143            Self::CarriageReturn => f.write_str("\\r"),
144            Self::Tab => f.write_str("\\t"),
145            Self::VerticalTab => f.write_str("\\v"),
146            Self::Control(b) => write!(f, "\\c{}", (*b ^ 0x40) as char),
147            Self::Octal(b) => write!(f, "\\{b:03o}"),
148            Self::Hex(b) => write!(f, "\\x{b:02X}"),
149            Self::Unicode(c) if *c <= '\u{FFFF}' => write!(f, "\\u{:04x}", *c as u32),
150            Self::Unicode(c) => write!(f, "\\U{:08X}", *c as u32),
151        }
152    }
153}
154
155impl fmt::Display for EscapedString {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        self.0.iter().try_for_each(|unit| unit.fmt(f))
158    }
159}
160
161impl fmt::Display for WordUnit {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            Unquoted(dq) => dq.fmt(f),
165            SingleQuote(s) => write!(f, "'{s}'"),
166            DoubleQuote(content) => write!(f, "\"{content}\""),
167            DollarSingleQuote(content) => write!(f, "$'{content}'"),
168            Tilde(s) => write!(f, "~{s}"),
169        }
170    }
171}
172
173impl fmt::Display for Word {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        self.units.iter().try_for_each(|unit| write!(f, "{unit}"))
176    }
177}
178
179impl fmt::Display for Value {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            Scalar(word) => word.fmt(f),
183            Array(words) => write!(f, "({})", words.iter().format(" ")),
184        }
185    }
186}
187
188impl fmt::Display for Assign {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(f, "{}={}", &self.name, &self.value)
191    }
192}
193
194impl fmt::Display for Fd {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        self.0.fmt(f)
197    }
198}
199
200impl fmt::Display for RedirOp {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        Operator::from(*self).fmt(f)
203    }
204}
205
206impl fmt::Display for HereDoc {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        f.write_str(if self.remove_tabs { "<<-" } else { "<<" })?;
209
210        // This space is to disambiguate `<< --` and `<<- -`
211        if let Some(Unquoted(Literal('-'))) = self.delimiter.units.first() {
212            f.write_char(' ')?;
213        }
214
215        write!(f, "{}", self.delimiter)
216    }
217}
218
219impl fmt::Display for RedirBody {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self {
222            RedirBody::Normal { operator, operand } => write!(f, "{operator}{operand}"),
223            RedirBody::HereDoc(h) => write!(f, "{h}"),
224        }
225    }
226}
227
228impl fmt::Display for Redir {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        if let Some(fd) = self.fd {
231            write!(f, "{fd}")?;
232        }
233        write!(f, "{}", self.body)
234    }
235}
236
237impl fmt::Display for SimpleCommand {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        let i1 = self.assigns.iter().map(|x| x as &dyn fmt::Display);
240        let i2 = self.words.iter().map(|x| x as &dyn fmt::Display);
241        let i3 = self.redirs.iter().map(|x| x as &dyn fmt::Display);
242
243        if !self.assigns.is_empty() || !self.first_word_is_keyword() {
244            write!(f, "{}", i1.chain(i2).chain(i3).format(" "))
245        } else {
246            // If the simple command starts with an assignment or redirection,
247            // the first word may be a keyword which is treated as a plain word.
248            // In this case, we need to avoid the word being interpreted as a
249            // keyword by printing the assignment or redirection first.
250            write!(f, "{}", i3.chain(i2).format(" "))
251        }
252    }
253}
254
255impl fmt::Display for ElifThen {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        write!(f, "elif {:#} then ", self.condition)?;
258        if f.alternate() {
259            write!(f, "{:#}", self.body)
260        } else {
261            write!(f, "{}", self.body)
262        }
263    }
264}
265
266impl fmt::Display for CaseContinuation {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        Operator::from(*self).fmt(f)
269    }
270}
271
272impl fmt::Display for CaseItem {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        write!(
275            f,
276            "({}) {}{}",
277            self.patterns.iter().format(" | "),
278            self.body,
279            self.continuation,
280        )
281    }
282}
283
284impl fmt::Display for CompoundCommand {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        use CompoundCommand::*;
287        match self {
288            Grouping(list) => write!(f, "{{ {list:#} }}"),
289            Subshell { body, .. } => write!(f, "({body})"),
290            For { name, values, body } => {
291                write!(f, "for {name}")?;
292                if let Some(values) = values {
293                    f.write_str(" in")?;
294                    for value in values {
295                        write!(f, " {value}")?;
296                    }
297                    f.write_char(';')?;
298                }
299                write!(f, " do {body:#} done")
300            }
301            While { condition, body } => write!(f, "while {condition:#} do {body:#} done"),
302            Until { condition, body } => write!(f, "until {condition:#} do {body:#} done"),
303            If {
304                condition,
305                body,
306                elifs,
307                r#else,
308            } => {
309                write!(f, "if {condition:#} then {body:#} ")?;
310                for elif in elifs {
311                    write!(f, "{elif:#} ")?;
312                }
313                if let Some(r#else) = r#else {
314                    write!(f, "else {else:#} ")?;
315                }
316                f.write_str("fi")
317            }
318            Case { subject, items } => {
319                write!(f, "case {subject} in ")?;
320                for item in items {
321                    write!(f, "{item} ")?;
322                }
323                f.write_str("esac")
324            }
325        }
326    }
327}
328
329impl fmt::Display for FullCompoundCommand {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        let FullCompoundCommand { command, redirs } = self;
332        write!(f, "{command}")?;
333        redirs.iter().try_for_each(|redir| write!(f, " {redir}"))
334    }
335}
336
337impl fmt::Display for FunctionDefinition {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        if self.has_keyword {
340            f.write_str("function ")?;
341        }
342        write!(f, "{}() {}", self.name, self.body)
343    }
344}
345
346impl fmt::Display for Command {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
348        match self {
349            Command::Simple(c) => c.fmt(f),
350            Command::Compound(c) => c.fmt(f),
351            Command::Function(c) => c.fmt(f),
352        }
353    }
354}
355
356impl fmt::Display for Pipeline {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
358        if self.negation {
359            write!(f, "! ")?;
360        }
361        write!(f, "{}", self.commands.iter().format(" | "))
362    }
363}
364
365impl fmt::Display for AndOr {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        match self {
368            AndOr::AndThen => write!(f, "&&"),
369            AndOr::OrElse => write!(f, "||"),
370        }
371    }
372}
373
374impl fmt::Display for AndOrList {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        write!(f, "{}", self.first)?;
377        self.rest
378            .iter()
379            .try_for_each(|(c, p)| write!(f, " {c} {p}"))
380    }
381}
382
383/// Allows conversion from Item to String.
384///
385/// By default, the `;` terminator is omitted from the formatted string.
386/// When the alternate flag is specified as in `{:#}`, the result is always
387/// terminated by either `;` or `&`.
388impl fmt::Display for Item {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        write!(f, "{}", self.and_or)?;
391        if self.async_flag.is_some() {
392            write!(f, "&")
393        } else if f.alternate() {
394            write!(f, ";")
395        } else {
396            Ok(())
397        }
398    }
399}
400
401/// Allows conversion from List to String.
402///
403/// By default, the last `;` terminator is omitted from the formatted string.
404/// When the alternate flag is specified as in `{:#}`, the result is always
405/// terminated by either `;` or `&`.
406impl fmt::Display for List {
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        if let Some((last, others)) = self.0.split_last() {
409            for item in others {
410                write!(f, "{item:#} ")?;
411            }
412            if f.alternate() {
413                write!(f, "{last:#}")
414            } else {
415                write!(f, "{last}")
416            }
417        } else {
418            Ok(())
419        }
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn switch_display() {
429        let switch = Switch {
430            r#type: SwitchType::Alter,
431            condition: SwitchCondition::Unset,
432            word: "".parse().unwrap(),
433        };
434        assert_eq!(switch.to_string(), "+");
435
436        let switch = Switch {
437            r#type: SwitchType::Default,
438            condition: SwitchCondition::UnsetOrEmpty,
439            word: "foo".parse().unwrap(),
440        };
441        assert_eq!(switch.to_string(), ":-foo");
442
443        let switch = Switch {
444            r#type: SwitchType::Assign,
445            condition: SwitchCondition::UnsetOrEmpty,
446            word: "bar baz".parse().unwrap(),
447        };
448        assert_eq!(switch.to_string(), ":=bar baz");
449
450        let switch = Switch {
451            r#type: SwitchType::Error,
452            condition: SwitchCondition::Unset,
453            word: "?error".parse().unwrap(),
454        };
455        assert_eq!(switch.to_string(), "??error");
456    }
457
458    #[test]
459    fn trim_display() {
460        let trim = Trim {
461            side: TrimSide::Prefix,
462            length: TrimLength::Shortest,
463            pattern: "foo".parse().unwrap(),
464        };
465        assert_eq!(trim.to_string(), "#foo");
466
467        let trim = Trim {
468            side: TrimSide::Prefix,
469            length: TrimLength::Longest,
470            pattern: "".parse().unwrap(),
471        };
472        assert_eq!(trim.to_string(), "##");
473
474        let trim = Trim {
475            side: TrimSide::Suffix,
476            length: TrimLength::Shortest,
477            pattern: "bar".parse().unwrap(),
478        };
479        assert_eq!(trim.to_string(), "%bar");
480
481        let trim = Trim {
482            side: TrimSide::Suffix,
483            length: TrimLength::Longest,
484            pattern: "*".parse().unwrap(),
485        };
486        assert_eq!(trim.to_string(), "%%*");
487    }
488
489    #[test]
490    fn braced_param_display() {
491        let param = BracedParam {
492            param: Param::variable("foo"),
493            modifier: Modifier::None,
494            location: Location::dummy(""),
495        };
496        assert_eq!(param.to_string(), "${foo}");
497
498        let param = BracedParam {
499            modifier: Modifier::Length,
500            ..param
501        };
502        assert_eq!(param.to_string(), "${#foo}");
503
504        let switch = Switch {
505            r#type: SwitchType::Assign,
506            condition: SwitchCondition::UnsetOrEmpty,
507            word: "bar baz".parse().unwrap(),
508        };
509        let param = BracedParam {
510            modifier: Modifier::Switch(switch),
511            ..param
512        };
513        assert_eq!(param.to_string(), "${foo:=bar baz}");
514
515        let trim = Trim {
516            side: TrimSide::Suffix,
517            length: TrimLength::Shortest,
518            pattern: "baz' 'bar".parse().unwrap(),
519        };
520        let param = BracedParam {
521            modifier: Modifier::Trim(trim),
522            ..param
523        };
524        assert_eq!(param.to_string(), "${foo%baz' 'bar}");
525    }
526
527    #[test]
528    fn backquote_unit_display() {
529        let literal = BackquoteUnit::Literal('A');
530        assert_eq!(literal.to_string(), "A");
531        let backslashed = BackquoteUnit::Backslashed('X');
532        assert_eq!(backslashed.to_string(), r"\X");
533    }
534
535    #[test]
536    fn text_unit_display() {
537        let literal = Literal('A');
538        assert_eq!(literal.to_string(), "A");
539        let backslashed = Backslashed('X');
540        assert_eq!(backslashed.to_string(), r"\X");
541
542        let raw_param = RawParam {
543            param: Param::variable("PARAM"),
544            location: Location::dummy(""),
545        };
546        assert_eq!(raw_param.to_string(), "$PARAM");
547
548        let command_subst = CommandSubst {
549            content: r"foo\bar".into(),
550            location: Location::dummy(""),
551        };
552        assert_eq!(command_subst.to_string(), r"$(foo\bar)");
553
554        let backquote = Backquote {
555            content: vec![
556                BackquoteUnit::Literal('a'),
557                BackquoteUnit::Backslashed('b'),
558                BackquoteUnit::Backslashed('c'),
559                BackquoteUnit::Literal('d'),
560            ],
561            location: Location::dummy(""),
562        };
563        assert_eq!(backquote.to_string(), r"`a\b\cd`");
564
565        let arith = Arith {
566            content: Text(vec![literal, backslashed, command_subst, backquote]),
567            location: Location::dummy(""),
568        };
569        assert_eq!(arith.to_string(), r"$((A\X$(foo\bar)`a\b\cd`))");
570    }
571
572    #[test]
573    fn escape_unit_display() {
574        use EscapeUnit::*;
575
576        assert_eq!(Literal('A').to_string(), "A");
577        assert_eq!(DoubleQuote.to_string(), r#"\""#);
578        assert_eq!(SingleQuote.to_string(), r"\'");
579        assert_eq!(Backslash.to_string(), r"\\");
580        assert_eq!(Question.to_string(), r"\?");
581        assert_eq!(Alert.to_string(), r"\a");
582        assert_eq!(Backspace.to_string(), r"\b");
583        assert_eq!(Escape.to_string(), r"\e");
584        assert_eq!(FormFeed.to_string(), r"\f");
585        assert_eq!(Newline.to_string(), r"\n");
586        assert_eq!(CarriageReturn.to_string(), r"\r");
587        assert_eq!(Tab.to_string(), r"\t");
588        assert_eq!(VerticalTab.to_string(), r"\v");
589        assert_eq!(Control(b'\x01').to_string(), r"\cA");
590        assert_eq!(Control(b'\x7F').to_string(), r"\c?");
591        assert_eq!(Octal(0o003).to_string(), r"\003");
592        assert_eq!(Octal(0o123).to_string(), r"\123");
593        assert_eq!(Hex(0x05).to_string(), r"\x05");
594        assert_eq!(Hex(0xAB).to_string(), r"\xAB");
595        assert_eq!(Unicode('A').to_string(), r"\u0041");
596        assert_eq!(Unicode('😊').to_string(), r"\U0001F60A");
597    }
598
599    #[test]
600    fn word_unit_display() {
601        let unquoted = Unquoted(Literal('A'));
602        assert_eq!(unquoted.to_string(), "A");
603        let unquoted = Unquoted(Backslashed('B'));
604        assert_eq!(unquoted.to_string(), "\\B");
605
606        let single_quote = SingleQuote("".to_string());
607        assert_eq!(single_quote.to_string(), "''");
608        let single_quote = SingleQuote(r#"a"b"c\"#.to_string());
609        assert_eq!(single_quote.to_string(), r#"'a"b"c\'"#);
610
611        let double_quote = DoubleQuote(Text(vec![]));
612        assert_eq!(double_quote.to_string(), "\"\"");
613        let double_quote = DoubleQuote(Text(vec![Literal('A'), Backslashed('B')]));
614        assert_eq!(double_quote.to_string(), "\"A\\B\"");
615
616        let dollar_single_quote = DollarSingleQuote(EscapedString(vec![]));
617        assert_eq!(dollar_single_quote.to_string(), "$''");
618        let dollar_single_quote = DollarSingleQuote(EscapedString(vec![
619            EscapeUnit::Literal('A'),
620            EscapeUnit::Backslash,
621        ]));
622        assert_eq!(dollar_single_quote.to_string(), r"$'A\\'");
623
624        let tilde = Tilde("".to_string());
625        assert_eq!(tilde.to_string(), "~");
626        let tilde = Tilde("foo".to_string());
627        assert_eq!(tilde.to_string(), "~foo");
628    }
629
630    #[test]
631    fn scalar_display() {
632        let s = Scalar(Word::from_str("my scalar value").unwrap());
633        assert_eq!(s.to_string(), "my scalar value");
634    }
635
636    #[test]
637    fn array_display_empty() {
638        let a = Array(vec![]);
639        assert_eq!(a.to_string(), "()");
640    }
641
642    #[test]
643    fn array_display_one() {
644        let a = Array(vec![Word::from_str("one").unwrap()]);
645        assert_eq!(a.to_string(), "(one)");
646    }
647
648    #[test]
649    fn array_display_many() {
650        let a = Array(vec![
651            Word::from_str("let").unwrap(),
652            Word::from_str("me").unwrap(),
653            Word::from_str("see").unwrap(),
654        ]);
655        assert_eq!(a.to_string(), "(let me see)");
656    }
657
658    #[test]
659    fn assign_display() {
660        let mut a = Assign::from_str("foo=bar").unwrap();
661        assert_eq!(a.to_string(), "foo=bar");
662
663        a.value = Array(vec![]);
664        assert_eq!(a.to_string(), "foo=()");
665    }
666
667    #[test]
668    fn here_doc_display() {
669        let heredoc = HereDoc {
670            delimiter: Word::from_str("END").unwrap(),
671            remove_tabs: true,
672            content: Text::from_str("here").unwrap().into(),
673        };
674        assert_eq!(heredoc.to_string(), "<<-END");
675
676        let heredoc = HereDoc {
677            delimiter: Word::from_str("XXX").unwrap(),
678            remove_tabs: false,
679            content: Text::from_str("there").unwrap().into(),
680        };
681        assert_eq!(heredoc.to_string(), "<<XXX");
682    }
683
684    #[test]
685    fn here_doc_display_disambiguation() {
686        let heredoc = HereDoc {
687            delimiter: Word::from_str("--").unwrap(),
688            remove_tabs: false,
689            content: Text::from_str("here").unwrap().into(),
690        };
691        assert_eq!(heredoc.to_string(), "<< --");
692
693        let heredoc = HereDoc {
694            delimiter: Word::from_str("-").unwrap(),
695            remove_tabs: true,
696            content: Text::from_str("here").unwrap().into(),
697        };
698        assert_eq!(heredoc.to_string(), "<<- -");
699    }
700
701    #[test]
702    fn redir_display() {
703        let heredoc = HereDoc {
704            delimiter: Word::from_str("END").unwrap(),
705            remove_tabs: false,
706            content: Text::from_str("here").unwrap().into(),
707        };
708
709        let redir = Redir {
710            fd: None,
711            body: heredoc.into(),
712        };
713        assert_eq!(redir.to_string(), "<<END");
714        let redir = Redir {
715            fd: Some(Fd(0)),
716            ..redir
717        };
718        assert_eq!(redir.to_string(), "0<<END");
719        let redir = Redir {
720            fd: Some(Fd(9)),
721            ..redir
722        };
723        assert_eq!(redir.to_string(), "9<<END");
724    }
725
726    #[test]
727    fn simple_command_display() {
728        let mut command = SimpleCommand {
729            assigns: vec![],
730            words: vec![],
731            redirs: vec![].into(),
732        };
733        assert_eq!(command.to_string(), "");
734
735        command
736            .assigns
737            .push(Assign::from_str("name=value").unwrap());
738        assert_eq!(command.to_string(), "name=value");
739
740        command
741            .assigns
742            .push(Assign::from_str("hello=world").unwrap());
743        assert_eq!(command.to_string(), "name=value hello=world");
744
745        command.words.push(Word::from_str("echo").unwrap());
746        assert_eq!(command.to_string(), "name=value hello=world echo");
747
748        command.words.push(Word::from_str("foo").unwrap());
749        assert_eq!(command.to_string(), "name=value hello=world echo foo");
750
751        Rc::make_mut(&mut command.redirs).push(Redir {
752            fd: None,
753            body: RedirBody::from(HereDoc {
754                delimiter: Word::from_str("END").unwrap(),
755                remove_tabs: false,
756                content: Text::from_str("").unwrap().into(),
757            }),
758        });
759        assert_eq!(command.to_string(), "name=value hello=world echo foo <<END");
760
761        command.assigns.clear();
762        assert_eq!(command.to_string(), "echo foo <<END");
763
764        command.words.clear();
765        assert_eq!(command.to_string(), "<<END");
766
767        Rc::make_mut(&mut command.redirs).push(Redir {
768            fd: Some(Fd(1)),
769            body: RedirBody::from(HereDoc {
770                delimiter: Word::from_str("here").unwrap(),
771                remove_tabs: true,
772                content: Text::from_str("ignored").unwrap().into(),
773            }),
774        });
775        assert_eq!(command.to_string(), "<<END 1<<-here");
776
777        command.assigns.push(Assign::from_str("foo=bar").unwrap());
778        assert_eq!(command.to_string(), "foo=bar <<END 1<<-here");
779    }
780
781    #[test]
782    fn simple_command_display_with_keyword() {
783        let command = SimpleCommand {
784            assigns: vec![],
785            words: vec!["if".parse().unwrap()],
786            redirs: vec!["<foo".parse().unwrap()].into(),
787        };
788        assert_eq!(command.to_string(), "<foo if");
789    }
790
791    #[test]
792    fn elif_then_display() {
793        let condition: List = "c 1& c 2".parse().unwrap();
794        let body = "b 1& b 2".parse().unwrap();
795        let elif = ElifThen { condition, body };
796        assert_eq!(format!("{elif}"), "elif c 1& c 2; then b 1& b 2");
797        assert_eq!(format!("{elif:#}"), "elif c 1& c 2; then b 1& b 2;");
798
799        let condition: List = "c&".parse().unwrap();
800        let body = "b&".parse().unwrap();
801        let elif = ElifThen { condition, body };
802        assert_eq!(format!("{elif}"), "elif c& then b&");
803        assert_eq!(format!("{elif:#}"), "elif c& then b&");
804    }
805
806    #[test]
807    fn case_item_display() {
808        let item = CaseItem {
809            patterns: vec!["foo".parse().unwrap()],
810            body: "".parse::<List>().unwrap(),
811            continuation: CaseContinuation::Break,
812        };
813        assert_eq!(item.to_string(), "(foo) ;;");
814
815        let item = CaseItem {
816            patterns: vec!["bar".parse().unwrap()],
817            body: "echo ok".parse::<List>().unwrap(),
818            continuation: CaseContinuation::Break,
819        };
820        assert_eq!(item.to_string(), "(bar) echo ok;;");
821
822        let item = CaseItem {
823            patterns: ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect(),
824            body: "foo; bar&".parse::<List>().unwrap(),
825            continuation: CaseContinuation::Break,
826        };
827        assert_eq!(item.to_string(), "(a | b | c) foo; bar&;;");
828
829        let item = CaseItem {
830            patterns: vec!["foo".parse().unwrap()],
831            body: "bar".parse::<List>().unwrap(),
832            continuation: CaseContinuation::FallThrough,
833        };
834        assert_eq!(item.to_string(), "(foo) bar;&");
835    }
836
837    #[test]
838    fn grouping_display() {
839        let list = "foo".parse::<List>().unwrap();
840        let grouping = CompoundCommand::Grouping(list);
841        assert_eq!(grouping.to_string(), "{ foo; }");
842    }
843
844    #[test]
845    fn for_display_without_values() {
846        let name = Word::from_str("foo").unwrap();
847        let values = None;
848        let body = "echo ok".parse::<List>().unwrap();
849        let r#for = CompoundCommand::For { name, values, body };
850        assert_eq!(r#for.to_string(), "for foo do echo ok; done");
851    }
852
853    #[test]
854    fn for_display_with_empty_values() {
855        let name = Word::from_str("foo").unwrap();
856        let values = Some(vec![]);
857        let body = "echo ok".parse::<List>().unwrap();
858        let r#for = CompoundCommand::For { name, values, body };
859        assert_eq!(r#for.to_string(), "for foo in; do echo ok; done");
860    }
861
862    #[test]
863    fn for_display_with_some_values() {
864        let name = Word::from_str("V").unwrap();
865        let values = Some(vec![
866            Word::from_str("a").unwrap(),
867            Word::from_str("b").unwrap(),
868        ]);
869        let body = "one; two&".parse::<List>().unwrap();
870        let r#for = CompoundCommand::For { name, values, body };
871        assert_eq!(r#for.to_string(), "for V in a b; do one; two& done");
872    }
873
874    #[test]
875    fn while_display() {
876        let condition = "true& false".parse::<List>().unwrap();
877        let body = "echo ok".parse::<List>().unwrap();
878        let r#while = CompoundCommand::While { condition, body };
879        assert_eq!(r#while.to_string(), "while true& false; do echo ok; done");
880    }
881
882    #[test]
883    fn until_display() {
884        let condition = "true& false".parse::<List>().unwrap();
885        let body = "echo ok".parse::<List>().unwrap();
886        let until = CompoundCommand::Until { condition, body };
887        assert_eq!(until.to_string(), "until true& false; do echo ok; done");
888    }
889
890    #[test]
891    fn if_display() {
892        let r#if: CompoundCommand = CompoundCommand::If {
893            condition: "c 1; c 2&".parse().unwrap(),
894            body: "b 1; b 2&".parse().unwrap(),
895            elifs: vec![],
896            r#else: None,
897        };
898        assert_eq!(r#if.to_string(), "if c 1; c 2& then b 1; b 2& fi");
899
900        let r#if: CompoundCommand = CompoundCommand::If {
901            condition: "c 1& c 2;".parse().unwrap(),
902            body: "b 1& b 2;".parse().unwrap(),
903            elifs: vec![ElifThen {
904                condition: "c 3&".parse().unwrap(),
905                body: "b 3&".parse().unwrap(),
906            }],
907            r#else: Some("b 4".parse().unwrap()),
908        };
909        assert_eq!(
910            r#if.to_string(),
911            "if c 1& c 2; then b 1& b 2; elif c 3& then b 3& else b 4; fi"
912        );
913
914        let r#if: CompoundCommand = CompoundCommand::If {
915            condition: "true".parse().unwrap(),
916            body: ":".parse().unwrap(),
917            elifs: vec![
918                ElifThen {
919                    condition: "false".parse().unwrap(),
920                    body: "a".parse().unwrap(),
921                },
922                ElifThen {
923                    condition: "echo&".parse().unwrap(),
924                    body: "b&".parse().unwrap(),
925                },
926            ],
927            r#else: None,
928        };
929        assert_eq!(
930            r#if.to_string(),
931            "if true; then :; elif false; then a; elif echo& then b& fi"
932        );
933    }
934
935    #[test]
936    fn case_display() {
937        let subject = "foo".parse().unwrap();
938        let items = Vec::<CaseItem>::new();
939        let case = CompoundCommand::Case { subject, items };
940        assert_eq!(case.to_string(), "case foo in esac");
941
942        let subject = "bar".parse().unwrap();
943        let items = vec!["foo)".parse::<CaseItem>().unwrap()];
944        let case = CompoundCommand::Case { subject, items };
945        assert_eq!(case.to_string(), "case bar in (foo) ;; esac");
946
947        let subject = "baz".parse().unwrap();
948        let items = vec![
949            "1)".parse::<CaseItem>().unwrap(),
950            "(a|b|c) :&".parse().unwrap(),
951        ];
952        let case = CompoundCommand::Case { subject, items };
953        assert_eq!(case.to_string(), "case baz in (1) ;; (a | b | c) :&;; esac");
954    }
955
956    #[test]
957    fn function_definition_display() {
958        let body = FullCompoundCommand {
959            command: "( bar )".parse::<CompoundCommand>().unwrap(),
960            redirs: vec![],
961        };
962        let fd = FunctionDefinition {
963            has_keyword: false,
964            name: Word::from_str("foo").unwrap(),
965            body: Rc::new(body),
966        };
967        assert_eq!(fd.to_string(), "foo() (bar)");
968    }
969
970    #[test]
971    fn pipeline_display() {
972        let mut p = Pipeline {
973            commands: vec![Rc::new("first".parse::<Command>().unwrap())],
974            negation: false,
975        };
976        assert_eq!(p.to_string(), "first");
977
978        p.negation = true;
979        assert_eq!(p.to_string(), "! first");
980
981        p.commands.push(Rc::new("second".parse().unwrap()));
982        assert_eq!(p.to_string(), "! first | second");
983
984        p.commands.push(Rc::new("third".parse().unwrap()));
985        p.negation = false;
986        assert_eq!(p.to_string(), "first | second | third");
987    }
988
989    #[test]
990    fn and_or_list_display() {
991        let p = "first".parse::<Pipeline>().unwrap();
992        let mut aol = AndOrList {
993            first: p,
994            rest: vec![],
995        };
996        assert_eq!(aol.to_string(), "first");
997
998        let p = "second".parse().unwrap();
999        aol.rest.push((AndOr::AndThen, p));
1000        assert_eq!(aol.to_string(), "first && second");
1001
1002        let p = "third".parse().unwrap();
1003        aol.rest.push((AndOr::OrElse, p));
1004        assert_eq!(aol.to_string(), "first && second || third");
1005    }
1006
1007    #[test]
1008    fn list_display() {
1009        let and_or = "first".parse::<AndOrList>().unwrap();
1010        let item = Item {
1011            and_or: Rc::new(and_or),
1012            async_flag: None,
1013        };
1014        let mut list = List(vec![item]);
1015        assert_eq!(list.to_string(), "first");
1016
1017        let and_or = "second".parse().unwrap();
1018        let item = Item {
1019            and_or: Rc::new(and_or),
1020            async_flag: Some(Location::dummy("")),
1021        };
1022        list.0.push(item);
1023        assert_eq!(list.to_string(), "first; second&");
1024
1025        let and_or = "third".parse().unwrap();
1026        let item = Item {
1027            and_or: Rc::new(and_or),
1028            async_flag: None,
1029        };
1030        list.0.push(item);
1031        assert_eq!(list.to_string(), "first; second& third");
1032    }
1033
1034    #[test]
1035    fn list_display_alternate() {
1036        let and_or = "first".parse::<AndOrList>().unwrap();
1037        let item = Item {
1038            and_or: Rc::new(and_or),
1039            async_flag: None,
1040        };
1041        let mut list = List(vec![item]);
1042        assert_eq!(format!("{list:#}"), "first;");
1043
1044        let and_or = "second".parse().unwrap();
1045        let item = Item {
1046            and_or: Rc::new(and_or),
1047            async_flag: Some(Location::dummy("")),
1048        };
1049        list.0.push(item);
1050        assert_eq!(format!("{list:#}"), "first; second&");
1051
1052        let and_or = "third".parse().unwrap();
1053        let item = Item {
1054            and_or: Rc::new(and_or),
1055            async_flag: None,
1056        };
1057        list.0.push(item);
1058        assert_eq!(format!("{list:#}"), "first; second& third;");
1059    }
1060}