flatten.go raw

   1  package parser
   2  
   3  import "github.com/hashicorp/hcl/hcl/ast"
   4  
   5  // flattenObjects takes an AST node, walks it, and flattens
   6  func flattenObjects(node ast.Node) {
   7  	ast.Walk(node, func(n ast.Node) (ast.Node, bool) {
   8  		// We only care about lists, because this is what we modify
   9  		list, ok := n.(*ast.ObjectList)
  10  		if !ok {
  11  			return n, true
  12  		}
  13  
  14  		// Rebuild the item list
  15  		items := make([]*ast.ObjectItem, 0, len(list.Items))
  16  		frontier := make([]*ast.ObjectItem, len(list.Items))
  17  		copy(frontier, list.Items)
  18  		for len(frontier) > 0 {
  19  			// Pop the current item
  20  			n := len(frontier)
  21  			item := frontier[n-1]
  22  			frontier = frontier[:n-1]
  23  
  24  			switch v := item.Val.(type) {
  25  			case *ast.ObjectType:
  26  				items, frontier = flattenObjectType(v, item, items, frontier)
  27  			case *ast.ListType:
  28  				items, frontier = flattenListType(v, item, items, frontier)
  29  			default:
  30  				items = append(items, item)
  31  			}
  32  		}
  33  
  34  		// Reverse the list since the frontier model runs things backwards
  35  		for i := len(items)/2 - 1; i >= 0; i-- {
  36  			opp := len(items) - 1 - i
  37  			items[i], items[opp] = items[opp], items[i]
  38  		}
  39  
  40  		// Done! Set the original items
  41  		list.Items = items
  42  		return n, true
  43  	})
  44  }
  45  
  46  func flattenListType(
  47  	ot *ast.ListType,
  48  	item *ast.ObjectItem,
  49  	items []*ast.ObjectItem,
  50  	frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
  51  	// If the list is empty, keep the original list
  52  	if len(ot.List) == 0 {
  53  		items = append(items, item)
  54  		return items, frontier
  55  	}
  56  
  57  	// All the elements of this object must also be objects!
  58  	for _, subitem := range ot.List {
  59  		if _, ok := subitem.(*ast.ObjectType); !ok {
  60  			items = append(items, item)
  61  			return items, frontier
  62  		}
  63  	}
  64  
  65  	// Great! We have a match go through all the items and flatten
  66  	for _, elem := range ot.List {
  67  		// Add it to the frontier so that we can recurse
  68  		frontier = append(frontier, &ast.ObjectItem{
  69  			Keys:        item.Keys,
  70  			Assign:      item.Assign,
  71  			Val:         elem,
  72  			LeadComment: item.LeadComment,
  73  			LineComment: item.LineComment,
  74  		})
  75  	}
  76  
  77  	return items, frontier
  78  }
  79  
  80  func flattenObjectType(
  81  	ot *ast.ObjectType,
  82  	item *ast.ObjectItem,
  83  	items []*ast.ObjectItem,
  84  	frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
  85  	// If the list has no items we do not have to flatten anything
  86  	if ot.List.Items == nil {
  87  		items = append(items, item)
  88  		return items, frontier
  89  	}
  90  
  91  	// All the elements of this object must also be objects!
  92  	for _, subitem := range ot.List.Items {
  93  		if _, ok := subitem.Val.(*ast.ObjectType); !ok {
  94  			items = append(items, item)
  95  			return items, frontier
  96  		}
  97  	}
  98  
  99  	// Great! We have a match go through all the items and flatten
 100  	for _, subitem := range ot.List.Items {
 101  		// Copy the new key
 102  		keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
 103  		copy(keys, item.Keys)
 104  		copy(keys[len(item.Keys):], subitem.Keys)
 105  
 106  		// Add it to the frontier so that we can recurse
 107  		frontier = append(frontier, &ast.ObjectItem{
 108  			Keys:        keys,
 109  			Assign:      item.Assign,
 110  			Val:         subitem.Val,
 111  			LeadComment: item.LeadComment,
 112  			LineComment: item.LineComment,
 113  		})
 114  	}
 115  
 116  	return items, frontier
 117  }
 118