bash - format_print.go
1 package bash
2
3 import (
4 "strings"
5
6 "vimagination.zapto.org/parser"
7 )
8
9 func (a ArithmeticExpansion) printSource(w writer, v bool) {
10 if a.Expression {
11 w.WriteString("((")
12 } else {
13 w.WriteString("$((")
14 }
15
16 if len(a.WordsAndOperators) > 0 {
17 if v {
18 w.WriteString(" ")
19 }
20
21 a.WordsAndOperators[0].printSource(w, v)
22
23 for _, wo := range a.WordsAndOperators[1:] {
24 if v && !wo.operatorIsToken(parser.Token{Type: TokenPunctuator, Data: ";"}) {
25 w.WriteString(" ")
26 }
27
28 wo.printSource(w, v)
29 }
30
31 if v {
32 w.WriteString(" ")
33 }
34 }
35
36 w.WriteString("))")
37 }
38
39 func (a ArrayWord) printSource(w writer, v bool) {
40 if len(a.Comments[0]) > 0 {
41 a.Comments[0].printSource(w, true)
42 }
43
44 a.Word.printSource(w, v)
45
46 if len(a.Comments[1]) > 0 {
47 w.WriteString(" ")
48 a.Comments[1].printSource(w, false)
49 }
50 }
51
52 func (a Assignment) printSource(w writer, v bool) {
53 if a.Assignment == AssignmentAssign || a.Assignment == AssignmentAppend {
54 a.Identifier.printSource(w, v)
55 a.Assignment.printSource(w, v)
56
57 if a.Value != nil {
58 a.Value.printSource(w, v)
59 } else {
60 parens := 0
61
62 for _, e := range a.Expression {
63 if parens > 0 {
64 w.WriteString(" ")
65 }
66
67 e.printSource(w, v)
68
69 if v && e.Operator != nil {
70 if e.Operator.Token == (parser.Token{Type: TokenPunctuator, Data: "("}) {
71 parens++
72 } else if e.Operator.Token == (parser.Token{Type: TokenPunctuator, Data: ")"}) {
73 parens--
74 }
75 }
76 }
77 }
78 }
79 }
80
81 func (a AssignmentOrWord) printSource(w writer, v bool) {
82 if a.Assignment != nil {
83 a.Assignment.printSource(w, v)
84 } else if a.Word != nil {
85 a.Word.printSource(w, v)
86 }
87 }
88
89 func (b BraceExpansion) printSource(w writer, v bool) {
90 if b.BraceExpansionType == BraceExpansionWords && len(b.Words) > 1 || (b.BraceExpansionType == BraceExpansionSequence && (len(b.Words) == 2 || len(b.Words) == 3)) {
91 w.WriteString("{")
92
93 b.Words[0].printSource(w, v)
94
95 for _, wd := range b.Words[1:] {
96 b.BraceExpansionType.printSource(w, v)
97 wd.printSource(w, v)
98 }
99
100 w.WriteString("}")
101 }
102 }
103
104 func (b BraceWord) printSource(w writer, v bool) {
105 for _, wp := range b.Parts {
106 wp.printSource(w, v)
107 }
108 }
109
110 func (c CaseCompound) printSource(w writer, v bool) {
111 w.WriteString("case ")
112 c.Word.printSource(w, v)
113
114 if len(c.Comments[0]) > 0 {
115 w.WriteString(" ")
116 c.Comments[0].printSource(w, false)
117 w.WriteString("\nin")
118 } else {
119 w.WriteString(" in")
120 }
121
122 if len(c.Comments[1]) > 0 {
123 w.WriteString(" ")
124 c.Comments[1].printSource(w, false)
125 }
126
127 for _, m := range c.Matches {
128 w.WriteString("\n")
129 m.printSource(w, v)
130 }
131
132 if len(c.Comments[2]) > 0 {
133 w.WriteString("\n")
134 c.Comments[2].printSource(w, false)
135 }
136
137 w.WriteString("\nesac")
138 }
139
140 func (c Command) printSource(w writer, v bool) {
141 if len(c.Vars) > 0 {
142 c.Vars[0].printSource(w, v)
143
144 for _, vr := range c.Vars[1:] {
145 w.WriteString(" ")
146 vr.printSource(w, v)
147 }
148 }
149
150 if len(c.AssignmentsOrWords) > 0 {
151 if len(c.Vars) > 0 {
152 w.WriteString(" ")
153 }
154
155 c.AssignmentsOrWords[0].printSource(w, v)
156
157 for _, wd := range c.AssignmentsOrWords[1:] {
158 w.WriteString(" ")
159 wd.printSource(w, v)
160 }
161 }
162
163 if len(c.Redirections) > 0 {
164 if len(c.Vars) > 0 || len(c.AssignmentsOrWords) > 0 {
165 w.WriteString(" ")
166 }
167
168 c.Redirections[0].printSource(w, v)
169
170 for _, r := range c.Redirections[1:] {
171 w.WriteString(" ")
172 r.printSource(w, v)
173 }
174 }
175 }
176
177 func (c Command) printHeredoc(w writer, v bool) {
178 for _, r := range c.Redirections {
179 r.printHeredoc(w, v)
180 }
181 }
182
183 func (c Command) hasHeredoc() bool {
184 for _, r := range c.Redirections {
185 if r.isHeredoc() {
186 return true
187 }
188 }
189
190 return false
191 }
192
193 func (c CommandOrCompound) printSource(w writer, v bool) {
194 if c.Command != nil {
195 c.Command.printSource(w, v)
196 } else if c.Compound != nil {
197 c.Compound.printSource(w, v)
198 }
199 }
200
201 func (c CommandOrCompound) printHeredoc(w writer, v bool) {
202 if c.Command != nil {
203 c.Command.printHeredoc(w, v)
204 } else if c.Compound != nil {
205 c.Compound.printHeredoc(w, v)
206 }
207 }
208
209 func (c CommandOrCompound) hasHeredoc() bool {
210 if c.Command != nil && c.Command.hasHeredoc() {
211 return true
212 }
213
214 if c.Compound != nil && c.Compound.hasHeredoc() {
215 return true
216 }
217
218 return false
219 }
220
221 func (c CommandSubstitution) printSource(w writer, v bool) {
222 closing := ")"
223
224 switch c.SubstitutionType {
225 case SubstitutionNew:
226 w.WriteString("$(")
227 case SubstitutionBacktick:
228 closing = "`"
229
230 if c.Backtick != nil {
231 closing = c.Backtick.Data
232 }
233
234 w.WriteString(closing)
235
236 closing = c.Backtick.Data
237 case SubstitutionProcessInput:
238 w.WriteString("<(")
239 case SubstitutionProcessOutput:
240 w.WriteString(">(")
241 }
242
243 if c.Command.isMultiline(v) {
244 ip := w.Indent()
245
246 ip.WriteString("\n")
247 c.Command.printSource(ip, v)
248 w.WriteString("\n")
249 } else {
250 c.Command.printSourceEnd(w, v, false)
251 }
252
253 w.WriteString(closing)
254 }
255
256 func (c Compound) printSource(w writer, v bool) {
257 if c.IfCompound != nil {
258 c.IfCompound.printSource(w, v)
259 } else if c.CaseCompound != nil {
260 c.CaseCompound.printSource(w, v)
261 } else if c.LoopCompound != nil {
262 c.LoopCompound.printSource(w, v)
263 } else if c.ForCompound != nil {
264 c.ForCompound.printSource(w, v)
265 } else if c.SelectCompound != nil {
266 c.SelectCompound.printSource(w, v)
267 } else if c.GroupingCompound != nil {
268 c.GroupingCompound.printSource(w, v)
269 } else if c.TestCompound != nil {
270 c.TestCompound.printSource(w, v)
271 } else if c.ArithmeticCompound != nil {
272 c.ArithmeticCompound.printSource(w, v)
273 } else if c.FunctionCompound != nil {
274 c.FunctionCompound.printSource(w, v)
275 }
276
277 for _, r := range c.Redirections {
278 w.WriteString(" ")
279 r.printSource(w, v)
280 }
281 }
282
283 func (c Compound) printHeredoc(w writer, v bool) {
284 for _, r := range c.Redirections {
285 r.printHeredoc(w, v)
286 }
287 }
288
289 func (c Compound) hasHeredoc() bool {
290 for _, r := range c.Redirections {
291 if r.isHeredoc() {
292 return true
293 }
294 }
295
296 return false
297 }
298
299 func (f File) printSource(w writer, v bool) {
300 f.printSourceEnd(w, v, true)
301 }
302
303 func (f File) printSourceEnd(w writer, v, end bool) {
304 f.Comments[0].printSource(w, true)
305
306 topLevel := w.Underlying() == w
307
308 if len(f.Lines) > 0 {
309 if topLevel && len(f.Comments[0]) > 0 {
310 w.WriteString("\n")
311 }
312
313 f.Lines[0].printSourceEnd(w, v, end || len(f.Lines) > 1)
314
315 lastLine := lastTokenPos(f.Lines[0].Tokens)
316
317 for n, l := range f.Lines[1:] {
318 if firstTokenPos(l.Tokens) > lastLine+1 {
319 w.WriteString("\n")
320 }
321
322 w.WriteString("\n")
323 l.printSourceEnd(w, v, end || len(f.Lines) > n+1)
324
325 lastLine = lastTokenPos(l.Tokens)
326 }
327 }
328
329 if len(f.Comments[1]) > 0 {
330 w.WriteString("\n\n")
331 f.Comments[1].printSource(w, false)
332 }
333
334 if topLevel && end {
335 w.WriteString("\n")
336 }
337 }
338
339 func firstTokenPos(tk Tokens) (pos uint64) {
340 if len(tk) > 0 {
341 pos = tk[0].Line
342 }
343
344 return pos
345 }
346
347 func lastTokenPos(tk Tokens) (pos uint64) {
348 if len(tk) > 0 {
349 pos = tk[len(tk)-1].Line
350 }
351
352 return pos
353 }
354
355 func (f ForCompound) printSource(w writer, v bool) {
356 if f.ArithmeticExpansion != nil || f.Identifier != nil {
357 w.WriteString("for ")
358
359 if f.ArithmeticExpansion != nil {
360 f.ArithmeticExpansion.printSource(w, v)
361 } else {
362 w.WriteString(f.Identifier.Data)
363
364 if f.Words != nil {
365 w.WriteString(" ")
366 f.Comments[0].printSource(w, true)
367 w.WriteString("in")
368
369 for _, wd := range f.Words {
370 w.WriteString(" ")
371 wd.printSource(w, v)
372 }
373 }
374 }
375
376 ip := w.Indent()
377
378 ip.WriteString("; ")
379 f.Comments[1].printSource(ip, true)
380 ip.WriteString("do\n")
381 f.File.printSource(ip, v)
382 w.WriteString("\ndone")
383 }
384 }
385
386 func (f FunctionCompound) printSource(w writer, v bool) {
387 if f.Identifier != nil {
388 if f.HasKeyword {
389 w.WriteString("function ")
390 }
391
392 w.WriteString(f.Identifier.Data)
393 w.WriteString("() ")
394 f.Comments.printSource(w, true)
395 f.Body.printSource(w, v)
396 }
397 }
398
399 func (g GroupingCompound) printSource(w writer, v bool) {
400 if g.SubShell {
401 w.WriteString("(")
402 } else {
403 w.WriteString("{")
404 }
405
406 ip := w.Indent()
407 multiline := v || g.File.isMultiline(v)
408
409 if len(g.File.Comments[0]) > 0 || !multiline {
410 w.WriteString(" ")
411 } else {
412 ip.WriteString("\n")
413 }
414
415 g.File.printSource(ip, v)
416
417 if multiline {
418 w.WriteString("\n")
419 } else {
420 w.WriteString(" ")
421 }
422
423 if g.SubShell {
424 w.WriteString(")")
425 } else {
426 w.WriteString("}")
427 }
428 }
429
430 func (h Heredoc) printSource(w writer, v bool) {
431 w.WriteString("\n")
432
433 for _, p := range h.HeredocPartsOrWords {
434 p.printSource(w, v)
435 }
436 }
437
438 func (h HeredocPartOrWord) printSource(w writer, v bool) {
439 if h.HeredocPart != nil {
440 w.WriteString(h.HeredocPart.Data)
441 } else if h.Word != nil {
442 h.Word.printSource(w, v)
443 }
444 }
445
446 func (i IfCompound) printSource(w writer, v bool) {
447 w.WriteString("if ")
448 i.If.printSource(w, v)
449
450 for _, e := range i.ElIf {
451 w.WriteString("\nelif ")
452 e.printSource(w, v)
453 }
454
455 if i.Else != nil {
456 ip := w.Indent()
457
458 w.WriteString("\nelse")
459 ip.WriteString("\n")
460 i.Else.printSource(ip, v)
461 }
462
463 w.WriteString("\nfi")
464 }
465
466 func (l Line) printSource(w writer, v bool) {
467 l.printSourceEnd(w, v, true)
468 }
469
470 func (l Line) printSourceEnd(w writer, v, end bool) {
471 if len(l.Statements) > 0 {
472 end = end && !l.hasHeredoc()
473
474 l.Comments[0].printSource(w, true)
475 l.Statements[0].printSourceEnd(w, v, end || len(l.Statements) > 1)
476
477 for n, s := range l.Statements[1:] {
478 if v {
479 l.Statements[n].printHeredoc(w, v)
480 w.WriteString("\n")
481 } else {
482 w.WriteString(" ")
483 }
484
485 s.printSourceEnd(w, v, end || len(l.Statements) > n+1)
486 }
487
488 if len(l.Comments[1]) > 0 {
489 w.WriteString(" ")
490 l.Comments[1].printSource(w, false)
491 }
492
493 if v {
494 l.Statements[len(l.Statements)-1].printHeredoc(w, v)
495 } else {
496 for _, s := range l.Statements {
497 s.printHeredoc(w, v)
498 }
499 }
500 }
501 }
502
503 func (l Line) hasHeredoc() bool {
504 for _, s := range l.Statements {
505 if s.hasHeredoc() {
506 return true
507 }
508 }
509
510 return false
511 }
512
513 func (l LoopCompound) printSource(w writer, v bool) {
514 if l.Until {
515 w.WriteString("until ")
516 } else {
517 w.WriteString("while ")
518 }
519
520 l.Statement.printSource(w, v)
521
522 if l.Statement.endsWithGrouping() {
523 w.WriteString(";")
524 }
525
526 if len(l.Comments) > 0 {
527 w.WriteString(" ")
528 l.Comments.printSource(w, true)
529 w.WriteString("do")
530 } else {
531 w.WriteString(" do")
532 }
533
534 ip := w.Indent()
535
536 ip.WriteString("\n")
537 l.File.printSource(ip, v)
538 w.WriteString("\ndone")
539 }
540
541 func (p ParameterAssign) printSource(w writer, v bool) {
542 if p.Identifier != nil {
543 w.WriteString(p.Identifier.Data)
544
545 if len(p.Subscript) > 0 {
546 w.WriteString("[")
547 p.Subscript[0].printSource(w, v)
548
549 for _, s := range p.Subscript[1:] {
550 if v {
551 w.WriteString(" ")
552 }
553
554 s.printSource(w, v)
555 }
556
557 w.WriteString("]")
558 }
559 }
560 }
561
562 func (p ParameterExpansion) printSource(w writer, v bool) {
563 w.WriteString("${")
564
565 if p.Indirect || p.Type == ParameterPrefix || p.Type == ParameterPrefixSeperate {
566 w.WriteString("!")
567 } else if p.Type == ParameterLength {
568 w.WriteString("#")
569 }
570
571 p.Parameter.printSource(w, v)
572
573 if p.BraceWord != nil {
574 switch p.Type {
575 case ParameterSubstitution:
576 w.WriteString(":=")
577 p.BraceWord.printSource(w, v)
578 case ParameterAssignment:
579 w.WriteString(":?")
580 p.BraceWord.printSource(w, v)
581 case ParameterMessage:
582 w.WriteString(":+")
583 p.BraceWord.printSource(w, v)
584 case ParameterSetAssign:
585 w.WriteString(":-")
586 p.BraceWord.printSource(w, v)
587 case ParameterUnsetSubstitution:
588 w.WriteString("=")
589 p.BraceWord.printSource(w, v)
590 case ParameterUnsetAssignment:
591 w.WriteString("?")
592 p.BraceWord.printSource(w, v)
593 case ParameterUnsetMessage:
594 w.WriteString("+")
595 p.BraceWord.printSource(w, v)
596 case ParameterUnsetSetAssign:
597 w.WriteString("-")
598 p.BraceWord.printSource(w, v)
599 case ParameterRemoveStartShortest:
600 w.WriteString("#")
601 p.BraceWord.printSource(w, v)
602 case ParameterRemoveStartLongest:
603 w.WriteString("##")
604 p.BraceWord.printSource(w, v)
605 case ParameterRemoveEndShortest:
606 w.WriteString("%")
607 p.BraceWord.printSource(w, v)
608 case ParameterRemoveEndLongest:
609 w.WriteString("%%")
610 p.BraceWord.printSource(w, v)
611 }
612 } else if p.Pattern != nil {
613 isReplacement := false
614
615 switch p.Type {
616 case ParameterReplace:
617 isReplacement = true
618
619 w.WriteString("/")
620 w.WriteString(p.Pattern.Data)
621 case ParameterReplaceAll:
622 isReplacement = true
623
624 w.WriteString("//")
625 w.WriteString(p.Pattern.Data)
626 case ParameterReplaceStart:
627 isReplacement = true
628
629 w.WriteString("/#")
630 w.WriteString(p.Pattern.Data)
631 case ParameterReplaceEnd:
632 isReplacement = true
633
634 w.WriteString("/%")
635 w.WriteString(p.Pattern.Data)
636 case ParameterLowercaseFirstMatch:
637 w.WriteString(",")
638 w.WriteString(p.Pattern.Data)
639 case ParameterLowercaseAllMatches:
640 w.WriteString(",,")
641 w.WriteString(p.Pattern.Data)
642 case ParameterUppercaseFirstMatch:
643 w.WriteString("^")
644 w.WriteString(p.Pattern.Data)
645 case ParameterUppercaseAllMatches:
646 w.WriteString("^^")
647 w.WriteString(p.Pattern.Data)
648 }
649
650 if isReplacement && p.String != nil {
651 w.WriteString("/")
652 p.String.printSource(w, v)
653 }
654 } else if !p.Indirect {
655 switch p.Type {
656 case ParameterPrefix:
657 w.WriteString("*")
658 case ParameterPrefixSeperate:
659 w.WriteString("@")
660 }
661 }
662
663 switch p.Type {
664 case ParameterSubstring:
665 if p.SubstringStart != nil {
666 w.WriteString(":")
667
668 if len(p.SubstringStart.Parts) > 0 && p.SubstringStart.Parts[0].Part != nil && strings.HasPrefix(p.SubstringStart.Parts[0].Part.Data, "-") {
669 w.WriteString(" ")
670 }
671
672 p.SubstringStart.printSource(w, v)
673
674 if p.SubstringEnd != nil {
675 w.WriteString(":")
676 p.SubstringEnd.printSource(w, v)
677 }
678 }
679 case ParameterUppercase:
680 w.WriteString("@U")
681 case ParameterUppercaseFirst:
682 w.WriteString("@u")
683 case ParameterLowercase:
684 w.WriteString("@L")
685 case ParameterQuoted:
686 w.WriteString("@Q")
687 case ParameterEscaped:
688 w.WriteString("@E")
689 case ParameterPrompt:
690 w.WriteString("@P")
691 case ParameterDeclare:
692 w.WriteString("@A")
693 case ParameterQuotedArrays:
694 w.WriteString("@K")
695 case ParameterAttributes:
696 w.WriteString("@a")
697 case ParameterQuotedArraysSeperate:
698 w.WriteString("@k")
699 }
700
701 w.WriteString("}")
702 }
703
704 func (p Parameter) printSource(w writer, v bool) {
705 if p.Parameter != nil {
706 w.WriteString(p.Parameter.Data)
707
708 if p.Array != nil {
709 w.WriteString("[")
710
711 for _, a := range p.Array {
712 a.printSource(w, v)
713 }
714
715 w.WriteString("]")
716 }
717 }
718 }
719
720 func (p Pattern) printSource(w writer, v bool) {
721 for _, word := range p.Parts {
722 word.printSource(w, v)
723 }
724 }
725
726 func (p PatternLines) printSource(w writer, v bool) {
727 if len(p.Patterns) > 0 {
728 p.Comments.printSource(w, true)
729 p.Patterns[0].printSource(w, v)
730
731 for _, pattern := range p.Patterns[1:] {
732 w.WriteString("|")
733 pattern.printSource(w, v)
734 }
735
736 ip := w.Indent()
737
738 w.WriteString(")")
739 ip.WriteString("\n")
740
741 if len(p.Lines.Lines) > 0 {
742 p.Lines.printSource(ip, v)
743 } else {
744 ip.WriteString(";")
745 }
746
747 p.CaseTerminationType.printSource(ip, v)
748 }
749 }
750
751 func (p Pipeline) printSource(w writer, v bool) {
752 p.PipelineTime.printSource(w, v)
753
754 if p.Not {
755 w.WriteString("! ")
756 }
757
758 if p.Coproc {
759 w.WriteString("coproc ")
760
761 if p.CoprocIdentifier != nil {
762 w.WriteString(p.CoprocIdentifier.Data)
763 w.WriteString(" ")
764 }
765 }
766
767 p.CommandOrCompound.printSource(w, v)
768
769 if p.Pipeline != nil {
770 w.WriteString(" | ")
771 p.Pipeline.printSource(w, v)
772 }
773 }
774
775 func (p Pipeline) endsWithGrouping() bool {
776 if p.Pipeline != nil {
777 return p.Pipeline.endsWithGrouping()
778 }
779
780 return p.CommandOrCompound.Compound != nil && len(p.CommandOrCompound.Compound.Redirections) == 0 && (p.CommandOrCompound.Compound.GroupingCompound != nil || p.CommandOrCompound.Compound.FunctionCompound != nil && p.CommandOrCompound.Compound.FunctionCompound.Body.GroupingCompound != nil)
781 }
782
783 func (p Pipeline) printHeredoc(w writer, v bool) {
784 p.CommandOrCompound.printHeredoc(w, v)
785
786 if p.Pipeline != nil {
787 p.Pipeline.printHeredoc(w, v)
788 }
789 }
790
791 func (p Pipeline) hasHeredoc() bool {
792 if p.Pipeline != nil && p.Pipeline.hasHeredoc() {
793 return true
794 }
795
796 return p.CommandOrCompound.hasHeredoc()
797 }
798
799 func (r Redirection) printSource(w writer, v bool) {
800 if r.Redirector != nil {
801 if r.Input != nil {
802 w.WriteString(r.Input.Data)
803 }
804
805 w.WriteString(r.Redirector.Data)
806
807 if v && r.Redirector.Data != "<<" && r.Redirector.Data != "<<-" && r.Redirector.Data != ">&" || r.Output.firstPartIsProcessSubstitution() {
808 w.WriteString(" ")
809 }
810
811 r.Output.printSource(w, v)
812 }
813 }
814
815 func (r Redirection) printHeredoc(w writer, v bool) {
816 if r.Redirector != nil && r.Heredoc != nil && (r.Redirector.Data == "<<" || r.Redirector.Data == "<<-") {
817 if r.Redirector.Data == "<<" {
818 w = w.Underlying()
819 }
820
821 r.Heredoc.printSource(w, v)
822 r.Output.printSource(w, v)
823 }
824 }
825
826 func (s SelectCompound) printSource(w writer, v bool) {
827 if s.Identifier != nil {
828 w.WriteString("select ")
829 w.WriteString(s.Identifier.Data)
830
831 if s.Words != nil {
832 w.WriteString(" ")
833 s.Comments[0].printSource(w, true)
834 w.WriteString("in")
835
836 for _, wd := range s.Words {
837 w.WriteString(" ")
838 wd.printSource(w, v)
839 }
840 }
841
842 ip := w.Indent()
843
844 ip.WriteString("; ")
845 s.Comments[1].printSource(w, true)
846 ip.WriteString("do\n")
847 s.File.printSource(ip, v)
848 w.WriteString("\ndone")
849 }
850 }
851
852 func (s Statement) printSource(w writer, v bool) {
853 s.printSourceEnd(w, v, true)
854 }
855
856 func (s Statement) printSourceEnd(w writer, v, end bool) {
857 s.Pipeline.printSource(w, v)
858
859 if (s.LogicalOperator == LogicalOperatorAnd || s.LogicalOperator == LogicalOperatorOr) && s.Statement != nil {
860 w.WriteString(" ")
861 s.LogicalOperator.printSource(w, v)
862 w.WriteString(" ")
863 s.Statement.printSourceEnd(w, v, false)
864 }
865
866 if s.JobControl == JobControlBackground {
867 if v {
868 w.WriteString(" &")
869 } else {
870 w.WriteString("&")
871 }
872 } else if end && !s.endsWithGrouping() {
873 w.WriteString(";")
874 }
875 }
876
877 func (s Statement) endsWithGrouping() bool {
878 if s.Statement != nil {
879 return s.Statement.endsWithGrouping()
880 }
881
882 return s.Pipeline.endsWithGrouping()
883 }
884
885 func (s Statement) hasHeredoc() bool {
886 if s.Statement != nil && s.Statement.hasHeredoc() {
887 return true
888 }
889
890 return s.Pipeline.hasHeredoc()
891 }
892
893 func (s Statement) printHeredoc(w writer, v bool) {
894 s.Pipeline.printHeredoc(w, v)
895
896 if s.Statement != nil {
897 s.Statement.printHeredoc(w, v)
898 }
899 }
900
901 func (s String) printSource(w writer, v bool) {
902 for _, p := range s.WordsOrTokens {
903 p.printSource(w, v)
904 }
905 }
906
907 func (t TestCompound) printSource(w writer, v bool) {
908 w.WriteString("[[")
909
910 iw := w
911 multi := t.isMultiline(v)
912
913 if multi {
914 iw = w.Indent()
915 multi = true
916
917 if len(t.Comments[0]) > 0 {
918 w.WriteString(" ")
919 t.Comments[0].printSource(w, len(t.Tests.Comments[0]) > 0)
920 }
921
922 iw.WriteString("\n")
923 } else {
924 w.WriteString(" ")
925 }
926
927 t.Tests.printSource(iw, v)
928
929 if len(t.Comments[1]) > 0 {
930 if t.Tests.lastIsComment() {
931 w.WriteString("\n")
932 }
933
934 w.WriteString("\n")
935 t.Comments[1].printSource(w, true)
936 } else if multi {
937 w.WriteString("\n")
938 } else {
939 w.WriteString(" ")
940 }
941
942 w.WriteString("]]")
943 }
944
945 func (t Tests) printSource(w writer, v bool) {
946 t.Comments[0].printSource(w, true)
947
948 if t.Not {
949 w.WriteString("! ")
950
951 t.Comments[1].printSource(w, true)
952 }
953
954 if t.Parens != nil {
955 w.WriteString("(")
956
957 multi := t.Parens.isMultiline(v) || len(t.Comments[2]) > 0 || len(t.Comments[3]) > 0
958 iw := w
959
960 if multi {
961 iw = w.Indent()
962
963 if len(t.Comments[2]) > 0 {
964 w.WriteString(" ")
965 t.Comments[2].printSource(w, len(t.Parens.Comments[0]) > 0)
966 }
967
968 iw.WriteString("\n")
969 } else {
970 w.WriteString(" ")
971 }
972
973 t.Parens.printSource(iw, v)
974
975 if multi {
976 if len(t.Comments[3]) > 0 && len(t.Parens.Comments[4]) > 0 {
977 w.WriteString("\n\n")
978 } else {
979 w.WriteString("\n")
980 }
981 } else {
982 w.WriteString(" ")
983 }
984
985 t.Comments[3].printSource(w, true)
986 w.WriteString(")")
987 } else if t.Word != nil && t.Test == TestOperatorNone {
988 t.Word.printSource(w, v)
989 } else if t.Word != nil && t.Pattern != nil && t.Test >= TestOperatorStringsEqual {
990 t.Word.printSource(w, v)
991 w.WriteString(" ")
992 t.Test.printSource(w, v)
993 w.WriteString(" ")
994 t.Comments[2].printSource(w, true)
995 t.Pattern.printSource(w, v)
996 } else if t.Word != nil && t.Test >= TestOperatorFileExists && t.Test <= TestOperatorStringIsNonZero {
997 t.Test.printSource(w, v)
998 w.WriteString(" ")
999 t.Word.printSource(w, v)
1000 }
1001
1002 if t.Tests != nil && (t.LogicalOperator == LogicalOperatorOr || t.LogicalOperator == LogicalOperatorAnd) {
1003 w.WriteString(" ")
1004 t.Comments[4].printSource(w, true)
1005 t.LogicalOperator.printSource(w, v)
1006 w.WriteString(" ")
1007 t.Tests.printSource(w, v)
1008 } else if len(t.Comments[4]) > 0 {
1009 w.WriteString(" ")
1010 t.Comments[4].printSource(w, false)
1011 }
1012 }
1013
1014 func (t Tests) lastIsComment() bool {
1015 if t.Tests != nil {
1016 return t.Tests.lastIsComment()
1017 }
1018
1019 return len(t.Comments[4]) > 0
1020 }
1021
1022 func (t TestConsequence) printSource(w writer, v bool) {
1023 t.Test.printSource(w, v)
1024
1025 if t.Test.endsWithGrouping() {
1026 w.WriteString(";")
1027 }
1028
1029 ip := w.Indent()
1030
1031 if len(t.Comments) > 0 {
1032 w.WriteString(" ")
1033 t.Comments.printSource(w, true)
1034 ip.WriteString("then\n")
1035 } else {
1036 ip.WriteString(" then\n")
1037 }
1038
1039 t.Consequence.printSource(ip, v)
1040 }
1041
1042 func (ve Value) printSource(w writer, v bool) {
1043 if ve.Word != nil {
1044 ve.Word.printSource(w, v)
1045 } else if ve.Array != nil {
1046 iw := w
1047 ml := ve.isMultiline(v)
1048 lastHadComment := ml
1049
1050 if ml {
1051 iw = w.Indent()
1052 }
1053
1054 if len(ve.Comments[0]) > 0 {
1055 w.WriteString("(")
1056
1057 if len(ve.Comments[0]) > 0 {
1058 w.WriteString(" ")
1059 ve.Comments[0].printSource(w, len(ve.Array) > 0)
1060
1061 lastHadComment = true
1062 }
1063 } else {
1064 w.WriteString("(")
1065 }
1066
1067 if len(ve.Array) > 0 {
1068 lastHadComment = lastHadComment || len(ve.Array[0].Comments[0]) > 0
1069
1070 for _, word := range ve.Array {
1071 if lastHadComment {
1072 iw.WriteString("\n")
1073 } else if v && len(word.Comments[0]) == 0 {
1074 iw.WriteString(" ")
1075 }
1076
1077 word.printSource(iw, v)
1078
1079 lastHadComment = len(word.Comments[1]) != 0
1080 }
1081
1082 if v && !ml {
1083 iw.WriteString(" ")
1084 }
1085 }
1086
1087 if len(ve.Comments[1]) != 0 {
1088 w.WriteString("\n")
1089
1090 if lastHadComment {
1091 w.WriteString("\n")
1092 }
1093
1094 ve.Comments[1].printSource(w, false)
1095
1096 lastHadComment = true
1097 }
1098
1099 if lastHadComment || ml {
1100 w.WriteString("\n")
1101 }
1102
1103 w.WriteString(")")
1104 }
1105 }
1106
1107 func (wo WordOrOperator) printSource(w writer, v bool) {
1108 if wo.Operator != nil {
1109 w.WriteString(wo.Operator.Data)
1110 } else if wo.Word != nil {
1111 wo.Word.printSource(w, v)
1112 }
1113 }
1114
1115 func (wt WordOrToken) printSource(w writer, v bool) {
1116 if wt.Word != nil {
1117 wt.Word.printSource(w, v)
1118 } else if wt.Token != nil {
1119 w.WriteString(wt.Token.Data)
1120 }
1121 }
1122
1123 func (wp WordPart) printSource(w writer, v bool) {
1124 if wp.Part != nil {
1125 if len(wp.Part.Data) > 0 {
1126 w.WriteString(wp.Part.Data[:1])
1127 w.Underlying().WriteString(wp.Part.Data[1:])
1128 }
1129 } else if wp.ArithmeticExpansion != nil {
1130 wp.ArithmeticExpansion.printSource(w, v)
1131 } else if wp.CommandSubstitution != nil {
1132 wp.CommandSubstitution.printSource(w, v)
1133 } else if wp.ParameterExpansion != nil {
1134 wp.ParameterExpansion.printSource(w, v)
1135 } else if wp.BraceExpansion != nil {
1136 wp.BraceExpansion.printSource(w, v)
1137 }
1138 }
1139
1140 func (wd Word) printSource(w writer, v bool) {
1141 for _, word := range wd.Parts {
1142 word.printSource(w, v)
1143 }
1144 }
1145
1146 func (wd Word) firstPartIsProcessSubstitution() bool {
1147 return len(wd.Parts) > 0 && wd.Parts[0].CommandSubstitution != nil && (wd.Parts[0].CommandSubstitution.SubstitutionType == SubstitutionProcessInput || wd.Parts[0].CommandSubstitution.SubstitutionType == SubstitutionProcessOutput)
1148 }
1149