-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathutil.go
166 lines (155 loc) · 4.67 KB
/
util.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
157
158
159
160
161
162
163
164
165
166
package config
import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
func mapKeysToFields(ptr reflect.Value, m map[string]reflect.Value, prefix string) {
structValue := ptr.Elem()
for i := 0; i < structValue.NumField(); i++ {
fieldType := structValue.Type().Field(i)
fieldPtr := structValue.Field(i).Addr()
key := getKey(fieldType, prefix)
switch fieldType.Type.Kind() {
case reflect.Struct:
mapKeysToFields(fieldPtr, m, key+structDelim)
default:
m[key] = fieldPtr
}
}
}
func mergeMaps(first, second map[string]string) {
for k, v := range second {
first[k] = v
}
}
// stringsToMap builds a map from a string slice.
// The input strings are assumed to be environment variable in style e.g. KEY=VALUE
// Keys with no value are not added to the map.
func stringsToMap(ss []string) map[string]string {
m := make(map[string]string)
for _, s := range ss {
if !strings.Contains(s, "=") {
continue // ensures return is always of length 2
}
// TODO replace with strings.Cut in go 1.18
split := strings.SplitN(s, "=", 2)
key, value := strings.ToLower(split[0]), split[1]
if key != "" && value != "" {
m[key] = value
}
}
return m
}
// getKey returns the string that represents this structField in the config map.
// If the structField has the appropriate structTag set, it is used.
// Otherwise, field's name is used.
func getKey(t reflect.StructField, prefix string) string {
name := t.Name
if tag, exists := t.Tag.Lookup(structTagKey); exists {
if tag = strings.TrimSpace(tag); tag != "" {
name = tag
}
}
return strings.ToLower(prefix + name)
}
// stringToSlice converts a string to a slice of string, using delim.
// It strips surrounding whitespace of all entries.
// If the input string is empty or all whitespace, nil is returned.
func stringToSlice(s, delim string) []string {
if delim == "" {
panic("empty delimiter") // impossible or programmer error
}
s = strings.TrimSpace(s)
if s == "" {
return nil
}
split := strings.Split(s, delim)
filtered := split[:0] // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
for _, v := range split {
v = strings.TrimSpace(v)
if v != "" {
filtered = append(filtered, v)
}
}
return filtered
}
// convertAndSetSlice builds a slice of a dynamic type.
// It converts each entry in "values" to the elemType of the passed in slice.
// The slice remains nil if "values" is empty.
// All values are attempted.
// Returns the indices of failed values
func convertAndSetSlice(slicePtr reflect.Value, values []string) []int {
sliceVal := slicePtr.Elem()
elemType := sliceVal.Type().Elem()
var failedIndices []int
for i, s := range values {
valuePtr := reflect.New(elemType)
if !convertAndSetValue(valuePtr, s) {
failedIndices = append(failedIndices, i)
} else {
sliceVal.Set(reflect.Append(sliceVal, valuePtr.Elem()))
}
}
return failedIndices
}
// convertAndSetValue receives a settable of an arbitrary kind, and sets its value to s, returning true.
// It calls the matching strconv function on s, based on the settable's kind.
// All basic types (bool, int, float, string) are handled by this function.
// Slice and struct are handled elsewhere.
//
// An unhandled kind or a failed parse returns false.
// False is used to prevent accidental logging of secrets as
// as the strconv include s in their error message.
func convertAndSetValue(settable reflect.Value, s string) bool {
settableValue := settable.Elem()
var (
err error
i int64
u uint64
b bool
f float64
url *url.URL
)
switch settableValue.Kind() {
case reflect.Ptr: // only one pointer type is handled at the moment, *url.URL
url, err = url.Parse(s)
if err == nil {
settableValue.Set(reflect.ValueOf(url))
}
case reflect.String:
settableValue.SetString(s)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if settableValue.Type().PkgPath() == "time" && settableValue.Type().Name() == "Duration" {
var d time.Duration
d, err = time.ParseDuration(s)
i = int64(d)
} else {
i, err = strconv.ParseInt(s, 10, settableValue.Type().Bits())
}
if err == nil {
settableValue.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u, err = strconv.ParseUint(s, 10, settableValue.Type().Bits())
if err == nil {
settableValue.SetUint(u)
}
case reflect.Bool:
b, err = strconv.ParseBool(s)
if err == nil {
settableValue.SetBool(b)
}
case reflect.Float32, reflect.Float64:
f, err = strconv.ParseFloat(s, settableValue.Type().Bits())
if err == nil {
settableValue.SetFloat(f)
}
default:
err = fmt.Errorf("config: cannot handle kind %v", settableValue.Type().Kind())
}
return err == nil
}