2014-08-02 18:38:41 +00:00
|
|
|
package json
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
2014-12-24 23:34:28 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
2015-11-07 00:48:00 +00:00
|
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
2014-08-02 18:38:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// jsonErrors are the errors built up from parsing. These should not
|
|
|
|
// be accessed directly.
|
|
|
|
var jsonErrors []error
|
|
|
|
var jsonLock sync.Mutex
|
2015-11-07 00:48:00 +00:00
|
|
|
var jsonResult *ast.File
|
2014-08-02 18:38:41 +00:00
|
|
|
|
|
|
|
// Parse parses the given string and returns the result.
|
2015-11-07 00:48:00 +00:00
|
|
|
func Parse(v string) (*ast.File, error) {
|
2014-08-02 18:38:41 +00:00
|
|
|
jsonLock.Lock()
|
|
|
|
defer jsonLock.Unlock()
|
|
|
|
jsonErrors = nil
|
|
|
|
jsonResult = nil
|
|
|
|
|
|
|
|
// Parse
|
|
|
|
lex := &jsonLex{Input: v}
|
|
|
|
jsonParse(lex)
|
|
|
|
|
|
|
|
// If we have an error in the lexer itself, return it
|
|
|
|
if lex.err != nil {
|
|
|
|
return nil, lex.err
|
|
|
|
}
|
|
|
|
|
2015-11-08 00:00:02 +00:00
|
|
|
// If we have a result, flatten it. This is an operation we take on
|
|
|
|
// to make our AST look more like traditional HCL. This makes parsing
|
|
|
|
// it a lot easier later.
|
|
|
|
if jsonResult != nil {
|
|
|
|
flattenObjects(jsonResult)
|
|
|
|
}
|
|
|
|
|
2014-08-02 18:38:41 +00:00
|
|
|
// Build up the errors
|
|
|
|
var err error
|
|
|
|
if len(jsonErrors) > 0 {
|
|
|
|
err = &multierror.Error{Errors: jsonErrors}
|
|
|
|
jsonResult = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return jsonResult, err
|
|
|
|
}
|
2015-11-08 00:00:02 +00:00
|
|
|
|
|
|
|
// flattenObjects takes an AST node, walks it, and flattens
|
|
|
|
func flattenObjects(node ast.Node) {
|
|
|
|
ast.Walk(jsonResult, func(n ast.Node) bool {
|
|
|
|
// We only care about lists, because this is what we modify
|
|
|
|
list, ok := n.(*ast.ObjectList)
|
|
|
|
if !ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rebuild the item list
|
|
|
|
items := make([]*ast.ObjectItem, 0, len(list.Items))
|
|
|
|
frontier := make([]*ast.ObjectItem, len(list.Items))
|
|
|
|
copy(frontier, list.Items)
|
|
|
|
for len(frontier) > 0 {
|
|
|
|
// Pop the current item
|
|
|
|
n := len(frontier)
|
|
|
|
item := frontier[n-1]
|
|
|
|
frontier = frontier[:n-1]
|
|
|
|
|
2015-11-08 00:34:47 +00:00
|
|
|
switch v := item.Val.(type) {
|
|
|
|
case *ast.ObjectType:
|
|
|
|
items, frontier = flattenObjectType(v, item, items, frontier)
|
|
|
|
case *ast.ListType:
|
|
|
|
items, frontier = flattenListType(v, item, items, frontier)
|
|
|
|
default:
|
2015-11-08 00:00:02 +00:00
|
|
|
items = append(items, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse the list since the frontier model runs things backwards
|
|
|
|
for i := len(items)/2 - 1; i >= 0; i-- {
|
|
|
|
opp := len(items) - 1 - i
|
|
|
|
items[i], items[opp] = items[opp], items[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done! Set the original items
|
|
|
|
list.Items = items
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
2015-11-08 00:34:47 +00:00
|
|
|
|
|
|
|
func flattenListType(
|
|
|
|
ot *ast.ListType,
|
|
|
|
item *ast.ObjectItem,
|
|
|
|
items []*ast.ObjectItem,
|
|
|
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
|
|
|
// All the elements of this object must also be objects!
|
|
|
|
for _, subitem := range ot.List {
|
|
|
|
if _, ok := subitem.(*ast.ObjectType); !ok {
|
|
|
|
items = append(items, item)
|
|
|
|
return items, frontier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Great! We have a match go through all the items and flatten
|
|
|
|
for _, elem := range ot.List {
|
|
|
|
// This won't fail since we verified it earlier
|
|
|
|
ot := elem.(*ast.ObjectType)
|
|
|
|
|
|
|
|
// Go over all the subitems and merge them in
|
|
|
|
for _, subitem := range ot.List.Items {
|
|
|
|
// Copy the new key
|
|
|
|
keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
|
|
|
|
copy(keys, item.Keys)
|
|
|
|
copy(keys[len(item.Keys):], subitem.Keys)
|
|
|
|
|
|
|
|
// Add it to the frontier so that we can recurse
|
|
|
|
frontier = append(frontier, &ast.ObjectItem{
|
|
|
|
Keys: keys,
|
|
|
|
Assign: item.Assign,
|
|
|
|
Val: subitem.Val,
|
|
|
|
LeadComment: item.LeadComment,
|
|
|
|
LineComment: item.LineComment,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return items, frontier
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenObjectType(
|
|
|
|
ot *ast.ObjectType,
|
|
|
|
item *ast.ObjectItem,
|
|
|
|
items []*ast.ObjectItem,
|
|
|
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
|
|
|
// All the elements of this object must also be objects!
|
|
|
|
for _, subitem := range ot.List.Items {
|
|
|
|
if _, ok := subitem.Val.(*ast.ObjectType); !ok {
|
|
|
|
items = append(items, item)
|
|
|
|
return items, frontier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Great! We have a match go through all the items and flatten
|
|
|
|
for _, subitem := range ot.List.Items {
|
|
|
|
// Copy the new key
|
|
|
|
keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
|
|
|
|
copy(keys, item.Keys)
|
|
|
|
copy(keys[len(item.Keys):], subitem.Keys)
|
|
|
|
|
|
|
|
// Add it to the frontier so that we can recurse
|
|
|
|
frontier = append(frontier, &ast.ObjectItem{
|
|
|
|
Keys: keys,
|
|
|
|
Assign: item.Assign,
|
|
|
|
Val: subitem.Val,
|
|
|
|
LeadComment: item.LeadComment,
|
|
|
|
LineComment: item.LineComment,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return items, frontier
|
|
|
|
}
|