github.com/simonferquel/app@v0.6.1-0.20181012141724-68b7cccf26ac/types/settings/settings.go (about) 1 package settings 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "strings" 8 9 "github.com/docker/app/internal/yaml" 10 "github.com/pkg/errors" 11 ) 12 13 // Settings represents a settings map 14 type Settings map[string]interface{} 15 16 // Flatten returns a flatten view of a settings 17 // This becomes a one-level map with keys joined with a dot 18 func (s Settings) Flatten() map[string]string { 19 return flatten(s) 20 } 21 22 func flatten(s Settings) map[string]string { 23 m := map[string]string{} 24 for k, v := range s { 25 switch vv := v.(type) { 26 case string: 27 m[k] = vv 28 case map[string]interface{}: 29 im := flatten(vv) 30 for ik, iv := range im { 31 m[k+"."+ik] = iv 32 } 33 case []string: 34 for i, e := range vv { 35 m[fmt.Sprintf("%s.%d", k, i)] = fmt.Sprintf("%v", e) 36 } 37 case []interface{}: 38 for i, e := range vv { 39 m[fmt.Sprintf("%s.%d", k, i)] = fmt.Sprintf("%v", e) 40 } 41 default: 42 m[k] = fmt.Sprintf("%v", vv) 43 } 44 } 45 return m 46 } 47 48 // FromFlatten takes a flatten map and loads it as a Settings map 49 // This uses yaml.Unmarshal to "guess" the type of the value 50 func FromFlatten(m map[string]string) (Settings, error) { 51 s := map[string]interface{}{} 52 for k, v := range m { 53 ks := strings.Split(k, ".") 54 var converted interface{} 55 if err := yaml.Unmarshal([]byte(v), &converted); err != nil { 56 return s, err 57 } 58 if err := assignKey(s, ks, converted); err != nil { 59 return s, err 60 } 61 } 62 return Settings(s), nil 63 } 64 65 func isSupposedSlice(ks []string) (int64, bool) { 66 if len(ks) != 1 { 67 return 0, false 68 } 69 i, err := strconv.ParseInt(ks[0], 10, 0) 70 return i, err == nil 71 } 72 73 func assignKey(m map[string]interface{}, keys []string, value interface{}) error { 74 key := keys[0] 75 if len(keys) == 1 { 76 if v, present := m[key]; present { 77 if reflect.TypeOf(v) != reflect.TypeOf(value) { 78 return errors.Errorf("key %s is already present and value has a different type (%T vs %T)", key, v, value) 79 } 80 } 81 m[key] = value 82 return nil 83 } 84 ks := keys[1:] 85 if i, ok := isSupposedSlice(ks); ok { 86 // it's a slice 87 if v, present := m[key]; !present { 88 m[key] = make([]interface{}, i+1) 89 } else if _, isSlice := v.([]interface{}); !isSlice { 90 return errors.Errorf("key %s already present and not a slice (%T)", key, v) 91 } 92 s := m[key].([]interface{}) 93 if int64(len(s)) <= i { 94 newSlice := make([]interface{}, i+1) 95 copy(newSlice, s) 96 s = newSlice 97 } 98 s[i] = value 99 m[key] = s 100 return nil 101 } 102 if v, present := m[key]; !present { 103 m[key] = map[string]interface{}{} 104 } else if _, isMap := v.(map[string]interface{}); !isMap { 105 return errors.Errorf("key %s already present and not a map (%T)", key, v) 106 } 107 return assignKey(m[key].(map[string]interface{}), ks, value) 108 }