зеркало из https://github.com/getsops/sops.git
188 строки
4.9 KiB
Go
188 строки
4.9 KiB
Go
package stores
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const mapSeparator = "__map_"
|
|
const listSeparator = "__list_"
|
|
|
|
// flattenAndMerge flattens the provided value and merges into the
|
|
// into map using prefix
|
|
func flattenAndMerge(into map[string]interface{}, prefix string, value interface{}) {
|
|
flattenedValue := flattenValue(value)
|
|
if flattenedValue, ok := flattenedValue.(map[string]interface{}); ok {
|
|
for flatK, flatV := range flattenedValue {
|
|
into[prefix+flatK] = flatV
|
|
}
|
|
} else {
|
|
into[prefix] = value
|
|
}
|
|
}
|
|
|
|
func flattenValue(value interface{}) interface{} {
|
|
var output interface{}
|
|
switch value := value.(type) {
|
|
case map[string]interface{}:
|
|
newMap := make(map[string]interface{})
|
|
for k, v := range value {
|
|
flattenAndMerge(newMap, mapSeparator+k, v)
|
|
}
|
|
output = newMap
|
|
case []interface{}:
|
|
newMap := make(map[string]interface{})
|
|
for i, v := range value {
|
|
flattenAndMerge(newMap, listSeparator+fmt.Sprintf("%d", i), v)
|
|
}
|
|
output = newMap
|
|
default:
|
|
output = value
|
|
}
|
|
return output
|
|
}
|
|
|
|
// Flatten flattens a map with potentially nested maps into a flat
|
|
// map. Only string keys are allowed on both the top-level map and
|
|
// child maps.
|
|
func Flatten(in map[string]interface{}) map[string]interface{} {
|
|
newMap := make(map[string]interface{})
|
|
for k, v := range in {
|
|
if flat, ok := flattenValue(v).(map[string]interface{}); ok {
|
|
for flatK, flatV := range flat {
|
|
newMap[k+flatK] = flatV
|
|
}
|
|
} else {
|
|
newMap[k] = v
|
|
}
|
|
}
|
|
return newMap
|
|
}
|
|
|
|
type token interface{}
|
|
|
|
type mapToken struct {
|
|
key string
|
|
}
|
|
|
|
type listToken struct {
|
|
position int
|
|
}
|
|
|
|
// tokenize converts a path generated by Flatten to be used as a key
|
|
// in the flattened map, and converts it to a slice of tokens
|
|
func tokenize(path string) []token {
|
|
const (
|
|
StateNormal = 0
|
|
StateMap = iota
|
|
StateList = iota
|
|
)
|
|
var tokens []token
|
|
state := StateNormal
|
|
lastTokenEnd := 0
|
|
i := 0
|
|
finishPrevToken := func() {
|
|
var t token
|
|
switch state {
|
|
case StateNormal:
|
|
t = mapToken{path[lastTokenEnd:i]}
|
|
case StateMap:
|
|
t = mapToken{path[lastTokenEnd+len(mapSeparator) : i]}
|
|
case StateList:
|
|
pos, _ := strconv.Atoi(path[lastTokenEnd+len(listSeparator) : i])
|
|
t = listToken{pos}
|
|
}
|
|
lastTokenEnd = i
|
|
tokens = append(tokens, t)
|
|
}
|
|
for i < len(path) {
|
|
if strings.HasPrefix(path[i:], mapSeparator) {
|
|
finishPrevToken()
|
|
state = StateMap
|
|
i += len(mapSeparator)
|
|
} else if strings.HasPrefix(path[i:], listSeparator) {
|
|
finishPrevToken()
|
|
state = StateList
|
|
i += len(listSeparator)
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
finishPrevToken()
|
|
return tokens
|
|
}
|
|
|
|
// unflatten takes the currentNode, currentToken, nextToken and value
|
|
// and populates currentNode such that currentToken can be considered
|
|
// processed. It inspects nextToken to decide what type to allocate
|
|
// and assign under currentNode.
|
|
func unflatten(currentNode interface{}, currentToken, nextToken token, value interface{}) interface{} {
|
|
switch currentToken := currentToken.(type) {
|
|
case mapToken:
|
|
currentNode := currentNode.(map[string]interface{})
|
|
switch nextToken := nextToken.(type) {
|
|
case mapToken:
|
|
if _, ok := currentNode[currentToken.key]; !ok {
|
|
currentNode[currentToken.key] = make(map[string]interface{})
|
|
}
|
|
next := currentNode[currentToken.key].(map[string]interface{})
|
|
return next
|
|
case listToken:
|
|
if _, ok := currentNode[currentToken.key]; !ok {
|
|
currentNode[currentToken.key] = make([]interface{}, nextToken.position+1)
|
|
}
|
|
next := currentNode[currentToken.key].([]interface{})
|
|
if nextToken.position >= len(next) {
|
|
// Grow the slice and reassign it
|
|
newNext := make([]interface{}, nextToken.position+1)
|
|
copy(newNext, next)
|
|
next = newNext
|
|
currentNode[currentToken.key] = next
|
|
}
|
|
return next
|
|
default:
|
|
currentNode[currentToken.key] = value
|
|
}
|
|
case listToken:
|
|
currentNode := currentNode.([]interface{})
|
|
switch nextToken := nextToken.(type) {
|
|
case mapToken:
|
|
if currentNode[currentToken.position] == nil {
|
|
currentNode[currentToken.position] = make(map[string]interface{})
|
|
}
|
|
next := currentNode[currentToken.position].(map[string]interface{})
|
|
return next
|
|
case listToken:
|
|
if currentNode[currentToken.position] == nil {
|
|
currentNode[currentToken.position] = make([]interface{}, nextToken.position+1)
|
|
}
|
|
next := currentNode[currentToken.position].([]interface{})
|
|
if nextToken.position >= len(next) {
|
|
// Grow the slice and reassign it
|
|
newNext := make([]interface{}, nextToken.position+1)
|
|
copy(newNext, next)
|
|
next = newNext
|
|
currentNode[currentToken.position] = next
|
|
}
|
|
return next
|
|
default:
|
|
currentNode[currentToken.position] = value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unflatten unflattens a map flattened by Flatten
|
|
func Unflatten(in map[string]interface{}) map[string]interface{} {
|
|
newMap := make(map[string]interface{})
|
|
for k, v := range in {
|
|
var current interface{} = newMap
|
|
tokens := append(tokenize(k), nil)
|
|
for i := 0; i < len(tokens)-1; i++ {
|
|
current = unflatten(current, tokens[i], tokens[i+1], v)
|
|
}
|
|
}
|
|
return newMap
|
|
}
|