OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / github.com / hashicorp / hcl / hcl / printer / nodes.go
diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go
new file mode 100644 (file)
index 0000000..c896d58
--- /dev/null
@@ -0,0 +1,779 @@
+package printer
+
+import (
+       "bytes"
+       "fmt"
+       "sort"
+
+       "github.com/hashicorp/hcl/hcl/ast"
+       "github.com/hashicorp/hcl/hcl/token"
+)
+
+const (
+       blank    = byte(' ')
+       newline  = byte('\n')
+       tab      = byte('\t')
+       infinity = 1 << 30 // offset or line
+)
+
+var (
+       unindent = []byte("\uE123") // in the private use space
+)
+
+type printer struct {
+       cfg  Config
+       prev token.Pos
+
+       comments           []*ast.CommentGroup // may be nil, contains all comments
+       standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node)
+
+       enableTrace bool
+       indentTrace int
+}
+
+type ByPosition []*ast.CommentGroup
+
+func (b ByPosition) Len() int           { return len(b) }
+func (b ByPosition) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
+func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) }
+
+// collectComments comments all standalone comments which are not lead or line
+// comment
+func (p *printer) collectComments(node ast.Node) {
+       // first collect all comments. This is already stored in
+       // ast.File.(comments)
+       ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
+               switch t := nn.(type) {
+               case *ast.File:
+                       p.comments = t.Comments
+                       return nn, false
+               }
+               return nn, true
+       })
+
+       standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0)
+       for _, c := range p.comments {
+               standaloneComments[c.Pos()] = c
+       }
+
+       // next remove all lead and line comments from the overall comment map.
+       // This will give us comments which are standalone, comments which are not
+       // assigned to any kind of node.
+       ast.Walk(node, func(nn ast.Node) (ast.Node, bool) {
+               switch t := nn.(type) {
+               case *ast.LiteralType:
+                       if t.LeadComment != nil {
+                               for _, comment := range t.LeadComment.List {
+                                       if _, ok := standaloneComments[comment.Pos()]; ok {
+                                               delete(standaloneComments, comment.Pos())
+                                       }
+                               }
+                       }
+
+                       if t.LineComment != nil {
+                               for _, comment := range t.LineComment.List {
+                                       if _, ok := standaloneComments[comment.Pos()]; ok {
+                                               delete(standaloneComments, comment.Pos())
+                                       }
+                               }
+                       }
+               case *ast.ObjectItem:
+                       if t.LeadComment != nil {
+                               for _, comment := range t.LeadComment.List {
+                                       if _, ok := standaloneComments[comment.Pos()]; ok {
+                                               delete(standaloneComments, comment.Pos())
+                                       }
+                               }
+                       }
+
+                       if t.LineComment != nil {
+                               for _, comment := range t.LineComment.List {
+                                       if _, ok := standaloneComments[comment.Pos()]; ok {
+                                               delete(standaloneComments, comment.Pos())
+                                       }
+                               }
+                       }
+               }
+
+               return nn, true
+       })
+
+       for _, c := range standaloneComments {
+               p.standaloneComments = append(p.standaloneComments, c)
+       }
+
+       sort.Sort(ByPosition(p.standaloneComments))
+}
+
+// output prints creates b printable HCL output and returns it.
+func (p *printer) output(n interface{}) []byte {
+       var buf bytes.Buffer
+
+       switch t := n.(type) {
+       case *ast.File:
+               // File doesn't trace so we add the tracing here
+               defer un(trace(p, "File"))
+               return p.output(t.Node)
+       case *ast.ObjectList:
+               defer un(trace(p, "ObjectList"))
+
+               var index int
+               for {
+                       // Determine the location of the next actual non-comment
+                       // item. If we're at the end, the next item is at "infinity"
+                       var nextItem token.Pos
+                       if index != len(t.Items) {
+                               nextItem = t.Items[index].Pos()
+                       } else {
+                               nextItem = token.Pos{Offset: infinity, Line: infinity}
+                       }
+
+                       // Go through the standalone comments in the file and print out
+                       // the comments that we should be for this object item.
+                       for _, c := range p.standaloneComments {
+                               // Go through all the comments in the group. The group
+                               // should be printed together, not separated by double newlines.
+                               printed := false
+                               newlinePrinted := false
+                               for _, comment := range c.List {
+                                       // We only care about comments after the previous item
+                                       // we've printed so that comments are printed in the
+                                       // correct locations (between two objects for example).
+                                       // And before the next item.
+                                       if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
+                                               // if we hit the end add newlines so we can print the comment
+                                               // we don't do this if prev is invalid which means the
+                                               // beginning of the file since the first comment should
+                                               // be at the first line.
+                                               if !newlinePrinted && p.prev.IsValid() && index == len(t.Items) {
+                                                       buf.Write([]byte{newline, newline})
+                                                       newlinePrinted = true
+                                               }
+
+                                               // Write the actual comment.
+                                               buf.WriteString(comment.Text)
+                                               buf.WriteByte(newline)
+
+                                               // Set printed to true to note that we printed something
+                                               printed = true
+                                       }
+                               }
+
+                               // If we're not at the last item, write a new line so
+                               // that there is a newline separating this comment from
+                               // the next object.
+                               if printed && index != len(t.Items) {
+                                       buf.WriteByte(newline)
+                               }
+                       }
+
+                       if index == len(t.Items) {
+                               break
+                       }
+
+                       buf.Write(p.output(t.Items[index]))
+                       if index != len(t.Items)-1 {
+                               // Always write a newline to separate us from the next item
+                               buf.WriteByte(newline)
+
+                               // Need to determine if we're going to separate the next item
+                               // with a blank line. The logic here is simple, though there
+                               // are a few conditions:
+                               //
+                               //   1. The next object is more than one line away anyways,
+                               //      so we need an empty line.
+                               //
+                               //   2. The next object is not a "single line" object, so
+                               //      we need an empty line.
+                               //
+                               //   3. This current object is not a single line object,
+                               //      so we need an empty line.
+                               current := t.Items[index]
+                               next := t.Items[index+1]
+                               if next.Pos().Line != t.Items[index].Pos().Line+1 ||
+                                       !p.isSingleLineObject(next) ||
+                                       !p.isSingleLineObject(current) {
+                                       buf.WriteByte(newline)
+                               }
+                       }
+                       index++
+               }
+       case *ast.ObjectKey:
+               buf.WriteString(t.Token.Text)
+       case *ast.ObjectItem:
+               p.prev = t.Pos()
+               buf.Write(p.objectItem(t))
+       case *ast.LiteralType:
+               buf.Write(p.literalType(t))
+       case *ast.ListType:
+               buf.Write(p.list(t))
+       case *ast.ObjectType:
+               buf.Write(p.objectType(t))
+       default:
+               fmt.Printf(" unknown type: %T\n", n)
+       }
+
+       return buf.Bytes()
+}
+
+func (p *printer) literalType(lit *ast.LiteralType) []byte {
+       result := []byte(lit.Token.Text)
+       switch lit.Token.Type {
+       case token.HEREDOC:
+               // Clear the trailing newline from heredocs
+               if result[len(result)-1] == '\n' {
+                       result = result[:len(result)-1]
+               }
+
+               // Poison lines 2+ so that we don't indent them
+               result = p.heredocIndent(result)
+       case token.STRING:
+               // If this is a multiline string, poison lines 2+ so we don't
+               // indent them.
+               if bytes.IndexRune(result, '\n') >= 0 {
+                       result = p.heredocIndent(result)
+               }
+       }
+
+       return result
+}
+
+// objectItem returns the printable HCL form of an object item. An object type
+// starts with one/multiple keys and has a value. The value might be of any
+// type.
+func (p *printer) objectItem(o *ast.ObjectItem) []byte {
+       defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))
+       var buf bytes.Buffer
+
+       if o.LeadComment != nil {
+               for _, comment := range o.LeadComment.List {
+                       buf.WriteString(comment.Text)
+                       buf.WriteByte(newline)
+               }
+       }
+
+       for i, k := range o.Keys {
+               buf.WriteString(k.Token.Text)
+               buf.WriteByte(blank)
+
+               // reach end of key
+               if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 {
+                       buf.WriteString("=")
+                       buf.WriteByte(blank)
+               }
+       }
+
+       buf.Write(p.output(o.Val))
+
+       if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil {
+               buf.WriteByte(blank)
+               for _, comment := range o.LineComment.List {
+                       buf.WriteString(comment.Text)
+               }
+       }
+
+       return buf.Bytes()
+}
+
+// objectType returns the printable HCL form of an object type. An object type
+// begins with a brace and ends with a brace.
+func (p *printer) objectType(o *ast.ObjectType) []byte {
+       defer un(trace(p, "ObjectType"))
+       var buf bytes.Buffer
+       buf.WriteString("{")
+
+       var index int
+       var nextItem token.Pos
+       var commented, newlinePrinted bool
+       for {
+               // Determine the location of the next actual non-comment
+               // item. If we're at the end, the next item is the closing brace
+               if index != len(o.List.Items) {
+                       nextItem = o.List.Items[index].Pos()
+               } else {
+                       nextItem = o.Rbrace
+               }
+
+               // Go through the standalone comments in the file and print out
+               // the comments that we should be for this object item.
+               for _, c := range p.standaloneComments {
+                       printed := false
+                       var lastCommentPos token.Pos
+                       for _, comment := range c.List {
+                               // We only care about comments after the previous item
+                               // we've printed so that comments are printed in the
+                               // correct locations (between two objects for example).
+                               // And before the next item.
+                               if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
+                                       // If there are standalone comments and the initial newline has not
+                                       // been printed yet, do it now.
+                                       if !newlinePrinted {
+                                               newlinePrinted = true
+                                               buf.WriteByte(newline)
+                                       }
+
+                                       // add newline if it's between other printed nodes
+                                       if index > 0 {
+                                               commented = true
+                                               buf.WriteByte(newline)
+                                       }
+
+                                       // Store this position
+                                       lastCommentPos = comment.Pos()
+
+                                       // output the comment itself
+                                       buf.Write(p.indent(p.heredocIndent([]byte(comment.Text))))
+
+                                       // Set printed to true to note that we printed something
+                                       printed = true
+
+                                       /*
+                                               if index != len(o.List.Items) {
+                                                       buf.WriteByte(newline) // do not print on the end
+                                               }
+                                       */
+                               }
+                       }
+
+                       // Stuff to do if we had comments
+                       if printed {
+                               // Always write a newline
+                               buf.WriteByte(newline)
+
+                               // If there is another item in the object and our comment
+                               // didn't hug it directly, then make sure there is a blank
+                               // line separating them.
+                               if nextItem != o.Rbrace && nextItem.Line != lastCommentPos.Line+1 {
+                                       buf.WriteByte(newline)
+                               }
+                       }
+               }
+
+               if index == len(o.List.Items) {
+                       p.prev = o.Rbrace
+                       break
+               }
+
+               // At this point we are sure that it's not a totally empty block: print
+               // the initial newline if it hasn't been printed yet by the previous
+               // block about standalone comments.
+               if !newlinePrinted {
+                       buf.WriteByte(newline)
+                       newlinePrinted = true
+               }
+
+               // check if we have adjacent one liner items. If yes we'll going to align
+               // the comments.
+               var aligned []*ast.ObjectItem
+               for _, item := range o.List.Items[index:] {
+                       // we don't group one line lists
+                       if len(o.List.Items) == 1 {
+                               break
+                       }
+
+                       // one means a oneliner with out any lead comment
+                       // two means a oneliner with lead comment
+                       // anything else might be something else
+                       cur := lines(string(p.objectItem(item)))
+                       if cur > 2 {
+                               break
+                       }
+
+                       curPos := item.Pos()
+
+                       nextPos := token.Pos{}
+                       if index != len(o.List.Items)-1 {
+                               nextPos = o.List.Items[index+1].Pos()
+                       }
+
+                       prevPos := token.Pos{}
+                       if index != 0 {
+                               prevPos = o.List.Items[index-1].Pos()
+                       }
+
+                       // fmt.Println("DEBUG ----------------")
+                       // fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
+                       // fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
+                       // fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)
+
+                       if curPos.Line+1 == nextPos.Line {
+                               aligned = append(aligned, item)
+                               index++
+                               continue
+                       }
+
+                       if curPos.Line-1 == prevPos.Line {
+                               aligned = append(aligned, item)
+                               index++
+
+                               // finish if we have a new line or comment next. This happens
+                               // if the next item is not adjacent
+                               if curPos.Line+1 != nextPos.Line {
+                                       break
+                               }
+                               continue
+                       }
+
+                       break
+               }
+
+               // put newlines if the items are between other non aligned items.
+               // newlines are also added if there is a standalone comment already, so
+               // check it too
+               if !commented && index != len(aligned) {
+                       buf.WriteByte(newline)
+               }
+
+               if len(aligned) >= 1 {
+                       p.prev = aligned[len(aligned)-1].Pos()
+
+                       items := p.alignedItems(aligned)
+                       buf.Write(p.indent(items))
+               } else {
+                       p.prev = o.List.Items[index].Pos()
+
+                       buf.Write(p.indent(p.objectItem(o.List.Items[index])))
+                       index++
+               }
+
+               buf.WriteByte(newline)
+       }
+
+       buf.WriteString("}")
+       return buf.Bytes()
+}
+
+func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
+       var buf bytes.Buffer
+
+       // find the longest key and value length, needed for alignment
+       var longestKeyLen int // longest key length
+       var longestValLen int // longest value length
+       for _, item := range items {
+               key := len(item.Keys[0].Token.Text)
+               val := len(p.output(item.Val))
+
+               if key > longestKeyLen {
+                       longestKeyLen = key
+               }
+
+               if val > longestValLen {
+                       longestValLen = val
+               }
+       }
+
+       for i, item := range items {
+               if item.LeadComment != nil {
+                       for _, comment := range item.LeadComment.List {
+                               buf.WriteString(comment.Text)
+                               buf.WriteByte(newline)
+                       }
+               }
+
+               for i, k := range item.Keys {
+                       keyLen := len(k.Token.Text)
+                       buf.WriteString(k.Token.Text)
+                       for i := 0; i < longestKeyLen-keyLen+1; i++ {
+                               buf.WriteByte(blank)
+                       }
+
+                       // reach end of key
+                       if i == len(item.Keys)-1 && len(item.Keys) == 1 {
+                               buf.WriteString("=")
+                               buf.WriteByte(blank)
+                       }
+               }
+
+               val := p.output(item.Val)
+               valLen := len(val)
+               buf.Write(val)
+
+               if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil {
+                       for i := 0; i < longestValLen-valLen+1; i++ {
+                               buf.WriteByte(blank)
+                       }
+
+                       for _, comment := range item.LineComment.List {
+                               buf.WriteString(comment.Text)
+                       }
+               }
+
+               // do not print for the last item
+               if i != len(items)-1 {
+                       buf.WriteByte(newline)
+               }
+       }
+
+       return buf.Bytes()
+}
+
+// list returns the printable HCL form of an list type.
+func (p *printer) list(l *ast.ListType) []byte {
+       var buf bytes.Buffer
+       buf.WriteString("[")
+
+       var longestLine int
+       for _, item := range l.List {
+               // for now we assume that the list only contains literal types
+               if lit, ok := item.(*ast.LiteralType); ok {
+                       lineLen := len(lit.Token.Text)
+                       if lineLen > longestLine {
+                               longestLine = lineLen
+                       }
+               }
+       }
+
+       insertSpaceBeforeItem := false
+       lastHadLeadComment := false
+       for i, item := range l.List {
+               // Keep track of whether this item is a heredoc since that has
+               // unique behavior.
+               heredoc := false
+               if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
+                       heredoc = true
+               }
+
+               if item.Pos().Line != l.Lbrack.Line {
+                       // multiline list, add newline before we add each item
+                       buf.WriteByte(newline)
+                       insertSpaceBeforeItem = false
+
+                       // If we have a lead comment, then we want to write that first
+                       leadComment := false
+                       if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
+                               leadComment = true
+
+                               // If this isn't the first item and the previous element
+                               // didn't have a lead comment, then we need to add an extra
+                               // newline to properly space things out. If it did have a
+                               // lead comment previously then this would be done
+                               // automatically.
+                               if i > 0 && !lastHadLeadComment {
+                                       buf.WriteByte(newline)
+                               }
+
+                               for _, comment := range lit.LeadComment.List {
+                                       buf.Write(p.indent([]byte(comment.Text)))
+                                       buf.WriteByte(newline)
+                               }
+                       }
+
+                       // also indent each line
+                       val := p.output(item)
+                       curLen := len(val)
+                       buf.Write(p.indent(val))
+
+                       // if this item is a heredoc, then we output the comma on
+                       // the next line. This is the only case this happens.
+                       comma := []byte{','}
+                       if heredoc {
+                               buf.WriteByte(newline)
+                               comma = p.indent(comma)
+                       }
+
+                       buf.Write(comma)
+
+                       if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
+                               // if the next item doesn't have any comments, do not align
+                               buf.WriteByte(blank) // align one space
+                               for i := 0; i < longestLine-curLen; i++ {
+                                       buf.WriteByte(blank)
+                               }
+
+                               for _, comment := range lit.LineComment.List {
+                                       buf.WriteString(comment.Text)
+                               }
+                       }
+
+                       lastItem := i == len(l.List)-1
+                       if lastItem {
+                               buf.WriteByte(newline)
+                       }
+
+                       if leadComment && !lastItem {
+                               buf.WriteByte(newline)
+                       }
+
+                       lastHadLeadComment = leadComment
+               } else {
+                       if insertSpaceBeforeItem {
+                               buf.WriteByte(blank)
+                               insertSpaceBeforeItem = false
+                       }
+
+                       // Output the item itself
+                       // also indent each line
+                       val := p.output(item)
+                       curLen := len(val)
+                       buf.Write(val)
+
+                       // If this is a heredoc item we always have to output a newline
+                       // so that it parses properly.
+                       if heredoc {
+                               buf.WriteByte(newline)
+                       }
+
+                       // If this isn't the last element, write a comma.
+                       if i != len(l.List)-1 {
+                               buf.WriteString(",")
+                               insertSpaceBeforeItem = true
+                       }
+
+                       if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
+                               // if the next item doesn't have any comments, do not align
+                               buf.WriteByte(blank) // align one space
+                               for i := 0; i < longestLine-curLen; i++ {
+                                       buf.WriteByte(blank)
+                               }
+
+                               for _, comment := range lit.LineComment.List {
+                                       buf.WriteString(comment.Text)
+                               }
+                       }
+               }
+
+       }
+
+       buf.WriteString("]")
+       return buf.Bytes()
+}
+
+// indent indents the lines of the given buffer for each non-empty line
+func (p *printer) indent(buf []byte) []byte {
+       var prefix []byte
+       if p.cfg.SpacesWidth != 0 {
+               for i := 0; i < p.cfg.SpacesWidth; i++ {
+                       prefix = append(prefix, blank)
+               }
+       } else {
+               prefix = []byte{tab}
+       }
+
+       var res []byte
+       bol := true
+       for _, c := range buf {
+               if bol && c != '\n' {
+                       res = append(res, prefix...)
+               }
+
+               res = append(res, c)
+               bol = c == '\n'
+       }
+       return res
+}
+
+// unindent removes all the indentation from the tombstoned lines
+func (p *printer) unindent(buf []byte) []byte {
+       var res []byte
+       for i := 0; i < len(buf); i++ {
+               skip := len(buf)-i <= len(unindent)
+               if !skip {
+                       skip = !bytes.Equal(unindent, buf[i:i+len(unindent)])
+               }
+               if skip {
+                       res = append(res, buf[i])
+                       continue
+               }
+
+               // We have a marker. we have to backtrace here and clean out
+               // any whitespace ahead of our tombstone up to a \n
+               for j := len(res) - 1; j >= 0; j-- {
+                       if res[j] == '\n' {
+                               break
+                       }
+
+                       res = res[:j]
+               }
+
+               // Skip the entire unindent marker
+               i += len(unindent) - 1
+       }
+
+       return res
+}
+
+// heredocIndent marks all the 2nd and further lines as unindentable
+func (p *printer) heredocIndent(buf []byte) []byte {
+       var res []byte
+       bol := false
+       for _, c := range buf {
+               if bol && c != '\n' {
+                       res = append(res, unindent...)
+               }
+               res = append(res, c)
+               bol = c == '\n'
+       }
+       return res
+}
+
+// isSingleLineObject tells whether the given object item is a single
+// line object such as "obj {}".
+//
+// A single line object:
+//
+//   * has no lead comments (hence multi-line)
+//   * has no assignment
+//   * has no values in the stanza (within {})
+//
+func (p *printer) isSingleLineObject(val *ast.ObjectItem) bool {
+       // If there is a lead comment, can't be one line
+       if val.LeadComment != nil {
+               return false
+       }
+
+       // If there is assignment, we always break by line
+       if val.Assign.IsValid() {
+               return false
+       }
+
+       // If it isn't an object type, then its not a single line object
+       ot, ok := val.Val.(*ast.ObjectType)
+       if !ok {
+               return false
+       }
+
+       // If the object has no items, it is single line!
+       return len(ot.List.Items) == 0
+}
+
+func lines(txt string) int {
+       endline := 1
+       for i := 0; i < len(txt); i++ {
+               if txt[i] == '\n' {
+                       endline++
+               }
+       }
+       return endline
+}
+
+// ----------------------------------------------------------------------------
+// Tracing support
+
+func (p *printer) printTrace(a ...interface{}) {
+       if !p.enableTrace {
+               return
+       }
+
+       const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
+       const n = len(dots)
+       i := 2 * p.indentTrace
+       for i > n {
+               fmt.Print(dots)
+               i -= n
+       }
+       // i <= n
+       fmt.Print(dots[0:i])
+       fmt.Println(a...)
+}
+
+func trace(p *printer, msg string) *printer {
+       p.printTrace(msg, "(")
+       p.indentTrace++
+       return p
+}
+
+// Usage pattern: defer un(trace(p, "..."))
+func un(p *printer) {
+       p.indentTrace--
+       p.printTrace(")")
+}