-
Notifications
You must be signed in to change notification settings - Fork 0
/
options.go
159 lines (133 loc) · 3.92 KB
/
options.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
package cfgenv
import (
"os"
"reflect"
"regexp"
"strings"
"time"
)
// PrefixOption is an option that can be passed to Load or LoadAs
// and provides a prefix for all env var names
type PrefixOption interface {
// GetPrefix returns the prefix for all env var names
GetPrefix() string
}
type prefixOpt struct {
value string
}
func (p *prefixOpt) GetPrefix() string {
return p.value
}
// NewPrefix creates a new PrefixOption with the specified prefix
func NewPrefix(prefix string) PrefixOption {
return &prefixOpt{value: prefix}
}
// SeparatorOption is an option that can be passed to Load or LoadAs
// and provides the separator to be used between prefixes used in env var names
type SeparatorOption interface {
// GetSeparator returns the separator to be used between prefixes used in env var names
GetSeparator() string
}
type separatorOpt struct {
value string
}
func (s *separatorOpt) GetSeparator() string {
return s.value
}
// NewSeparator creates a new NewSeparator with the specified separator
func NewSeparator(separator string) SeparatorOption {
return &separatorOpt{
value: separator,
}
}
// NamingOption is an option that can be passed to Load or LoadAs
// and provides a means of overriding how env var names are deduced
type NamingOption interface {
BuildName(prefix string, separator string, fld reflect.StructField, overrideName string) string
}
var defaultNamingOption NamingOption = &namingOption{}
type namingOption struct{}
func (n *namingOption) BuildName(prefix string, separator string, fld reflect.StructField, overrideName string) string {
name := overrideName
if name == "" {
name = toSnakeCase(fld.Name)
}
if prefix != "" {
name = prefix + separator + name
}
return name
}
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
func toSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToUpper(snake)
}
// CustomSetterOption is an option that can be passed to Load or LoadAs
// and provides support for reading additional struct field types
type CustomSetterOption interface {
// IsApplicable should return true if the fld type is supported by this custom setter
IsApplicable(fld reflect.StructField) bool
// Set sets the field value `v` using the environment var `raw` value
Set(fld reflect.StructField, v reflect.Value, raw string) error
}
type dateTimeSetterOption struct {
format string
}
func NewDatetimeSetter(format string) CustomSetterOption {
if format == "" {
return &dateTimeSetterOption{
format: time.RFC3339,
}
}
return &dateTimeSetterOption{
format: format,
}
}
func (d *dateTimeSetterOption) IsApplicable(fld reflect.StructField) bool {
return fld.Type == dtType
}
func (d *dateTimeSetterOption) Set(fld reflect.StructField, v reflect.Value, raw string) error {
dt, err := time.Parse(d.format, raw)
if err != nil {
return err
}
v.Set(reflect.ValueOf(dt))
return nil
}
var dtType = reflect.TypeOf(time.Time{})
// Expand creates a default ExpandOption (for use in Load / LoadAs)
//
// Any supplied lookup maps are checked first - if a given env var name, e.g. "${FOO}", is
// not found in lookups then the value is taken from env var
func Expand(lookups ...map[string]string) ExpandOption {
return &expandOpt{
lookups: lookups,
}
}
// ExpandOption is an option that can be passed to Load or LoadAs
// and provides support for expanding environment var values like...
//
// FOO=${BAR}
type ExpandOption interface {
// Expand expands the env var value s
Expand(s string) string
}
type expandOpt struct {
lookups []map[string]string
}
func (e *expandOpt) Expand(s string) string {
return os.Expand(s, e.expand)
}
func (e *expandOpt) expand(s string) string {
for _, m := range e.lookups {
if v, ok := m[s]; ok {
return e.Expand(v)
}
}
if v, ok := os.LookupEnv(s); ok {
return e.Expand(v)
}
return ""
}