diff --git a/main.go b/main.go index cdeebf16..bfdb3eb2 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,11 @@ package main import ( "fmt" - "github.com/roboll/helmfile/pkg/app/version" "os" "strings" + "github.com/roboll/helmfile/pkg/app/version" + "github.com/roboll/helmfile/pkg/app" "github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/maputil" @@ -483,10 +484,10 @@ func NewUrfaveCliConfigImpl(c *cli.Context) (configImpl, error) { ops := strings.Split(optsSet[i], ",") for j := range ops { op := strings.SplitN(ops[j], "=", 2) - k := strings.Split(op[0], ".") + k := maputil.ParseKey(op[0]) v := op[1] - set = maputil.Set(set, k, v) + maputil.Set(set, k, v) } } conf.set = set diff --git a/pkg/maputil/maputil.go b/pkg/maputil/maputil.go index 76b2fe82..25930e4c 100644 --- a/pkg/maputil/maputil.go +++ b/pkg/maputil/maputil.go @@ -1,6 +1,9 @@ package maputil -import "fmt" +import ( + "fmt" + "strings" +) func CastKeysToStrings(s interface{}) (map[string]interface{}, error) { new := map[string]interface{}{} @@ -60,32 +63,129 @@ func recursivelyStringifyMapKey(v interface{}) (interface{}, error) { return casted_v, nil } -func Set(m map[string]interface{}, key []string, value string) map[string]interface{} { +type arg interface { + getMap(map[string]interface{}) map[string]interface{} + set(map[string]interface{}, string) +} + +type keyArg struct { + key string +} + +func (a keyArg) getMap(m map[string]interface{}) map[string]interface{} { + _, ok := m[a.key] + if !ok { + m[a.key] = map[string]interface{}{} + } + switch t := m[a.key].(type) { + case map[string]interface{}: + return t + default: + panic(fmt.Errorf("unexpected type: %v(%T)", t, t)) + } +} + +func (a keyArg) set(m map[string]interface{}, value string) { + m[a.key] = value +} + +type indexedKeyArg struct { + key string + index int +} + +func (a indexedKeyArg) getArray(m map[string]interface{}) []interface{} { + _, ok := m[a.key] + if !ok { + m[a.key] = make([]interface{}, a.index+1) + } + switch t := m[a.key].(type) { + case []interface{}: + if len(t) <= a.index { + t2 := make([]interface{}, a.index+1) + copy(t, t2) + t = t2 + } + return t + default: + panic(fmt.Errorf("unexpected type: %v(%T)", t, t)) + } +} + +func (a indexedKeyArg) getMap(m map[string]interface{}) map[string]interface{} { + t := a.getArray(m) + if t[a.index] == nil { + t[a.index] = map[string]interface{}{} + } + switch t := t[a.index].(type) { + case map[string]interface{}: + return t + default: + panic(fmt.Errorf("unexpected type: %v(%T)", t, t)) + } +} + +func (a indexedKeyArg) set(m map[string]interface{}, value string) { + t := a.getArray(m) + t[a.index] = value + m[a.key] = t +} + +func getCursor(key string) arg { + key = strings.ReplaceAll(key, "[", " ") + key = strings.ReplaceAll(key, "]", " ") + k := key + idx := 0 + + n, err := fmt.Sscanf(key, "%s %d", &k, &idx) + + if n == 2 && err == nil { + return indexedKeyArg{ + key: k, + index: idx, + } + } + + return keyArg{ + key: key, + } +} + +func ParseKey(key string) []string { + r := []string{} + part := "" + escaped := false + for _, rune := range key { + split := false + switch { + case escaped == false && rune == '\\': + escaped = true + continue + case rune == '.': + split = escaped == false + } + escaped = false + if split { + r = append(r, part) + part = "" + } else { + part = part + string(rune) + } + } + if len(part) > 0 { + r = append(r, part) + } + return r +} + +func Set(m map[string]interface{}, key []string, value string) { if len(key) == 0 { panic(fmt.Errorf("bug: unexpected length of key: %d", len(key))) } - k := key[0] - - if len(key) == 1 { - m[k] = value - return m + for len(key) > 1 { + m, key = getCursor(key[0]).getMap(m), key[1:] } - remain := key[1:] - - nested, ok := m[k] - if !ok { - nested = map[string]interface{}{} - } - switch t := nested.(type) { - case map[string]interface{}: - nested = Set(t, remain, value) - default: - panic(fmt.Errorf("unexpected type: %v(%T)", t, t)) - } - - m[k] = nested - - return m + getCursor(key[0]).set(m, value) } diff --git a/pkg/maputil/maputil_test.go b/pkg/maputil/maputil_test.go index dd43b9c5..9c80fd06 100644 --- a/pkg/maputil/maputil_test.go +++ b/pkg/maputil/maputil_test.go @@ -59,3 +59,72 @@ func TestMapUtil_IFKeys(t *testing.T) { t.Errorf("unexpected c: expected=C, got=%s", c) } } + +func TestMapUtil_KeyArg(t *testing.T) { + m := map[string]interface{}{} + + key := []string{"a", "b", "c"} + + Set(m, key, "C") + + c := (((m["a"].(map[string]interface{}))["b"]).(map[string]interface{}))["c"] + + if c != "C" { + t.Errorf("unexpected c: expected=C, got=%s", c) + } +} + +func TestMapUtil_IndexedKeyArg(t *testing.T) { + m := map[string]interface{}{} + + key := []string{"a", "b[0]", "c"} + + Set(m, key, "C") + + c := (((m["a"].(map[string]interface{}))["b"].([]interface{}))[0].(map[string]interface{}))["c"] + + if c != "C" { + t.Errorf("unexpected c: expected=C, got=%s", c) + } +} + +type parseKeyTc struct { + key string + result map[int]string +} + +func TestMapUtil_ParseKey(t *testing.T) { + tcs := []parseKeyTc{ + { + key: `a.b.c`, + result: map[int]string{ + 0: "a", + 1: "b", + 2: "c", + }, + }, + { + key: `a\.b.c`, + result: map[int]string{ + 0: "a.b", + 1: "c", + }, + }, + { + key: `a\.b\.c`, + result: map[int]string{ + 0: "a.b.c", + }, + }, + } + + for _, tc := range tcs { + parts := ParseKey(tc.key) + + for index, value := range tc.result { + if parts[index] != value { + t.Errorf("unexpected key part[%d]: expected=%s, got=%s", index, value, parts[index]) + } + } + } +}