-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* YAML merge * code cleanup * extended tests & error messages * reorganized for use with CLI service * filename => path * renamed YML tree Config to Contents * replaced reflection with typw assertions * concat is go 1.22 --------- Co-authored-by: Tamas Papik <[email protected]>
- Loading branch information
Showing
3 changed files
with
672 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package models | ||
|
||
import ( | ||
"fmt" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
func (ymlTree *ConfigFileTreeModel) Merge() (string, error) { | ||
result, err := merge(ymlTree) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
mergedYml, err := yaml.Marshal(result) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create YML result, error: %s", err) | ||
} | ||
|
||
return string(mergedYml), nil | ||
} | ||
|
||
type yamlMap = map[any]any | ||
|
||
func merge(ymlTree *ConfigFileTreeModel) (yamlMap, error) { | ||
// Initial state is an empty map (YAML root) | ||
initial := make(yamlMap) | ||
|
||
result, err := mergeTree(initial, ymlTree) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to merge YML files, error: %s", err) | ||
} | ||
|
||
// Remove include list from result | ||
delete(result, "include") | ||
|
||
return result, nil | ||
} | ||
|
||
func mergeTree(existingValue yamlMap, treeToMerge *ConfigFileTreeModel) (yamlMap, error) { | ||
var err error | ||
|
||
// DFS: first the includes in the specified order, then the including file | ||
for _, includedTree := range treeToMerge.Includes { | ||
existingValue, err = mergeTree(existingValue, &includedTree) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to merge YML file %s, error: %s", includedTree.Path, err) | ||
} | ||
} | ||
|
||
// We assume that each YML file has a map at root, it's invalid otherwise | ||
var config yamlMap | ||
err = yaml.Unmarshal([]byte(treeToMerge.Contents), &config) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse YML file %s, error: %s", treeToMerge.Path, err) | ||
} | ||
|
||
if config == nil { | ||
// File is empty | ||
config = make(yamlMap) | ||
} | ||
|
||
return mergeMap(existingValue, config), nil | ||
} | ||
|
||
func mergeValue(existingValue any, valueToMerge any) any { | ||
|
||
switch valueToMerge.(type) { | ||
case yamlMap: | ||
existingMap, ok := existingValue.(yamlMap) | ||
if !ok { | ||
// Existing value is not a map, replace with new value | ||
existingMap = make(yamlMap) | ||
} | ||
return mergeMap(existingMap, valueToMerge.(yamlMap)) | ||
case []any: | ||
existingSlice, ok := existingValue.([]any) | ||
if !ok { | ||
// Existing value is not a slice, replace with new value | ||
existingSlice = nil | ||
} | ||
return mergeSlice(existingSlice, valueToMerge.([]any)) | ||
default: | ||
// Simple types | ||
return valueToMerge | ||
} | ||
} | ||
|
||
func mergeMap(existingMap yamlMap, mapToMerge yamlMap) yamlMap { | ||
for key, valueToMerge := range mapToMerge { | ||
existingValue, exists := existingMap[key] | ||
if exists { | ||
// Key exists in result and merged maps | ||
existingMap[key] = mergeValue(existingValue, valueToMerge) | ||
} else { | ||
// Key doesn't exist in result yet | ||
existingMap[key] = valueToMerge | ||
} | ||
} | ||
|
||
return existingMap | ||
} | ||
|
||
func mergeSlice(existingArray []any, arrayToAppend []any) []any { | ||
existingArray = append(existingArray, arrayToAppend...) | ||
return existingArray | ||
} |
Oops, something went wrong.