printer: implement standalone comments, still WIP

This commit is contained in:
Fatih Arslan 2015-10-31 15:01:49 +03:00
parent 792e0fef49
commit 9b5083066a
6 changed files with 223 additions and 86 deletions

View File

@ -10,6 +10,12 @@ type Node interface {
Pos() token.Pos
}
// NewNode returns a non usable Node interface implementer. The position is
// initalizied to zero.
func NewNode() Node {
return &zero{}
}
func (File) node() {}
func (ObjectList) node() {}
func (ObjectKey) node() {}
@ -21,6 +27,14 @@ func (ObjectType) node() {}
func (LiteralType) node() {}
func (ListType) node() {}
type zero struct{}
func (zero) node() {}
func (z *zero) Pos() token.Pos {
return token.Pos{}
}
// File represents a single HCL file
type File struct {
Node Node // usually a *ObjectList

View File

@ -3,6 +3,7 @@ package printer
import (
"bytes"
"fmt"
"sort"
"github.com/fatih/hcl/ast"
"github.com/fatih/hcl/token"
@ -15,11 +16,22 @@ const (
)
type printer struct {
cfg Config
cfg Config
prev ast.Node
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()) }
func (p *printer) collectComments(node ast.Node) {
// first collect all comments. This is already stored in
// ast.File.(comments)
@ -64,28 +76,33 @@ func (p *printer) collectComments(node ast.Node) {
})
for _, c := range standaloneComments {
p.standaloneComments = append(p.standaloneComments, c)
}
sort.Sort(ByPosition(p.standaloneComments))
fmt.Printf("standaloneComments = %+v\n", len(p.standaloneComments))
for _, c := range p.standaloneComments {
for _, comment := range c.List {
fmt.Printf("comment = %+v\n", comment)
}
p.standaloneComments = append(p.standaloneComments, c)
}
}
// output prints creates a printable HCL output and returns it.
var count int
// output prints creates b printable HCL output and returns it.
func (p *printer) output(n interface{}) []byte {
var buf bytes.Buffer
count++
switch t := n.(type) {
case *ast.File:
// for i, group := range t.Comments {
// for _, comment := range group.List {
// fmt.Printf("[%d] comment = %+v\n", i, comment)
// }
// }
return p.output(t.Node)
case *ast.ObjectList:
for i, item := range t.Items {
buf.Write(p.objectItem(item))
fmt.Printf("[%d] item: %s\n", i, item.Keys[0].Token.Text)
buf.Write(p.output(item))
if i != len(t.Items)-1 {
buf.Write([]byte{newline, newline})
}
@ -93,6 +110,20 @@ func (p *printer) output(n interface{}) []byte {
case *ast.ObjectKey:
buf.WriteString(t.Token.Text)
case *ast.ObjectItem:
for _, c := range p.standaloneComments {
for _, comment := range c.List {
fmt.Printf("[%d] OBJECTITEM p.prev = %+v\n", count, p.prev.Pos())
fmt.Printf("[%d] OBJECTITEM comment.Pos() = %+v\n", count, comment.Pos())
fmt.Printf("[%d] OBJECTTYPE t.Pos() = %+v\n", count, t.Pos())
if comment.Pos().After(p.prev.Pos()) && comment.Pos().Before(t.Pos()) {
buf.WriteString(comment.Text)
buf.WriteByte(newline)
buf.WriteByte(newline)
}
}
}
p.prev = t
buf.Write(p.objectItem(t))
case *ast.LiteralType:
buf.WriteString(t.Token.Text)
@ -104,10 +135,15 @@ func (p *printer) output(n interface{}) []byte {
fmt.Printf(" unknown type: %T\n", n)
}
// if item, ok := n.(ast.Node); ok {
// p.prev = item
// }
return buf.Bytes()
}
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 {
@ -140,6 +176,100 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte {
return buf.Bytes()
}
func (p *printer) objectType(o *ast.ObjectType) []byte {
defer un(trace(p, "ObjectType"))
var buf bytes.Buffer
buf.WriteString("{")
buf.WriteByte(newline)
for _, c := range p.standaloneComments {
for _, comment := range c.List {
fmt.Printf("[%d] OBJECTTYPE p.prev = %+v\n", count, p.prev.Pos())
fmt.Printf("[%d] OBJECTTYPE comment.Pos() = %+v\n", count, comment.Pos())
fmt.Printf("[%d] OBJECTTYPE t.Pos() = %+v\n", count, o.Pos())
firstItem := o.List.Pos()
if comment.Pos().After(p.prev.Pos()) && comment.Pos().Before(firstItem) {
buf.Write(p.indent([]byte(comment.Text))) // TODO(arslan): indent
buf.WriteByte(newline)
buf.WriteByte(newline)
}
}
}
var index int
for {
// check if we have adjacent one liner items. If yes we'll going to align
// the comments.
var aligned []*ast.ObjectItem
for i, 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
}
next := 0
if index != len(o.List.Items)-1 {
next = lines(string(p.objectItem(o.List.Items[index+1])))
}
prev := 0
if index != 0 {
prev = lines(string(p.objectItem(o.List.Items[index-1])))
}
if (cur == next && next == 1) || (next == 1 && cur == 2 && i == 0) {
aligned = append(aligned, item)
index++
} else if (cur == prev && prev == 1) || (prev == 2 && cur == 1) {
aligned = append(aligned, item)
index++
} else {
break
}
}
// fmt.Printf("==================> len(aligned) = %+v\n", len(aligned))
// for _, b := range aligned {
// fmt.Printf("b = %+v\n", b)
// }
// put newlines if the items are between other non aligned items
if index != len(aligned) {
buf.WriteByte(newline)
}
if len(aligned) >= 1 {
p.prev = aligned[len(aligned)-1]
items := p.alignedItems(aligned)
buf.Write(p.indent(items))
} else {
p.prev = o.List.Items[index]
buf.Write(p.indent(p.objectItem(o.List.Items[index])))
index++
}
buf.WriteByte(newline)
if index == len(o.List.Items) {
break
}
}
buf.WriteString("}")
return buf.Bytes()
}
func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
var buf bytes.Buffer
@ -199,82 +329,6 @@ func (p *printer) literal(l *ast.LiteralType) []byte {
return []byte(l.Token.Text)
}
func (p *printer) objectType(o *ast.ObjectType) []byte {
var buf bytes.Buffer
buf.WriteString("{")
buf.WriteByte(newline)
var index int
for {
// check if we have adjacent one liner items. If yes we'll going to align
// the comments.
var aligned []*ast.ObjectItem
for i, 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
}
next := 0
if index != len(o.List.Items)-1 {
next = lines(string(p.objectItem(o.List.Items[index+1])))
}
prev := 0
if index != 0 {
prev = lines(string(p.objectItem(o.List.Items[index-1])))
}
if (cur == next && next == 1) || (next == 1 && cur == 2 && i == 0) {
aligned = append(aligned, item)
index++
} else if (cur == prev && prev == 1) || (prev == 2 && cur == 1) {
aligned = append(aligned, item)
index++
} else {
break
}
}
// fmt.Printf("==================> len(aligned) = %+v\n", len(aligned))
// for _, b := range aligned {
// fmt.Printf("b = %+v\n", b)
// }
// put newlines if the items are between other non aligned items
if index != len(aligned) {
buf.WriteByte(newline)
}
if len(aligned) >= 1 {
items := p.alignedItems(aligned)
buf.Write(p.indent(items))
} else {
buf.Write(p.indent(p.objectItem(o.List.Items[index])))
index++
}
buf.WriteByte(newline)
if index == len(o.List.Items) {
break
}
}
buf.WriteString("}")
return buf.Bytes()
}
// printList prints a HCL list
func (p *printer) list(l *ast.ListType) []byte {
var buf bytes.Buffer
@ -335,3 +389,35 @@ func lines(txt string) int {
}
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(")")
}

