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  }