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 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 { 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 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 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.0 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 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
383impl 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
401impl 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 {
625 name: "".to_string(),
626 followed_by_slash: false,
627 };
628 assert_eq!(tilde.to_string(), "~");
629 let tilde = Tilde {
630 name: "foo".to_string(),
631 followed_by_slash: true,
632 };
633 assert_eq!(tilde.to_string(), "~foo");
634 }
635
636 #[test]
637 fn scalar_display() {
638 let s = Scalar(Word::from_str("my scalar value").unwrap());
639 assert_eq!(s.to_string(), "my scalar value");
640 }
641
642 #[test]
643 fn array_display_empty() {
644 let a = Array(vec![]);
645 assert_eq!(a.to_string(), "()");
646 }
647
648 #[test]
649 fn array_display_one() {
650 let a = Array(vec![Word::from_str("one").unwrap()]);
651 assert_eq!(a.to_string(), "(one)");
652 }
653
654 #[test]
655 fn array_display_many() {
656 let a = Array(vec![
657 Word::from_str("let").unwrap(),
658 Word::from_str("me").unwrap(),
659 Word::from_str("see").unwrap(),
660 ]);
661 assert_eq!(a.to_string(), "(let me see)");
662 }
663
664 #[test]
665 fn assign_display() {
666 let mut a = Assign::from_str("foo=bar").unwrap();
667 assert_eq!(a.to_string(), "foo=bar");
668
669 a.value = Array(vec![]);
670 assert_eq!(a.to_string(), "foo=()");
671 }
672
673 #[test]
674 fn here_doc_display() {
675 let heredoc = HereDoc {
676 delimiter: Word::from_str("END").unwrap(),
677 remove_tabs: true,
678 content: Text::from_str("here").unwrap().into(),
679 };
680 assert_eq!(heredoc.to_string(), "<<-END");
681
682 let heredoc = HereDoc {
683 delimiter: Word::from_str("XXX").unwrap(),
684 remove_tabs: false,
685 content: Text::from_str("there").unwrap().into(),
686 };
687 assert_eq!(heredoc.to_string(), "<<XXX");
688 }
689
690 #[test]
691 fn here_doc_display_disambiguation() {
692 let heredoc = HereDoc {
693 delimiter: Word::from_str("--").unwrap(),
694 remove_tabs: false,
695 content: Text::from_str("here").unwrap().into(),
696 };
697 assert_eq!(heredoc.to_string(), "<< --");
698
699 let heredoc = HereDoc {
700 delimiter: Word::from_str("-").unwrap(),
701 remove_tabs: true,
702 content: Text::from_str("here").unwrap().into(),
703 };
704 assert_eq!(heredoc.to_string(), "<<- -");
705 }
706
707 #[test]
708 fn redir_display() {
709 let heredoc = HereDoc {
710 delimiter: Word::from_str("END").unwrap(),
711 remove_tabs: false,
712 content: Text::from_str("here").unwrap().into(),
713 };
714
715 let redir = Redir {
716 fd: None,
717 body: heredoc.into(),
718 };
719 assert_eq!(redir.to_string(), "<<END");
720 let redir = Redir {
721 fd: Some(Fd(0)),
722 ..redir
723 };
724 assert_eq!(redir.to_string(), "0<<END");
725 let redir = Redir {
726 fd: Some(Fd(9)),
727 ..redir
728 };
729 assert_eq!(redir.to_string(), "9<<END");
730 }
731
732 #[test]
733 fn simple_command_display() {
734 let mut command = SimpleCommand {
735 assigns: vec![],
736 words: vec![],
737 redirs: vec![].into(),
738 };
739 assert_eq!(command.to_string(), "");
740
741 command
742 .assigns
743 .push(Assign::from_str("name=value").unwrap());
744 assert_eq!(command.to_string(), "name=value");
745
746 command
747 .assigns
748 .push(Assign::from_str("hello=world").unwrap());
749 assert_eq!(command.to_string(), "name=value hello=world");
750
751 command
752 .words
753 .push((Word::from_str("echo").unwrap(), ExpansionMode::Multiple));
754 assert_eq!(command.to_string(), "name=value hello=world echo");
755
756 command
757 .words
758 .push((Word::from_str("foo").unwrap(), ExpansionMode::Single));
759 assert_eq!(command.to_string(), "name=value hello=world echo foo");
760
761 Rc::make_mut(&mut command.redirs).push(Redir {
762 fd: None,
763 body: RedirBody::from(HereDoc {
764 delimiter: Word::from_str("END").unwrap(),
765 remove_tabs: false,
766 content: Text::from_str("").unwrap().into(),
767 }),
768 });
769 assert_eq!(command.to_string(), "name=value hello=world echo foo <<END");
770
771 command.assigns.clear();
772 assert_eq!(command.to_string(), "echo foo <<END");
773
774 command.words.clear();
775 assert_eq!(command.to_string(), "<<END");
776
777 Rc::make_mut(&mut command.redirs).push(Redir {
778 fd: Some(Fd(1)),
779 body: RedirBody::from(HereDoc {
780 delimiter: Word::from_str("here").unwrap(),
781 remove_tabs: true,
782 content: Text::from_str("ignored").unwrap().into(),
783 }),
784 });
785 assert_eq!(command.to_string(), "<<END 1<<-here");
786
787 command.assigns.push(Assign::from_str("foo=bar").unwrap());
788 assert_eq!(command.to_string(), "foo=bar <<END 1<<-here");
789 }
790
791 #[test]
792 fn simple_command_display_with_keyword() {
793 let command = SimpleCommand {
794 assigns: vec![],
795 words: vec![("if".parse().unwrap(), ExpansionMode::Multiple)],
796 redirs: vec!["<foo".parse().unwrap()].into(),
797 };
798 assert_eq!(command.to_string(), "<foo if");
799 }
800
801 #[test]
802 fn elif_then_display() {
803 let condition: List = "c 1& c 2".parse().unwrap();
804 let body = "b 1& b 2".parse().unwrap();
805 let elif = ElifThen { condition, body };
806 assert_eq!(format!("{elif}"), "elif c 1& c 2; then b 1& b 2");
807 assert_eq!(format!("{elif:#}"), "elif c 1& c 2; then b 1& b 2;");
808
809 let condition: List = "c&".parse().unwrap();
810 let body = "b&".parse().unwrap();
811 let elif = ElifThen { condition, body };
812 assert_eq!(format!("{elif}"), "elif c& then b&");
813 assert_eq!(format!("{elif:#}"), "elif c& then b&");
814 }
815
816 #[test]
817 fn case_item_display() {
818 let item = CaseItem {
819 patterns: vec!["foo".parse().unwrap()],
820 body: "".parse::<List>().unwrap(),
821 continuation: CaseContinuation::Break,
822 };
823 assert_eq!(item.to_string(), "(foo) ;;");
824
825 let item = CaseItem {
826 patterns: vec!["bar".parse().unwrap()],
827 body: "echo ok".parse::<List>().unwrap(),
828 continuation: CaseContinuation::Break,
829 };
830 assert_eq!(item.to_string(), "(bar) echo ok;;");
831
832 let item = CaseItem {
833 patterns: ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect(),
834 body: "foo; bar&".parse::<List>().unwrap(),
835 continuation: CaseContinuation::Break,
836 };
837 assert_eq!(item.to_string(), "(a | b | c) foo; bar&;;");
838
839 let item = CaseItem {
840 patterns: vec!["foo".parse().unwrap()],
841 body: "bar".parse::<List>().unwrap(),
842 continuation: CaseContinuation::FallThrough,
843 };
844 assert_eq!(item.to_string(), "(foo) bar;&");
845 }
846
847 #[test]
848 fn grouping_display() {
849 let list = "foo".parse::<List>().unwrap();
850 let grouping = CompoundCommand::Grouping(list);
851 assert_eq!(grouping.to_string(), "{ foo; }");
852 }
853
854 #[test]
855 fn for_display_without_values() {
856 let name = Word::from_str("foo").unwrap();
857 let values = None;
858 let body = "echo ok".parse::<List>().unwrap();
859 let r#for = CompoundCommand::For { name, values, body };
860 assert_eq!(r#for.to_string(), "for foo do echo ok; done");
861 }
862
863 #[test]
864 fn for_display_with_empty_values() {
865 let name = Word::from_str("foo").unwrap();
866 let values = Some(vec![]);
867 let body = "echo ok".parse::<List>().unwrap();
868 let r#for = CompoundCommand::For { name, values, body };
869 assert_eq!(r#for.to_string(), "for foo in; do echo ok; done");
870 }
871
872 #[test]
873 fn for_display_with_some_values() {
874 let name = Word::from_str("V").unwrap();
875 let values = Some(vec![
876 Word::from_str("a").unwrap(),
877 Word::from_str("b").unwrap(),
878 ]);
879 let body = "one; two&".parse::<List>().unwrap();
880 let r#for = CompoundCommand::For { name, values, body };
881 assert_eq!(r#for.to_string(), "for V in a b; do one; two& done");
882 }
883
884 #[test]
885 fn while_display() {
886 let condition = "true& false".parse::<List>().unwrap();
887 let body = "echo ok".parse::<List>().unwrap();
888 let r#while = CompoundCommand::While { condition, body };
889 assert_eq!(r#while.to_string(), "while true& false; do echo ok; done");
890 }
891
892 #[test]
893 fn until_display() {
894 let condition = "true& false".parse::<List>().unwrap();
895 let body = "echo ok".parse::<List>().unwrap();
896 let until = CompoundCommand::Until { condition, body };
897 assert_eq!(until.to_string(), "until true& false; do echo ok; done");
898 }
899
900 #[test]
901 fn if_display() {
902 let r#if: CompoundCommand = CompoundCommand::If {
903 condition: "c 1; c 2&".parse().unwrap(),
904 body: "b 1; b 2&".parse().unwrap(),
905 elifs: vec![],
906 r#else: None,
907 };
908 assert_eq!(r#if.to_string(), "if c 1; c 2& then b 1; b 2& fi");
909
910 let r#if: CompoundCommand = CompoundCommand::If {
911 condition: "c 1& c 2;".parse().unwrap(),
912 body: "b 1& b 2;".parse().unwrap(),
913 elifs: vec![ElifThen {
914 condition: "c 3&".parse().unwrap(),
915 body: "b 3&".parse().unwrap(),
916 }],
917 r#else: Some("b 4".parse().unwrap()),
918 };
919 assert_eq!(
920 r#if.to_string(),
921 "if c 1& c 2; then b 1& b 2; elif c 3& then b 3& else b 4; fi"
922 );
923
924 let r#if: CompoundCommand = CompoundCommand::If {
925 condition: "true".parse().unwrap(),
926 body: ":".parse().unwrap(),
927 elifs: vec![
928 ElifThen {
929 condition: "false".parse().unwrap(),
930 body: "a".parse().unwrap(),
931 },
932 ElifThen {
933 condition: "echo&".parse().unwrap(),
934 body: "b&".parse().unwrap(),
935 },
936 ],
937 r#else: None,
938 };
939 assert_eq!(
940 r#if.to_string(),
941 "if true; then :; elif false; then a; elif echo& then b& fi"
942 );
943 }
944
945 #[test]
946 fn case_display() {
947 let subject = "foo".parse().unwrap();
948 let items = Vec::<CaseItem>::new();
949 let case = CompoundCommand::Case { subject, items };
950 assert_eq!(case.to_string(), "case foo in esac");
951
952 let subject = "bar".parse().unwrap();
953 let items = vec!["foo)".parse::<CaseItem>().unwrap()];
954 let case = CompoundCommand::Case { subject, items };
955 assert_eq!(case.to_string(), "case bar in (foo) ;; esac");
956
957 let subject = "baz".parse().unwrap();
958 let items = vec![
959 "1)".parse::<CaseItem>().unwrap(),
960 "(a|b|c) :&".parse().unwrap(),
961 ];
962 let case = CompoundCommand::Case { subject, items };
963 assert_eq!(case.to_string(), "case baz in (1) ;; (a | b | c) :&;; esac");
964 }
965
966 #[test]
967 fn function_definition_display() {
968 let body = FullCompoundCommand {
969 command: "( bar )".parse::<CompoundCommand>().unwrap(),
970 redirs: vec![],
971 };
972 let fd = FunctionDefinition {
973 has_keyword: false,
974 name: Word::from_str("foo").unwrap(),
975 body: Rc::new(body),
976 };
977 assert_eq!(fd.to_string(), "foo() (bar)");
978 }
979
980 #[test]
981 fn pipeline_display() {
982 let mut p = Pipeline {
983 commands: vec![Rc::new("first".parse::<Command>().unwrap())],
984 negation: false,
985 };
986 assert_eq!(p.to_string(), "first");
987
988 p.negation = true;
989 assert_eq!(p.to_string(), "! first");
990
991 p.commands.push(Rc::new("second".parse().unwrap()));
992 assert_eq!(p.to_string(), "! first | second");
993
994 p.commands.push(Rc::new("third".parse().unwrap()));
995 p.negation = false;
996 assert_eq!(p.to_string(), "first | second | third");
997 }
998
999 #[test]
1000 fn and_or_list_display() {
1001 let p = "first".parse::<Pipeline>().unwrap();
1002 let mut aol = AndOrList {
1003 first: p,
1004 rest: vec![],
1005 };
1006 assert_eq!(aol.to_string(), "first");
1007
1008 let p = "second".parse().unwrap();
1009 aol.rest.push((AndOr::AndThen, p));
1010 assert_eq!(aol.to_string(), "first && second");
1011
1012 let p = "third".parse().unwrap();
1013 aol.rest.push((AndOr::OrElse, p));
1014 assert_eq!(aol.to_string(), "first && second || third");
1015 }
1016
1017 #[test]
1018 fn list_display() {
1019 let and_or = "first".parse::<AndOrList>().unwrap();
1020 let item = Item {
1021 and_or: Rc::new(and_or),
1022 async_flag: None,
1023 };
1024 let mut list = List(vec![item]);
1025 assert_eq!(list.to_string(), "first");
1026
1027 let and_or = "second".parse().unwrap();
1028 let item = Item {
1029 and_or: Rc::new(and_or),
1030 async_flag: Some(Location::dummy("")),
1031 };
1032 list.0.push(item);
1033 assert_eq!(list.to_string(), "first; second&");
1034
1035 let and_or = "third".parse().unwrap();
1036 let item = Item {
1037 and_or: Rc::new(and_or),
1038 async_flag: None,
1039 };
1040 list.0.push(item);
1041 assert_eq!(list.to_string(), "first; second& third");
1042 }
1043
1044 #[test]
1045 fn list_display_alternate() {
1046 let and_or = "first".parse::<AndOrList>().unwrap();
1047 let item = Item {
1048 and_or: Rc::new(and_or),
1049 async_flag: None,
1050 };
1051 let mut list = List(vec![item]);
1052 assert_eq!(format!("{list:#}"), "first;");
1053
1054 let and_or = "second".parse().unwrap();
1055 let item = Item {
1056 and_or: Rc::new(and_or),
1057 async_flag: Some(Location::dummy("")),
1058 };
1059 list.0.push(item);
1060 assert_eq!(format!("{list:#}"), "first; second&");
1061
1062 let and_or = "third".parse().unwrap();
1063 let item = Item {
1064 and_or: Rc::new(and_or),
1065 async_flag: None,
1066 };
1067 list.0.push(item);
1068 assert_eq!(format!("{list:#}"), "first; second& third;");
1069 }
1070}