View File

@ -22,6 +22,8 @@ func (c *Config) Fprint(output io.Writer, node ast.Node) error {
cfg: *c,
comments: make([]*ast.CommentGroup, 0),
standaloneComments: make([]*ast.CommentGroup, 0),
prev: ast.NewNode(),
// enableTrace: true,
}
p.collectComments(node)

View File

@ -1,9 +1,16 @@
// A standalone comment is a comment which is not attached to any kind of node
// This comes from Terraform, as a test
variable "foo" {
# Standalone comment should be still here
default = "bar"
description = "bar" # yooo
}
/* This is a multi line standalone
comment*/
// fatih arslan
/* This is a developer test
account and a multine comment */
@ -15,6 +22,12 @@ numbers = [1, 2] // another line here
# Another comment
variable = {
description = "bar" # another yooo
foo = {
# Nested standalone
bar = "fatih"
}
}
// lead comment

View File

@ -1,9 +1,17 @@
// A standalone comment is a comment which is not attached to any kind of node
// This comes from Terraform, as a test
variable "foo" {
# Standalone comment should be still here
default = "bar"
description = "bar" # yooo
}
/* This is a multi line standalone
comment*/
// fatih arslan
/* This is a developer test
account and a multine comment */
@ -15,9 +23,13 @@ numbers = [1,2] // another line here
# Another comment
variable = {
description = "bar" # another yooo
foo {
# Nested standalone
bar = "fatih"
}
}
// lead comment
foo {
bar = "fatih" // line comment 2

View File

@ -34,3 +34,13 @@ func (p Pos) String() string {
}
return s
}
// Before reports whether the position p is before u.
func (p Pos) Before(u Pos) bool {
return u.Offset > p.Offset || u.Line > p.Line
}
// After reports whether the position p is after u.
func (p Pos) After(u Pos) bool {
return u.Offset < p.Offset || u.Line < p.Line
}