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(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 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 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("".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}