Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 36 additions & 37 deletions hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (m *Merger) mergeTokens(aTokens hclwrite.Tokens, bTokens hclwrite.Tokens) (
// and are identified by their key, e.g.
// key = value
// or key = { ... }
func (m *Merger) mergeAttrs(aAttr map[string]*hclwrite.Attribute, bAttr map[string]*hclwrite.Attribute) map[string]hclwrite.Tokens {
func (m *Merger) mergeAttrs(aAttr map[string]*hclwrite.Attribute, bAttr map[string]*hclwrite.Attribute, outBody *hclwrite.Body) {
outAttr := make(map[string]hclwrite.Tokens)

for key, aValue := range aAttr {
Expand Down Expand Up @@ -113,7 +113,17 @@ func (m *Merger) mergeAttrs(aAttr map[string]*hclwrite.Attribute, bAttr map[stri
}
}

return outAttr
// sort and add attributes
keys := make([]string, 0, len(outAttr))
for key := range outAttr {
keys = append(keys, key)
}

sort.Strings(keys)

for _, key := range keys {
outBody.SetAttributeRaw(key, outAttr[key])
}
}

// objectForTokensMap is the inverse of hclwrite.TokensForObject, only merges the top level keys, but not the values of the keys.
Expand Down Expand Up @@ -218,33 +228,25 @@ func objectForTokensMap(tokens hclwrite.Tokens) (map[string]hclwrite.ObjectAttrT
}

// mergeFiles merges two HCL files together
// Files are composed of a body, which contains attributes and blocks
func (m *Merger) mergeFiles(aFile *hclwrite.File, bFile *hclwrite.File) *hclwrite.File {
out := hclwrite.NewFile()
outBlocks := m.mergeBlocks(aFile.Body().Blocks(), bFile.Body().Blocks())

lastIndex := len(outBlocks) - 1

for i, block := range outBlocks {
out.Body().AppendBlock(block)
out.Body().AppendNewline()

// append extra newline for spacing between blocks, but not at the EOF
if i < lastIndex {
out.Body().AppendNewline()
}
}
m.mergeAttrs(aFile.Body().Attributes(), bFile.Body().Attributes(), out.Body())
m.mergeBlocks(aFile.Body().Blocks(), bFile.Body().Blocks(), out.Body())

return out
}

// mergeBlocks merges two blocks together, a block is identified by its type and labels, e.g.
// type "label" { ... }
// or type { ... }
func (m *Merger) mergeBlocks(aBlocks []*hclwrite.Block, bBlocks []*hclwrite.Block) []*hclwrite.Block {
func (m *Merger) mergeBlocks(aBlocks []*hclwrite.Block, bBlocks []*hclwrite.Block, outBody *hclwrite.Body) {
outBlocks := make([]*hclwrite.Block, 0)
aBlockMap := blockToMap(aBlocks)
bBlockMap := blockToMap(bBlocks)

// add all blocks from a and check if they are in b
for _, aBlock := range aBlocks {
blockKey := formatBlockKey(aBlock)
outBlock := aBlock
Expand All @@ -254,45 +256,42 @@ func (m *Merger) mergeBlocks(aBlocks []*hclwrite.Block, bBlocks []*hclwrite.Bloc
// override outBlock with the new block to merge the two blocks into
outBlock = hclwrite.NewBlock(aBlock.Type(), aBlock.Labels())

// merge block attributes
outAttributes := m.mergeAttrs(aBlock.Body().Attributes(), bBlock.Body().Attributes())
// sort the keys to ensure consistent ordering
keys := make([]string, 0, len(outAttributes))
for key := range outAttributes {
keys = append(keys, key)
}

sort.Strings(keys)
// merge the attributes and blocks of the two blocks
m.mergeAttrs(aBlock.Body().Attributes(), bBlock.Body().Attributes(), outBlock.Body())

for _, key := range keys {
outBlock.Body().SetAttributeRaw(key, outAttributes[key])
// format the attributes
if len(outBody.Attributes()) > 0 {
outBody.AppendNewline()
}

// recursively merge nested blocks
aNestedBlocks := aBlock.Body().Blocks()
bNestedBlocks := bBlock.Body().Blocks()
outNestedBlocks := m.mergeBlocks(aNestedBlocks, bNestedBlocks)

for _, nestedBlock := range outNestedBlocks {
outBlock.Body().AppendNewline()
outBlock.Body().AppendBlock(nestedBlock)
}
// merge the nested blocks
m.mergeBlocks(aBlock.Body().Blocks(), bBlock.Body().Blocks(), outBlock.Body())
}

outBlocks = append(outBlocks, outBlock)
}

// add all blocks from b that are not found in a
for _, bBlock := range bBlocks {
blockKey := formatBlockKey(bBlock)
_, found := aBlockMap[blockKey]

if !found {
// append any target blocks that were not in the source
outBlocks = append(outBlocks, bBlock)
}
}

return outBlocks
// formatting of the body
lastIndex := len(outBlocks) - 1

for i, block := range outBlocks {
outBody.AppendBlock(block)

// append extra newline for spacing between blocks, but not at the EOF
if i < lastIndex {
outBody.AppendNewline()
}
}
}

func parseBytes(bytes []byte) (*hclwrite.File, error) {
Expand Down
20 changes: 13 additions & 7 deletions hcl/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ func TestMerge(t *testing.T) {
want string
wantErr error
}{
{
name: "merge simple key values",
input: input{
a: `foo = "bar"
some_value = 10
`,
b: `foo = "baz"`,
},
want: `foo = "baz"
some_value = 10
`,
},
{
name: "merge simple",
input: input{
Expand All @@ -45,13 +57,11 @@ func TestMerge(t *testing.T) {
description = "Variable A"
default = "a"
}

variable "b" {
type = string
description = "Variable B"
default = "b"
}
`,
}`,
wantErr: nil,
},
{
Expand All @@ -77,7 +87,6 @@ variable "b" {
override = true
type = string
}

`,
wantErr: nil,
},
Expand Down Expand Up @@ -110,7 +119,6 @@ variable "b" {
warning = 80
}
}

`,
wantErr: nil,
},
Expand Down Expand Up @@ -149,7 +157,6 @@ variable "b" {
}
}
}

`,
},
{
Expand Down Expand Up @@ -229,7 +236,6 @@ variable "b" {
var_key = var.value
}
}

`,
},
}
Expand Down
17 changes: 11 additions & 6 deletions src/hcl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
import { merge } from "./hcl";

describe("merge", () => {
it("should merge simple key values", async () => {
const a = `foo = "bar"`;
const b = `foo = "baz"`;

const expected = `foo = "baz"
`;
const actual = await merge(a, b);
expect(actual).toBe(expected);
});
it("should merge two hcl strings", async () => {
const a = `variable "a" {
type = string
Expand All @@ -22,13 +31,11 @@ describe("merge", () => {
description = "Variable A"
default = "a"
}

variable "b" {
type = string
description = "Variable B"
default = "b"
}
`;
}`;

const out = await merge(a, b);
expect(out).toBe(expected);
Expand All @@ -46,8 +53,7 @@ variable "b" {
type = string
description = "Variable B"
default = "b"
}
`;
}`;

const actual = await merge(a, b);
expect(actual).toBe(expected);
Expand Down Expand Up @@ -122,7 +128,6 @@ variable "b" {
},
}
}

`;
const options = { mergeMapKeys: true };
const actual = await merge(a, b, options);
Expand Down