1use 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 SwitchAction {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 use SwitchAction::*;
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.action, 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 { name, .. } => write!(f, "~{name}"),
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 RedirOp {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 Operator::from(*self).fmt(f)
197 }
198}
199
200impl fmt::Display for HereDoc {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.write_str(if self.remove_tabs { "<<-" } else { "<<" })?;
203
204 if let Some(Unquoted(Literal('-'))) = self.delimiter.units.first() {
206 f.write_char(' ')?;
207 }
208
209 write!(f, "{}", self.delimiter)
210 }
211}
212
213impl fmt::Display for RedirBody {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 RedirBody::Normal { operator, operand } => write!(f, "{operator}{operand}"),
217 RedirBody::HereDoc(h) => write!(f, "{h}"),
218 }
219 }
220}
221
222impl fmt::Display for Redir {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 if let Some(fd) = self.fd {
225 write!(f, "{fd}")?;
226 }
227 write!(f, "{}", self.body)
228 }
229}
230
231impl fmt::Display for SimpleCommand {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 let i1 = self.assigns.iter().map(|x| x as &dyn fmt::Display);
234 let i2 = self.words.iter().map(|x| &x.0 as &dyn fmt::Display);
235 let i3 = self.redirs.iter().map(|x| x as &dyn fmt::Display);
236
237 if !self.assigns.is_empty() || !self.first_word_is_keyword() {
238 write!(f, "{}", i1.chain(i2).chain(i3).format(" "))
239 } else {
240 write!(f, "{}", i3.chain(i2).format(" "))
245 }
246 }
247}
248
249impl fmt::Display for ElifThen {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 write!(f, "elif {:#} then ", self.condition)?;
252 if f.alternate() {
253 write!(f, "{:#}", self.body)
254 } else {
255 write!(f, "{}", self.body)
256 }
257 }
258}
259
260impl fmt::Display for CaseContinuation {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 Operator::from(*self).fmt(f)
263 }
264}
265
266impl fmt::Display for CaseItem {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 write!(
269 f,
270 "({}) {}{}",
271 self.patterns.iter().format(" | "),
272 self.body,
273 self.continuation,
274 )
275 }
276}
277
278impl fmt::Display for CompoundCommand {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 use CompoundCommand::*;
281 match self {
282 Grouping(list) => write!(f, "{{ {list:#} }}"),
283 Subshell { body, .. } => write!(f, "({body})"),
284 For { name, values, body } => {
285 write!(f, "for {name}")?;
286 if let Some(values) = values {
287 f.write_str(" in")?;
288 for value in values {
289 write!(f, " {value}")?;
290 }
291 f.write_char(';')?;
292 }
293 write!(f, " do {body:#} done")
294 }
295 While { condition, body } => write!(f, "while {condition:#} do {body:#} done"),
296 Until { condition, body } => write!(f, "until {condition:#} do {body:#} done"),
297 If {
298 condition,
299 body,
300 elifs,
301 r#else,
302 } => {
303 write!(f, "if {condition:#} then {body:#} ")?;
304 for elif in elifs {
305 write!(f, "{elif:#} ")?;
306 }
307 if let Some(r#else) = r#else {
308 write!(f, "else {else:#} ")?;
309 }
310 f.write_str("fi")
311 }
312 Case { subject, items } => {
313 write!(f, "case {subject} in ")?;
314 for item in items {
315 write!(f, "{item} ")?;
316 }
317 f.write_str("esac")
318 }
319 }
320 }
321}
322
323impl fmt::Display for FullCompoundCommand {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 let FullCompoundCommand { command, redirs } = self;
326 write!(f, "{command}")?;
327 redirs.iter().try_for_each(|redir| write!(f, " {redir}"))
328 }
329}
330
331impl fmt::Display for FunctionDefinition {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 if self.has_keyword {
334 f.write_str("function ")?;
335 }
336 write!(f, "{}() {}", self.name, self.body)
337 }
338}
339
340impl fmt::Display for Command {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
342 match self {
343 Command::Simple(c) => c.fmt(f),
344 Command::Compound(c) => c.fmt(f),
345 Command::Function(c) => c.fmt(f),
346 }
347 }
348}
349
350impl fmt::Display for Pipeline {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
352 if self.negation {
353 write!(f, "! ")?;
354 }
355 write!(f, "{}", self.commands.iter().format(" | "))
356 }
357}
358
359impl fmt::Display for AndOr {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 match self {
362 AndOr::AndThen => write!(f, "&&"),
363 AndOr::OrElse => write!(f, "||"),
364 }
365 }
366}
367
368impl fmt::Display for AndOrList {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 write!(f, "{}", self.first)?;
371 self.rest
372 .iter()
373 .try_for_each(|(c, p)| write!(f, " {c} {p}"))
374 }
375}
376
377impl fmt::Display for Item {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 write!(f, "{}", self.and_or)?;
385 if self.async_flag.is_some() {
386 write!(f, "&")
387 } else if f.alternate() {
388 write!(f, ";")
389 } else {
390 Ok(())
391 }
392 }
393}
394
395impl fmt::Display for List {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 if let Some((last, others)) = self.0.split_last() {
403 for item in others {
404 write!(f, "{item:#} ")?;
405 }
406 if f.alternate() {
407 write!(f, "{last:#}")
408 } else {
409 write!(f, "{last}")
410 }
411 } else {
412 Ok(())
413 }
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn switch_display() {
423 let switch = Switch {
424 action: SwitchAction::Alter,
425 condition: SwitchCondition::Unset,
426 word: "".parse().unwrap(),
427 };
428 assert_eq!(switch.to_string(), "+");
429
430 let switch = Switch {
431 action: SwitchAction::Default,
432 condition: SwitchCondition::UnsetOrEmpty,
433 word: "foo".parse().unwrap(),
434 };
435 assert_eq!(switch.to_string(), ":-foo");
436
437 let switch = Switch {
438 action: SwitchAction::Assign,
439 condition: SwitchCondition::UnsetOrEmpty,
440 word: "bar baz".parse().unwrap(),
441 };
442 assert_eq!(switch.to_string(), ":=bar baz");
443
444 let switch = Switch {
445 action: SwitchAction::Error,
446 condition: SwitchCondition::Unset,
447 word: "?error".parse().unwrap(),
448 };
449 assert_eq!(switch.to_string(), "??error");
450 }
451
452 #[test]
453 fn trim_display() {
454 let trim = Trim {
455 side: TrimSide::Prefix,
456 length: TrimLength::Shortest,
457 pattern: "foo".parse().unwrap(),
458 };
459 assert_eq!(trim.to_string(), "#foo");
460
461 let trim = Trim {
462 side: TrimSide::Prefix,
463 length: TrimLength::Longest,
464 pattern: "".parse().unwrap(),
465 };
466 assert_eq!(trim.to_string(), "##");
467
468 let trim = Trim {
469 side: TrimSide::Suffix,
470 length: TrimLength::Shortest,
471 pattern: "bar".parse().unwrap(),
472 };
473 assert_eq!(trim.to_string(), "%bar");
474
475 let trim = Trim {
476 side: TrimSide::Suffix,
477 length: TrimLength::Longest,
478 pattern: "*".parse().unwrap(),
479 };
480 assert_eq!(trim.to_string(), "%%*");
481 }
482
483 #[test]
484 fn braced_param_display() {
485 let param = BracedParam {
486 param: Param::variable("foo"),
487 modifier: Modifier::None,
488 location: Location::dummy(""),
489 };
490 assert_eq!(param.to_string(), "${foo}");
491
492 let param = BracedParam {
493 modifier: Modifier::Length,
494 ..param
495 };
496 assert_eq!(param.to_string(), "${#foo}");
497
498 let switch = Switch {
499 action: SwitchAction::Assign,
500 condition: SwitchCondition::UnsetOrEmpty,
501 word: "bar baz".parse().unwrap(),
502 };
503 let param = BracedParam {
504 modifier: Modifier::Switch(switch),
505 ..param
506 };
507 assert_eq!(param.to_string(), "${foo:=bar baz}");
508
509 let trim = Trim {
510 side: TrimSide::Suffix,
511 length: TrimLength::Shortest,
512 pattern: "baz' 'bar".parse().unwrap(),
513 };
514 let param = BracedParam {
515 modifier: Modifier::Trim(trim),
516 ..param
517 };
518 assert_eq!(param.to_string(), "${foo%baz' 'bar}");
519 }
520
521 #[test]
522 fn backquote_unit_display() {
523 let literal = BackquoteUnit::Literal('A');
524 assert_eq!(literal.to_string(), "A");
525 let backslashed = BackquoteUnit::Backslashed('X');
526 assert_eq!(backslashed.to_string(), r"\X");
527 }
528
529 #[test]
530 fn text_unit_display() {
531 let literal = Literal('A');
532 assert_eq!(literal.to_string(), "A");
533 let backslashed = Backslashed('X');
534 assert_eq!(backslashed.to_string(), r"\X");
535
536 let raw_param = RawParam {
537 param: Param::variable("PARAM"),
538 location: Location::dummy(""),
539 };
540 assert_eq!(raw_param.to_string(), "$PARAM");
541
542 let command_subst = CommandSubst {
543 content: r"foo\bar".into(),
544 location: Location::dummy(""),
545 };
546 assert_eq!(command_subst.to_string(), r"$(foo\bar)");
547
548 let backquote = Backquote {
549 content: vec![
550 BackquoteUnit::Literal('a'),
551 BackquoteUnit::Backslashed('b'),
552 BackquoteUnit::Backslashed('c'),
553 BackquoteUnit::Literal('d'),
554 ],
555 location: Location::dummy(""),
556 };
557 assert_eq!(backquote.to_string(), r"`a\b\cd`");
558
559 let arith = Arith {
560 content: Text(vec![literal, backslashed, command_subst, backquote]),
561 location: Location::dummy(""),
562 };
563 assert_eq!(arith.to_string(), r"$((A\X$(foo\bar)`a\b\cd`))");
564 }
565
566 #[test]
567 fn escape_unit_display() {
568 use EscapeUnit::*;
569
570 assert_eq!(Literal('A').to_string(), "A");
571 assert_eq!(DoubleQuote.to_string(), r#"\""#);
572 assert_eq!(SingleQuote.to_string(), r"\'");
573 assert_eq!(Backslash.to_string(), r"\\");
574 assert_eq!(Question.to_string(), r"\?");
575 assert_eq!(Alert.to_string(), r"\a");
576 assert_eq!(Backspace.to_string(), r"\b");
577 assert_eq!(Escape.to_string(), r"\e");
578 assert_eq!(FormFeed.to_string(), r"\f");
579 assert_eq!(Newline.to_string(), r"\n");
580 assert_eq!(CarriageReturn.to_string(), r"\r");
581 assert_eq!(Tab.to_string(), r"\t");
582 assert_eq!(VerticalTab.to_string(), r"\v");
583 assert_eq!(Control(b'\x01').to_string(), r"\cA");
584 assert_eq!(Control(b'\x7F').to_string(), r"\c?");
585 assert_eq!(Octal(0o003).to_string(), r"\003");
586 assert_eq!(Octal(0o123).to_string(), r"\123");
587 assert_eq!(Hex(0x05).to_string(), r"\x05");
588 assert_eq!(Hex(0xAB).to_string(), r"\xAB");
589 assert_eq!(Unicode('A').to_string(), r"\u0041");
590 assert_eq!(Unicode('😊').to_string(), r"\U0001F60A");
591 }
592
593 #[test]
594 fn word_unit_display() {
595 let unquoted = Unquoted(Literal('A'));
596 assert_eq!(unquoted.to_string(), "A");
597 let unquoted = Unquoted(Backslashed('B'));
598 assert_eq!(unquoted.to_string(), "\\B");
599
600 let single_quote = SingleQuote("".to_string());
601 assert_eq!(single_quote.to_string(), "''");
602 let single_quote = SingleQuote(r#"a"b"c\"#.to_string());
603 assert_eq!(single_quote.to_string(), r#"'a"b"c\'"#);
604
605 let double_quote = DoubleQuote(Text(vec![]));
606 assert_eq!(double_quote.to_string(), "\"\"");
607 let double_quote = DoubleQuote(Text(vec![Literal('A'), Backslashed('B')]));
608 assert_eq!(double_quote.to_string(), "\"A\\B\"");
609
610 let dollar_single_quote = DollarSingleQuote(EscapedString(vec![]));
611 assert_eq!(dollar_single_quote.to_string(), "$''");
612 let dollar_single_quote = DollarSingleQuote(EscapedString(vec![
613 EscapeUnit::Literal('A'),
614 EscapeUnit::Backslash,
615 ]));
616 assert_eq!(dollar_single_quote.to_string(), r"$'A\\'");
617
618 let tilde = Tilde {
619 name: "".to_string(),
620 followed_by_slash: false,
621 };
622 assert_eq!(tilde.to_string(), "~");
623 let tilde = Tilde {
624 name: "foo".to_string(),
625 followed_by_slash: true,
626 };
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
746 .words
747 .push((Word::from_str("echo").unwrap(), ExpansionMode::Multiple));
748 assert_eq!(command.to_string(), "name=value hello=world echo");
749
750 command
751 .words
752 .push((Word::from_str("foo").unwrap(), ExpansionMode::Single));
753 assert_eq!(command.to_string(), "name=value hello=world echo foo");
754
755 Rc::make_mut(&mut command.redirs).push(Redir {
756 fd: None,
757 body: RedirBody::from(HereDoc {
758 delimiter: Word::from_str("END").unwrap(),
759 remove_tabs: false,
760 content: Text::from_str("").unwrap().into(),
761 }),
762 });
763 assert_eq!(command.to_string(), "name=value hello=world echo foo <<END");
764
765 command.assigns.clear();
766 assert_eq!(command.to_string(), "echo foo <<END");
767
768 command.words.clear();
769 assert_eq!(command.to_string(), "<<END");
770
771 Rc::make_mut(&mut command.redirs).push(Redir {
772 fd: Some(Fd(1)),
773 body: RedirBody::from(HereDoc {
774 delimiter: Word::from_str("here").unwrap(),
775 remove_tabs: true,
776 content: Text::from_str("ignored").unwrap().into(),
777 }),
778 });
779 assert_eq!(command.to_string(), "<<END 1<<-here");
780
781 command.assigns.push(Assign::from_str("foo=bar").unwrap());
782 assert_eq!(command.to_string(), "foo=bar <<END 1<<-here");
783 }
784
785 #[test]
786 fn simple_command_display_with_keyword() {
787 let command = SimpleCommand {
788 assigns: vec![],
789 words: vec![("if".parse().unwrap(), ExpansionMode::Multiple)],
790 redirs: vec!["<foo".parse().unwrap()].into(),
791 };
792 assert_eq!(command.to_string(), "<foo if");
793 }
794
795 #[test]
796 fn elif_then_display() {
797 let condition: List = "c 1& c 2".parse().unwrap();
798 let body = "b 1& b 2".parse().unwrap();
799 let elif = ElifThen { condition, body };
800 assert_eq!(format!("{elif}"), "elif c 1& c 2; then b 1& b 2");
801 assert_eq!(format!("{elif:#}"), "elif c 1& c 2; then b 1& b 2;");
802
803 let condition: List = "c&".parse().unwrap();
804 let body = "b&".parse().unwrap();
805 let elif = ElifThen { condition, body };
806 assert_eq!(format!("{elif}"), "elif c& then b&");
807 assert_eq!(format!("{elif:#}"), "elif c& then b&");
808 }
809
810 #[test]
811 fn case_item_display() {
812 let item = CaseItem {
813 patterns: vec!["foo".parse().unwrap()],
814 body: "".parse::<List>().unwrap(),
815 continuation: CaseContinuation::Break,
816 };
817 assert_eq!(item.to_string(), "(foo) ;;");
818
819 let item = CaseItem {
820 patterns: vec!["bar".parse().unwrap()],
821 body: "echo ok".parse::<List>().unwrap(),
822 continuation: CaseContinuation::Break,
823 };
824 assert_eq!(item.to_string(), "(bar) echo ok;;");
825
826 let item = CaseItem {
827 patterns: ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect(),
828 body: "foo; bar&".parse::<List>().unwrap(),
829 continuation: CaseContinuation::Break,
830 };
831 assert_eq!(item.to_string(), "(a | b | c) foo; bar&;;");
832
833 let item = CaseItem {
834 patterns: vec!["foo".parse().unwrap()],
835 body: "bar".parse::<List>().unwrap(),
836 continuation: CaseContinuation::FallThrough,
837 };
838 assert_eq!(item.to_string(), "(foo) bar;&");
839 }
840
841 #[test]
842 fn grouping_display() {
843 let list = "foo".parse::<List>().unwrap();
844 let grouping = CompoundCommand::Grouping(list);
845 assert_eq!(grouping.to_string(), "{ foo; }");
846 }
847
848 #[test]
849 fn for_display_without_values() {
850 let name = Word::from_str("foo").unwrap();
851 let values = None;
852 let body = "echo ok".parse::<List>().unwrap();
853 let r#for = CompoundCommand::For { name, values, body };
854 assert_eq!(r#for.to_string(), "for foo do echo ok; done");
855 }
856
857 #[test]
858 fn for_display_with_empty_values() {
859 let name = Word::from_str("foo").unwrap();
860 let values = Some(vec![]);
861 let body = "echo ok".parse::<List>().unwrap();
862 let r#for = CompoundCommand::For { name, values, body };
863 assert_eq!(r#for.to_string(), "for foo in; do echo ok; done");
864 }
865
866 #[test]
867 fn for_display_with_some_values() {
868 let name = Word::from_str("V").unwrap();
869 let values = Some(vec![
870 Word::from_str("a").unwrap(),
871 Word::from_str("b").unwrap(),
872 ]);
873 let body = "one; two&".parse::<List>().unwrap();
874 let r#for = CompoundCommand::For { name, values, body };
875 assert_eq!(r#for.to_string(), "for V in a b; do one; two& done");
876 }
877
878 #[test]
879 fn while_display() {
880 let condition = "true& false".parse::<List>().unwrap();
881 let body = "echo ok".parse::<List>().unwrap();
882 let r#while = CompoundCommand::While { condition, body };
883 assert_eq!(r#while.to_string(), "while true& false; do echo ok; done");
884 }
885
886 #[test]
887 fn until_display() {
888 let condition = "true& false".parse::<List>().unwrap();
889 let body = "echo ok".parse::<List>().unwrap();
890 let until = CompoundCommand::Until { condition, body };
891 assert_eq!(until.to_string(), "until true& false; do echo ok; done");
892 }
893
894 #[test]
895 fn if_display() {
896 let r#if: CompoundCommand = CompoundCommand::If {
897 condition: "c 1; c 2&".parse().unwrap(),
898 body: "b 1; b 2&".parse().unwrap(),
899 elifs: vec![],
900 r#else: None,
901 };
902 assert_eq!(r#if.to_string(), "if c 1; c 2& then b 1; b 2& fi");
903
904 let r#if: CompoundCommand = CompoundCommand::If {
905 condition: "c 1& c 2;".parse().unwrap(),
906 body: "b 1& b 2;".parse().unwrap(),
907 elifs: vec![ElifThen {
908 condition: "c 3&".parse().unwrap(),
909 body: "b 3&".parse().unwrap(),
910 }],
911 r#else: Some("b 4".parse().unwrap()),
912 };
913 assert_eq!(
914 r#if.to_string(),
915 "if c 1& c 2; then b 1& b 2; elif c 3& then b 3& else b 4; fi"
916 );
917
918 let r#if: CompoundCommand = CompoundCommand::If {
919 condition: "true".parse().unwrap(),
920 body: ":".parse().unwrap(),
921 elifs: vec![
922 ElifThen {
923 condition: "false".parse().unwrap(),
924 body: "a".parse().unwrap(),
925 },
926 ElifThen {
927 condition: "echo&".parse().unwrap(),
928 body: "b&".parse().unwrap(),
929 },
930 ],
931 r#else: None,
932 };
933 assert_eq!(
934 r#if.to_string(),
935 "if true; then :; elif false; then a; elif echo& then b& fi"
936 );
937 }
938
939 #[test]
940 fn case_display() {
941 let subject = "foo".parse().unwrap();
942 let items = Vec::<CaseItem>::new();
943 let case = CompoundCommand::Case { subject, items };
944 assert_eq!(case.to_string(), "case foo in esac");
945
946 let subject = "bar".parse().unwrap();
947 let items = vec!["foo)".parse::<CaseItem>().unwrap()];
948 let case = CompoundCommand::Case { subject, items };
949 assert_eq!(case.to_string(), "case bar in (foo) ;; esac");
950
951 let subject = "baz".parse().unwrap();
952 let items = vec![
953 "1)".parse::<CaseItem>().unwrap(),
954 "(a|b|c) :&".parse().unwrap(),
955 ];
956 let case = CompoundCommand::Case { subject, items };
957 assert_eq!(case.to_string(), "case baz in (1) ;; (a | b | c) :&;; esac");
958 }
959
960 #[test]
961 fn function_definition_display() {
962 let body = FullCompoundCommand {
963 command: "( bar )".parse::<CompoundCommand>().unwrap(),
964 redirs: vec![],
965 };
966 let fd = FunctionDefinition {
967 has_keyword: false,
968 name: Word::from_str("foo").unwrap(),
969 body: Rc::new(body),
970 };
971 assert_eq!(fd.to_string(), "foo() (bar)");
972 }
973
974 #[test]
975 fn pipeline_display() {
976 let mut p = Pipeline {
977 commands: vec![Rc::new("first".parse::<Command>().unwrap())],
978 negation: false,
979 };
980 assert_eq!(p.to_string(), "first");
981
982 p.negation = true;
983 assert_eq!(p.to_string(), "! first");
984
985 p.commands.push(Rc::new("second".parse().unwrap()));
986 assert_eq!(p.to_string(), "! first | second");
987
988 p.commands.push(Rc::new("third".parse().unwrap()));
989 p.negation = false;
990 assert_eq!(p.to_string(), "first | second | third");
991 }
992
993 #[test]
994 fn and_or_list_display() {
995 let p = "first".parse::<Pipeline>().unwrap();
996 let mut aol = AndOrList {
997 first: p,
998 rest: vec![],
999 };
1000 assert_eq!(aol.to_string(), "first");
1001
1002 let p = "second".parse().unwrap();
1003 aol.rest.push((AndOr::AndThen, p));
1004 assert_eq!(aol.to_string(), "first && second");
1005
1006 let p = "third".parse().unwrap();
1007 aol.rest.push((AndOr::OrElse, p));
1008 assert_eq!(aol.to_string(), "first && second || third");
1009 }
1010
1011 #[test]
1012 fn list_display() {
1013 let and_or = "first".parse::<AndOrList>().unwrap();
1014 let item = Item {
1015 and_or: Rc::new(and_or),
1016 async_flag: None,
1017 };
1018 let mut list = List(vec![item]);
1019 assert_eq!(list.to_string(), "first");
1020
1021 let and_or = "second".parse().unwrap();
1022 let item = Item {
1023 and_or: Rc::new(and_or),
1024 async_flag: Some(Location::dummy("")),
1025 };
1026 list.0.push(item);
1027 assert_eq!(list.to_string(), "first; second&");
1028
1029 let and_or = "third".parse().unwrap();
1030 let item = Item {
1031 and_or: Rc::new(and_or),
1032 async_flag: None,
1033 };
1034 list.0.push(item);
1035 assert_eq!(list.to_string(), "first; second& third");
1036 }
1037
1038 #[test]
1039 fn list_display_alternate() {
1040 let and_or = "first".parse::<AndOrList>().unwrap();
1041 let item = Item {
1042 and_or: Rc::new(and_or),
1043 async_flag: None,
1044 };
1045 let mut list = List(vec![item]);
1046 assert_eq!(format!("{list:#}"), "first;");
1047
1048 let and_or = "second".parse().unwrap();
1049 let item = Item {
1050 and_or: Rc::new(and_or),
1051 async_flag: Some(Location::dummy("")),
1052 };
1053 list.0.push(item);
1054 assert_eq!(format!("{list:#}"), "first; second&");
1055
1056 let and_or = "third".parse().unwrap();
1057 let item = Item {
1058 and_or: Rc::new(and_or),
1059 async_flag: None,
1060 };
1061 list.0.push(item);
1062 assert_eq!(format!("{list:#}"), "first; second& third;");
1063 }
1064}