-
Notifications
You must be signed in to change notification settings - Fork 3
/
write.go
156 lines (130 loc) · 3.29 KB
/
write.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package config
import (
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/imdario/mergo"
)
var (
errReadonly = errors.New("the config instance in 'readonly' mode")
errKeyIsEmpty = errors.New("the config key is cannot be empty")
)
// SetData for override the Config.Data
func SetData(data map[string]interface{}) {
dc.SetData(data)
}
// SetData for override the Config.Data
func (c *Config) SetData(data map[string]interface{}) {
c.lock.Lock()
c.data = data
c.lock.Unlock()
c.fireHook(OnSetData)
}
// Set val by key
func Set(key string, val interface{}, setByPath ...bool) error {
return dc.Set(key, val, setByPath...)
}
// Set a value by key string.
func (c *Config) Set(key string, val interface{}, setByPath ...bool) (err error) {
if c.opts.Readonly {
return errReadonly
}
c.lock.Lock()
defer c.lock.Unlock()
sep := c.opts.Delimiter
if key = formatKey(key, string(sep)); key == "" {
return errKeyIsEmpty
}
defer c.fireHook(OnSetValue)
if strings.IndexByte(key, sep) == -1 {
c.data[key] = val
return
}
// disable set by path.
if len(setByPath) > 0 && !setByPath[0] {
c.data[key] = val
return
}
keys := strings.Split(key, string(sep))
topK := keys[0]
paths := keys[1:]
var ok bool
var item interface{}
// find top item data based on top key
if item, ok = c.data[topK]; !ok {
// not found, is new add
c.data[topK] = buildValueByPath(paths, val)
return
}
switch typeData := item.(type) {
case map[interface{}]interface{}: // from yaml
dstItem := make(map[string]interface{})
for k, v := range typeData {
sk := fmt.Sprintf("%v", k)
dstItem[sk] = v
}
// create a new item for the topK
newItem := buildValueByPath(paths, val)
// merge new item to old item
err = mergo.Merge(&dstItem, newItem, mergo.WithOverride)
if err != nil {
return
}
c.data[topK] = dstItem
case map[string]interface{}: // from json,toml
// create a new item for the topK
newItem := buildValueByPath(paths, val)
// merge new item to old item
err = mergo.Merge(&typeData, &newItem, mergo.WithOverride)
if err != nil {
return
}
c.data[topK] = typeData
case []interface{}: // is array
index, err := strconv.Atoi(keys[1])
if len(keys) == 2 && err == nil {
if index <= len(typeData) {
typeData[index] = val
}
c.data[topK] = typeData
} else {
err = errors.New("max allow 1 level for setting array value, current key: " + key)
return err
}
default:
// as a top key
c.data[key] = val
// err = errors.New("not supported value type, cannot setting value for the key: " + key)
}
return
}
/**
more setter: SetIntArr, SetIntMap, SetString, SetStringArr, SetStringMap
*/
// build new value by key paths
// "site.info" -> map[string]map[string]val
func buildValueByPath(paths []string, val interface{}) (newItem map[string]interface{}) {
if len(paths) == 1 {
return map[string]interface{}{paths[0]: val}
}
sliceReverse(paths)
// multi nodes
for _, p := range paths {
if newItem == nil {
newItem = map[string]interface{}{p: val}
} else {
newItem = map[string]interface{}{p: newItem}
}
}
return
}
// reverse a slice. (slice 是引用,所以可以直接改变)
func sliceReverse(ss []string) {
ln := len(ss)
for i := 0; i < ln/2; i++ {
li := ln - i - 1
// fmt.Println(i, "<=>", li)
ss[i], ss[li] = ss[li], ss[i]
}
}