-
Notifications
You must be signed in to change notification settings - Fork 0
/
merge.go
114 lines (99 loc) · 3.49 KB
/
merge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// SPDX-FileCopyrightText: 2023 6543 <[email protected]>
// SPDX-License-Identifier: MIT
package xyaml
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
const maxDepth uint8 = 10
var (
// ErrMaxDepth indicates there is likely a loop that got caught
ErrMaxDepth = errors.New("max depth reached")
// ErrBrokenMappingNode indicates a broken map node
ErrBrokenMappingNode = errors.New("broken mapping node")
// ErrSequenceMerge show that there is a sequence merge
// indicated but got wrong values to work with
ErrSequenceMerge = errors.New("sequence merge failed")
)
// MergeSequences recursively search for sequence merge indicator "<<:" and merge if detected
func MergeSequences(node *yaml.Node) error {
return mergeSequences(node, 0)
}
func mergeSequences(node *yaml.Node, depth uint8) error {
// prevent loop by hardcoded limit
if depth == maxDepth {
return ErrMaxDepth
}
switch node.Kind {
case yaml.DocumentNode:
return mergeSequences(node.Content[0], depth+1)
case yaml.MappingNode:
if (len(node.Content) % 2) != 0 {
return ErrBrokenMappingNode
}
for i := len(node.Content); i > 1; i = i - 2 {
if err := mergeSequences(node.Content[i-1], depth+1); err != nil {
return err
}
}
case yaml.SequenceNode:
var newContent []*yaml.Node // as long as we don't have a merge, it is empty and we don't perform slice operations
for i := range node.Content {
// detect "<<:" entry
if node.Content[i].Kind == yaml.MappingNode &&
len(node.Content[i].Content) == 2 &&
node.Content[i].Content[0].Kind == yaml.ScalarNode &&
node.Content[i].Content[0].Tag == mergeTag {
// we did detect a merge tag
if node.Content[i].Content[1].Kind == yaml.AliasNode {
newC := node.Content[i].Content[1].Alias
if newC.Kind != yaml.SequenceNode {
return fmt.Errorf("%w: can only merge sequence to sequence but got something else", ErrSequenceMerge)
}
if len(newContent) != 0 {
newContent = append(newContent, newC.Content...)
} else {
newContent = make([]*yaml.Node, i)
copy(newContent, node.Content[:i])
newContent = append(newContent, newC.Content...)
}
} else if node.Content[i].Content[1].Kind == yaml.SequenceNode {
newC := make([]*yaml.Node, 0, len(node.Content[i].Content[1].Content))
for _, alias := range node.Content[i].Content[1].Content {
if alias.Kind != yaml.AliasNode {
return fmt.Errorf("%w: merge sequences contain an non alias node", ErrSequenceMerge)
} else if alias.Alias.Kind != yaml.SequenceNode {
return fmt.Errorf("%w: merge sequences contain an alias to an non sequence node", ErrSequenceMerge)
}
newC = append(newC, alias.Alias.Content...)
}
if len(newContent) != 0 {
newContent = append(newContent, newC...)
} else {
newContent = make([]*yaml.Node, i)
copy(newContent, node.Content[:i])
newContent = append(newContent, newC...)
}
} else {
return fmt.Errorf("%w: merge map node did not contain an alias node", ErrSequenceMerge)
}
} else {
// we did not detect a merge tag
// else its just a normal sequence item we do process recursive
if err := mergeSequences(node.Content[i], depth+1); err != nil {
return err
}
// if there was a merge before we need to append it to the new content
if len(newContent) != 0 {
newContent = append(newContent, node.Content[i])
}
}
}
if len(newContent) != 0 {
node.Content = newContent
}
}
// we ignore Scalar and Alias Nodes
return nil
}