Repository navigation
Expand file tree
/
Copy pathengine.go
More file actions
163 lines (148 loc) · 4.74 KB
/
Copy pathengine.go
File metadata and controls
163 lines (148 loc) · 4.74 KB
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
package env
import (
"net/url"
"os"
"reflect"
"strings"
"sync"
"time"
)
// fieldInfo is the call-independent description of one struct field, derived
// from its tags. It is cached per struct type so the tags are parsed once.
type fieldInfo struct {
index int
name string // key name (env tag, or field name)
goName string // Go field name (for error messages)
sepTag string // sep tag, "" if absent
layoutTag string // layout tag, "" if absent
def string // def tag (default value)
required bool
absolute bool // key names the variable in full, ignoring any prefix
}
// fieldCache maps a struct reflect.Type to its cached []fieldInfo.
var fieldCache sync.Map
// The cachedFields returns the decoded field descriptors for the struct type t,
// computing them once and reusing them on later calls (like encoding/json's
// field cache). Unexported and env:"-" fields are excluded.
func cachedFields(t reflect.Type) []fieldInfo {
if v, ok := fieldCache.Load(t); ok {
return v.([]fieldInfo)
}
fields := make([]fieldInfo, 0, t.NumField())
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.PkgPath != "" {
continue // unexported, like encoding/json
}
name, required, absolute := parseEnvTag(f.Tag.Get(tagNameKey))
if name == defValueIgnored {
continue // env:"-"
}
if name == "" {
name = f.Name
}
fields = append(fields, fieldInfo{
index: i,
name: name,
goName: f.Name,
sepTag: f.Tag.Get(tagNameSep),
layoutTag: f.Tag.Get(tagNameLayout),
def: f.Tag.Get(tagNameValue),
required: required,
absolute: absolute,
})
}
v, _ := fieldCache.LoadOrStore(t, fields)
return v.([]fieldInfo)
}
// Cached reflect.Types for the special-cased field types (avoids recomputing
// them on every field/element in the hot path).
var (
timeDurationType = reflect.TypeOf(time.Duration(0))
timeTimeType = reflect.TypeOf(time.Time{})
timeTimePtrType = reflect.TypeOf((*time.Time)(nil))
urlType = reflect.TypeOf(url.URL{})
urlPtrType = reflect.TypeOf((*url.URL)(nil))
)
// The settings holds the resolved options for a marshal/unmarshal call.
//
// The prefix is used verbatim by the engine (the public API normalizes it,
// appending "_" when needed); the separator is the default used for
// slices/arrays of fields that do not carry a sep tag; timeLayout is the
// default layout for time.Time fields without a layout tag.
type settings struct {
prefix string
separator string
timeLayout string
fileMode os.FileMode
requireAll bool // WithRequiredAll: every leaf field must be present
raw bool // Raw variants: no ${VAR}/$VAR expansion on read/write
// Custom per-type parsers/encoders registered with WithParser/WithEncoder.
// Both maps are nil unless an option registers a type (a nil-map read is
// safe, so the hot path costs only a lookup).
parsers map[reflect.Type]func(string) (reflect.Value, error)
encoders map[reflect.Type]func(reflect.Value) (string, error)
}
// The resolveLayout maps a layout name to a Go reference-time layout. It
// accepts the names of the standard time constants for convenience and falls
// back to treating the value as a literal layout. An empty value defaults to
// RFC3339.
func resolveLayout(name string) string {
switch name {
case "", "RFC3339":
return time.RFC3339
case "RFC3339Nano":
return time.RFC3339Nano
case "RFC1123":
return time.RFC1123
case "RFC1123Z":
return time.RFC1123Z
case "RFC822":
return time.RFC822
case "RFC822Z":
return time.RFC822Z
case "RFC850":
return time.RFC850
case "ANSIC":
return time.ANSIC
case "UnixDate":
return time.UnixDate
case "Kitchen":
return time.Kitchen
case "Stamp":
return time.Stamp
case "DateTime":
return time.DateTime
case "DateOnly":
return time.DateOnly
case "TimeOnly":
return time.TimeOnly
default:
return name
}
}
// The environMap snapshots the process environment as a key/value map.
func environMap() map[string]string {
env := os.Environ()
m := make(map[string]string, len(env))
for _, kv := range env {
if i := strings.IndexByte(kv, '='); i >= 0 {
m[kv[:i]] = kv[i+1:]
}
}
return m
}
// key returns the environment variable name this field reads and writes.
//
// Normally that is the prefix its enclosing structs built up, plus the field's
// own name. A field tagged "absolute" drops the prefix entirely and names the
// variable in full, which is how a nested struct reaches a variable the
// deployment already calls something fixed - DATABASE_URL sitting inside a DB
// struct that would otherwise make it DB_DATABASE_URL. The whole prefix chain
// goes, not one level of it: a name that is absolute is absolute.
func (f fieldInfo) key(prefix string) string {
if f.absolute {
return f.name
}
return prefix + f.name
}