OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / github.com / hashicorp / hcl / hcl / printer / nodes.go
1 package printer
2
3 import (
4         "bytes"
5         "fmt"
6         "sort"
7
8         "github.com/hashicorp/hcl/hcl/ast"
9         "github.com/hashicorp/hcl/hcl/token"
10 )
11
12 const (
13         blank    = byte(' ')
14         newline  = byte('\n')
15         tab      = byte('\t')
16         infinity = 1 << 30 // offset or line
17 )
18
19 var (
20         unindent = []byte("\uE123") // in the private use space
21 )
22
23 type printer struct {
24         cfg  Config
25         prev token.Pos
26
27         comments           []*ast.CommentGroup // may be nil, contains all comments
28         standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node)
29
30         enableTrace bool
31         indentTrace int
32 }
33
34 type ByPosition []*ast.CommentGroup
35
36 func (b ByPosition) Len() int           { return len(b) }
37 func (b ByPosition) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
38 func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) }
39
40 // collectComments comments all standalone comments which are not lead or line
41 // comment
42 func (p *printer) collectComments(node ast.Node) {
43         // first collect all comments. This is already stored in
44         // ast.File.(comments)
45         ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
46                 switch t := nn.(type) {
47                 case *ast.File:
48                         p.comments = t.Comments
49                         return nn, false
50                 }
51                 return nn, true
52         })
53
54         standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0)
55         for _, c := range p.comments {
56                 standaloneComments[c.Pos()] = c
57         }
58
59         // next remove all lead and line comments from the overall comment map.
60         // This will give us comments which are standalone, comments which are not
61         // assigned to any kind of node.
62         ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
63                 switch t := nn.(type) {
64                 case *ast.LiteralType:
65                         if t.LeadComment != nil {
66                                 for _, comment := range t.LeadComment.List {
67                                         if _, ok := standaloneComments[comment.Pos()]; ok {
68                                                 delete(standaloneComments, comment.Pos())
69                                         }
70                                 }
71                         }
72
73                         if t.LineComment != nil {
74                                 for _, comment := range t.LineComment.List {
75                                         if _, ok := standaloneComments[comment.Pos()]; ok {
76                                                 delete(standaloneComments, comment.Pos())
77                                         }
78                                 }
79                         }
80                 case *ast.ObjectItem:
81                         if t.LeadComment != nil {
82                                 for _, comment := range t.LeadComment.List {
83                                         if _, ok := standaloneComments[comment.Pos()]; ok {
84                                                 delete(standaloneComments, comment.Pos())
85                                         }
86                                 }
87                         }
88
89                         if t.LineComment != nil {
90                                 for _, comment := range t.LineComment.List {
91                                         if _, ok := standaloneComments[comment.Pos()]; ok {
92                                                 delete(standaloneComments, comment.Pos())
93                                         }
94                                 }
95                         }
96                 }
97
98                 return nn, true
99         })
100
101         for _, c := range standaloneComments {
102                 p.standaloneComments = append(p.standaloneComments, c)
103         }
104
105         sort.Sort(ByPosition(p.standaloneComments))
106 }
107
108 // output prints creates b printable HCL output and returns it.
109 func (p *printer) output(n interface{}) []byte {
110         var buf bytes.Buffer
111
112         switch t := n.(type) {
113         case *ast.File:
114                 // File doesn't trace so we add the tracing here
115                 defer un(trace(p, "File"))
116                 return p.output(t.Node)
117         case *ast.ObjectList:
118                 defer un(trace(p, "ObjectList"))
119
120                 var index int
121                 for {
122                         // Determine the location of the next actual non-comment
123                         // item. If we're at the end, the next item is at "infinity"
124                         var nextItem token.Pos
125                         if index != len(t.Items) {
126                                 nextItem = t.Items[index].Pos()
127                         } else {
128                                 nextItem = token.Pos{Offset: infinity, Line: infinity}
129                         }
130
131                         // Go through the standalone comments in the file and print out
132                         // the comments that we should be for this object item.
133                         for _, c := range p.standaloneComments {
134                                 // Go through all the comments in the group. The group
135                                 // should be printed together, not separated by double newlines.
136                                 printed := false
137                                 newlinePrinted := false
138                                 for _, comment := range c.List {
139                                         // We only care about comments after the previous item
140                                         // we've printed so that comments are printed in the
141                                         // correct locations (between two objects for example).
142                                         // And before the next item.
143                                         if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
144                                                 // if we hit the end add newlines so we can print the comment
145                                                 // we don't do this if prev is invalid which means the
146                                                 // beginning of the file since the first comment should
147                                                 // be at the first line.
148                                                 if !newlinePrinted && p.prev.IsValid() && index == len(t.Items) {
149                                                         buf.Write([]byte{newline, newline})
150                                                         newlinePrinted = true
151                                                 }
152
153                                                 // Write the actual comment.
154                                                 buf.WriteString(comment.Text)
155                                                 buf.WriteByte(newline)
156
157                                                 // Set printed to true to note that we printed something
158                                                 printed = true
159                                         }
160                                 }
161
162                                 // If we're not at the last item, write a new line so
163                                 // that there is a newline separating this comment from
164                                 // the next object.
165                                 if printed && index != len(t.Items) {
166                                         buf.WriteByte(newline)
167                                 }
168                         }
169
170                         if index == len(t.Items) {
171                                 break
172                         }
173
174                         buf.Write(p.output(t.Items[index]))
175                         if index != len(t.Items)-1 {
176                                 // Always write a newline to separate us from the next item
177                                 buf.WriteByte(newline)
178
179                                 // Need to determine if we're going to separate the next item
180                                 // with a blank line. The logic here is simple, though there
181                                 // are a few conditions:
182                                 //
183                                 //   1. The next object is more than one line away anyways,
184                                 //      so we need an empty line.
185                                 //
186                                 //   2. The next object is not a "single line" object, so
187                                 //      we need an empty line.
188                                 //
189                                 //   3. This current object is not a single line object,
190                                 //      so we need an empty line.
191                                 current := t.Items[index]
192                                 next := t.Items[index+1]
193                                 if next.Pos().Line != t.Items[index].Pos().Line+1 ||
194                                         !p.isSingleLineObject(next) ||
195                                         !p.isSingleLineObject(current) {
196                                         buf.WriteByte(newline)
197                                 }
198                         }
199                         index++
200                 }
201         case *ast.ObjectKey:
202                 buf.WriteString(t.Token.Text)
203         case *ast.ObjectItem:
204                 p.prev = t.Pos()
205                 buf.Write(p.objectItem(t))
206         case *ast.LiteralType:
207                 buf.Write(p.literalType(t))
208         case *ast.ListType:
209                 buf.Write(p.list(t))
210         case *ast.ObjectType:
211                 buf.Write(p.objectType(t))
212         default:
213                 fmt.Printf(" unknown type: %T\n", n)
214         }
215
216         return buf.Bytes()
217 }
218
219 func (p *printer) literalType(lit *ast.LiteralType) []byte {
220         result := []byte(lit.Token.Text)
221         switch lit.Token.Type {
222         case token.HEREDOC:
223                 // Clear the trailing newline from heredocs
224                 if result[len(result)-1] == '\n' {
225                         result = result[:len(result)-1]
226                 }
227
228                 // Poison lines 2+ so that we don't indent them
229                 result = p.heredocIndent(result)
230         case token.STRING:
231                 // If this is a multiline string, poison lines 2+ so we don't
232                 // indent them.
233                 if bytes.IndexRune(result, '\n') >= 0 {
234                         result = p.heredocIndent(result)
235                 }
236         }
237
238         return result
239 }
240
241 // objectItem returns the printable HCL form of an object item. An object type
242 // starts with one/multiple keys and has a value. The value might be of any
243 // type.
244 func (p *printer) objectItem(o *ast.ObjectItem) []byte {
245         defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))
246         var buf bytes.Buffer
247
248         if o.LeadComment != nil {
249                 for _, comment := range o.LeadComment.List {
250                         buf.WriteString(comment.Text)
251                         buf.WriteByte(newline)
252                 }
253         }
254
255         for i, k := range o.Keys {
256                 buf.WriteString(k.Token.Text)
257                 buf.WriteByte(blank)
258
259                 // reach end of key
260                 if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 {
261                         buf.WriteString("=")
262                         buf.WriteByte(blank)
263                 }
264         }
265
266         buf.Write(p.output(o.Val))
267
268         if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil {
269                 buf.WriteByte(blank)
270                 for _, comment := range o.LineComment.List {
271                         buf.WriteString(comment.Text)
272                 }
273         }
274
275         return buf.Bytes()
276 }
277
278 // objectType returns the printable HCL form of an object type. An object type
279 // begins with a brace and ends with a brace.
280 func (p *printer) objectType(o *ast.ObjectType) []byte {
281         defer un(trace(p, "ObjectType"))
282         var buf bytes.Buffer
283         buf.WriteString("{")
284
285         var index int
286         var nextItem token.Pos
287         var commented, newlinePrinted bool
288         for {
289                 // Determine the location of the next actual non-comment
290                 // item. If we're at the end, the next item is the closing brace
291                 if index != len(o.List.Items) {
292                         nextItem = o.List.Items[index].Pos()
293                 } else {
294                         nextItem = o.Rbrace
295                 }
296
297                 // Go through the standalone comments in the file and print out
298                 // the comments that we should be for this object item.
299                 for _, c := range p.standaloneComments {
300                         printed := false
301                         var lastCommentPos token.Pos
302                         for _, comment := range c.List {
303                                 // We only care about comments after the previous item
304                                 // we've printed so that comments are printed in the
305                                 // correct locations (between two objects for example).
306                                 // And before the next item.
307                                 if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
308                                         // If there are standalone comments and the initial newline has not
309                                         // been printed yet, do it now.
310                                         if !newlinePrinted {
311                                                 newlinePrinted = true
312                                                 buf.WriteByte(newline)
313                                         }
314
315                                         // add newline if it's between other printed nodes
316                                         if index > 0 {
317                                                 commented = true
318                                                 buf.WriteByte(newline)
319                                         }
320
321                                         // Store this position
322                                         lastCommentPos = comment.Pos()
323
324                                         // output the comment itself
325                                         buf.Write(p.indent(p.heredocIndent([]byte(comment.Text))))
326
327                                         // Set printed to true to note that we printed something
328                                         printed = true
329
330                                         /*
331                                                 if index != len(o.List.Items) {
332                                                         buf.WriteByte(newline) // do not print on the end
333                                                 }
334                                         */
335                                 }
336                         }
337
338                         // Stuff to do if we had comments
339                         if printed {
340                                 // Always write a newline
341                                 buf.WriteByte(newline)
342
343                                 // If there is another item in the object and our comment
344                                 // didn't hug it directly, then make sure there is a blank
345                                 // line separating them.
346                                 if nextItem != o.Rbrace && nextItem.Line != lastCommentPos.Line+1 {
347                                         buf.WriteByte(newline)
348                                 }
349                         }
350                 }
351
352                 if index == len(o.List.Items) {
353                         p.prev = o.Rbrace
354                         break
355                 }
356
357                 // At this point we are sure that it's not a totally empty block: print
358                 // the initial newline if it hasn't been printed yet by the previous
359                 // block about standalone comments.
360                 if !newlinePrinted {
361                         buf.WriteByte(newline)
362                         newlinePrinted = true
363                 }
364
365                 // check if we have adjacent one liner items. If yes we'll going to align
366                 // the comments.
367                 var aligned []*ast.ObjectItem
368                 for _, item := range o.List.Items[index:] {
369                         // we don't group one line lists
370                         if len(o.List.Items) == 1 {
371                                 break
372                         }
373
374                         // one means a oneliner with out any lead comment
375                         // two means a oneliner with lead comment
376                         // anything else might be something else
377                         cur := lines(string(p.objectItem(item)))
378                         if cur > 2 {
379                                 break
380                         }
381
382                         curPos := item.Pos()
383
384                         nextPos := token.Pos{}
385                         if index != len(o.List.Items)-1 {
386                                 nextPos = o.List.Items[index+1].Pos()
387                         }
388
389                         prevPos := token.Pos{}
390                         if index != 0 {
391                                 prevPos = o.List.Items[index-1].Pos()
392                         }
393
394                         // fmt.Println("DEBUG ----------------")
395                         // fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
396                         // fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
397                         // fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)
398
399                         if curPos.Line+1 == nextPos.Line {
400                                 aligned = append(aligned, item)
401                                 index++
402                                 continue
403                         }
404
405                         if curPos.Line-1 == prevPos.Line {
406                                 aligned = append(aligned, item)
407                                 index++
408
409                                 // finish if we have a new line or comment next. This happens
410                                 // if the next item is not adjacent
411                                 if curPos.Line+1 != nextPos.Line {
412                                         break
413                                 }
414                                 continue
415                         }
416
417                         break
418                 }
419
420                 // put newlines if the items are between other non aligned items.
421                 // newlines are also added if there is a standalone comment already, so
422                 // check it too
423                 if !commented && index != len(aligned) {
424                         buf.WriteByte(newline)
425                 }
426
427                 if len(aligned) >= 1 {
428                         p.prev = aligned[len(aligned)-1].Pos()
429
430                         items := p.alignedItems(aligned)
431                         buf.Write(p.indent(items))
432                 } else {
433                         p.prev = o.List.Items[index].Pos()
434
435                         buf.Write(p.indent(p.objectItem(o.List.Items[index])))
436                         index++
437                 }
438
439                 buf.WriteByte(newline)
440         }
441
442         buf.WriteString("}")
443         return buf.Bytes()
444 }
445
446 func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
447         var buf bytes.Buffer
448
449         // find the longest key and value length, needed for alignment
450         var longestKeyLen int // longest key length
451         var longestValLen int // longest value length
452         for _, item := range items {
453                 key := len(item.Keys[0].Token.Text)
454                 val := len(p.output(item.Val))
455
456                 if key > longestKeyLen {
457                         longestKeyLen = key
458                 }
459
460                 if val > longestValLen {
461                         longestValLen = val
462                 }
463         }
464
465         for i, item := range items {
466                 if item.LeadComment != nil {
467                         for _, comment := range item.LeadComment.List {
468                                 buf.WriteString(comment.Text)
469                                 buf.WriteByte(newline)
470                         }
471                 }
472
473                 for i, k := range item.Keys {
474                         keyLen := len(k.Token.Text)
475                         buf.WriteString(k.Token.Text)
476                         for i := 0; i < longestKeyLen-keyLen+1; i++ {
477                                 buf.WriteByte(blank)
478                         }
479
480                         // reach end of key
481                         if i == len(item.Keys)-1 && len(item.Keys) == 1 {
482                                 buf.WriteString("=")
483                                 buf.WriteByte(blank)
484                         }
485                 }
486
487                 val := p.output(item.Val)
488                 valLen := len(val)
489                 buf.Write(val)
490
491                 if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil {
492                         for i := 0; i < longestValLen-valLen+1; i++ {
493                                 buf.WriteByte(blank)
494                         }
495
496                         for _, comment := range item.LineComment.List {
497                                 buf.WriteString(comment.Text)
498                         }
499                 }
500
501                 // do not print for the last item
502                 if i != len(items)-1 {
503                         buf.WriteByte(newline)
504                 }
505         }
506
507         return buf.Bytes()
508 }
509
510 // list returns the printable HCL form of an list type.
511 func (p *printer) list(l *ast.ListType) []byte {
512         var buf bytes.Buffer
513         buf.WriteString("[")
514
515         var longestLine int
516         for _, item := range l.List {
517                 // for now we assume that the list only contains literal types
518                 if lit, ok := item.(*ast.LiteralType); ok {
519                         lineLen := len(lit.Token.Text)
520                         if lineLen > longestLine {
521                                 longestLine = lineLen
522                         }
523                 }
524         }
525
526         insertSpaceBeforeItem := false
527         lastHadLeadComment := false
528         for i, item := range l.List {
529                 // Keep track of whether this item is a heredoc since that has
530                 // unique behavior.
531                 heredoc := false
532                 if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
533                         heredoc = true
534                 }
535
536                 if item.Pos().Line != l.Lbrack.Line {
537                         // multiline list, add newline before we add each item
538                         buf.WriteByte(newline)
539                         insertSpaceBeforeItem = false
540
541                         // If we have a lead comment, then we want to write that first
542                         leadComment := false
543                         if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
544                                 leadComment = true
545
546                                 // If this isn't the first item and the previous element
547                                 // didn't have a lead comment, then we need to add an extra
548                                 // newline to properly space things out. If it did have a
549                                 // lead comment previously then this would be done
550                                 // automatically.
551                                 if i > 0 && !lastHadLeadComment {
552                                         buf.WriteByte(newline)
553                                 }
554
555                                 for _, comment := range lit.LeadComment.List {
556                                         buf.Write(p.indent([]byte(comment.Text)))
557                                         buf.WriteByte(newline)
558                                 }
559                         }
560
561                         // also indent each line
562                         val := p.output(item)
563                         curLen := len(val)
564                         buf.Write(p.indent(val))
565
566                         // if this item is a heredoc, then we output the comma on
567                         // the next line. This is the only case this happens.
568                         comma := []byte{','}
569                         if heredoc {
570                                 buf.WriteByte(newline)
571                                 comma = p.indent(comma)
572                         }
573
574                         buf.Write(comma)
575
576                         if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
577                                 // if the next item doesn't have any comments, do not align
578                                 buf.WriteByte(blank) // align one space
579                                 for i := 0; i < longestLine-curLen; i++ {
580                                         buf.WriteByte(blank)
581                                 }
582
583                                 for _, comment := range lit.LineComment.List {
584                                         buf.WriteString(comment.Text)
585                                 }
586                         }
587
588                         lastItem := i == len(l.List)-1
589                         if lastItem {
590                                 buf.WriteByte(newline)
591                         }
592
593                         if leadComment && !lastItem {
594                                 buf.WriteByte(newline)
595                         }
596
597                         lastHadLeadComment = leadComment
598                 } else {
599                         if insertSpaceBeforeItem {
600                                 buf.WriteByte(blank)
601                                 insertSpaceBeforeItem = false
602                         }
603
604                         // Output the item itself
605                         // also indent each line
606                         val := p.output(item)
607                         curLen := len(val)
608                         buf.Write(val)
609
610                         // If this is a heredoc item we always have to output a newline
611                         // so that it parses properly.
612                         if heredoc {
613                                 buf.WriteByte(newline)
614                         }
615
616                         // If this isn't the last element, write a comma.
617                         if i != len(l.List)-1 {
618                                 buf.WriteString(",")
619                                 insertSpaceBeforeItem = true
620                         }
621
622                         if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
623                                 // if the next item doesn't have any comments, do not align
624                                 buf.WriteByte(blank) // align one space
625                                 for i := 0; i < longestLine-curLen; i++ {
626                                         buf.WriteByte(blank)
627                                 }
628
629                                 for _, comment := range lit.LineComment.List {
630                                         buf.WriteString(comment.Text)
631                                 }
632                         }
633                 }
634
635         }
636
637         buf.WriteString("]")
638         return buf.Bytes()
639 }
640
641 // indent indents the lines of the given buffer for each non-empty line
642 func (p *printer) indent(buf []byte) []byte {
643         var prefix []byte
644         if p.cfg.SpacesWidth != 0 {
645                 for i := 0; i < p.cfg.SpacesWidth; i++ {
646                         prefix = append(prefix, blank)
647                 }
648         } else {
649                 prefix = []byte{tab}
650         }
651
652         var res []byte
653         bol := true
654         for _, c := range buf {
655                 if bol && c != '\n' {
656                         res = append(res, prefix...)
657                 }
658
659                 res = append(res, c)
660                 bol = c == '\n'
661         }
662         return res
663 }
664
665 // unindent removes all the indentation from the tombstoned lines
666 func (p *printer) unindent(buf []byte) []byte {
667         var res []byte
668         for i := 0; i < len(buf); i++ {
669                 skip := len(buf)-i <= len(unindent)
670                 if !skip {
671                         skip = !bytes.Equal(unindent, buf[i:i+len(unindent)])
672                 }
673                 if skip {
674                         res = append(res, buf[i])
675                         continue
676                 }
677
678                 // We have a marker. we have to backtrace here and clean out
679                 // any whitespace ahead of our tombstone up to a \n
680                 for j := len(res) - 1; j >= 0; j-- {
681                         if res[j] == '\n' {
682                                 break
683                         }
684
685                         res = res[:j]
686                 }
687
688                 // Skip the entire unindent marker
689                 i += len(unindent) - 1
690         }
691
692         return res
693 }
694
695 // heredocIndent marks all the 2nd and further lines as unindentable
696 func (p *printer) heredocIndent(buf []byte) []byte {
697         var res []byte
698         bol := false
699         for _, c := range buf {
700                 if bol && c != '\n' {
701                         res = append(res, unindent...)
702                 }
703                 res = append(res, c)
704                 bol = c == '\n'
705         }
706         return res
707 }
708
709 // isSingleLineObject tells whether the given object item is a single
710 // line object such as "obj {}".
711 //
712 // A single line object:
713 //
714 //   * has no lead comments (hence multi-line)
715 //   * has no assignment
716 //   * has no values in the stanza (within {})
717 //
718 func (p *printer) isSingleLineObject(val *ast.ObjectItem) bool {
719         // If there is a lead comment, can't be one line
720         if val.LeadComment != nil {
721                 return false
722         }
723
724         // If there is assignment, we always break by line
725         if val.Assign.IsValid() {
726                 return false
727         }
728
729         // If it isn't an object type, then its not a single line object
730         ot, ok := val.Val.(*ast.ObjectType)
731         if !ok {
732                 return false
733         }
734
735         // If the object has no items, it is single line!
736         return len(ot.List.Items) == 0
737 }
738
739 func lines(txt string) int {
740         endline := 1
741         for i := 0; i < len(txt); i++ {
742                 if txt[i] == '\n' {
743                         endline++
744                 }
745         }
746         return endline
747 }
748
749 // ----------------------------------------------------------------------------
750 // Tracing support
751
752 func (p *printer) printTrace(a ...interface{}) {
753         if !p.enableTrace {
754                 return
755         }
756
757         const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
758         const n = len(dots)
759         i := 2 * p.indentTrace
760         for i > n {
761                 fmt.Print(dots)
762                 i -= n
763         }
764         // i <= n
765         fmt.Print(dots[0:i])
766         fmt.Println(a...)
767 }
768
769 func trace(p *printer, msg string) *printer {
770         p.printTrace(msg, "(")
771         p.indentTrace++
772         return p
773 }
774
775 // Usage pattern: defer un(trace(p, "..."))
776 func un(p *printer) {
777         p.indentTrace--
778         p.printTrace(")")
779 }