-
Notifications
You must be signed in to change notification settings - Fork 0
/
alias.go
188 lines (167 loc) · 4.25 KB
/
alias.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright © 2018 Kent Gibson <warthog618@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package config
import (
"regexp"
"strings"
"sync"
)
// NewAlias creates an Alias.
func NewAlias(options ...aliasOption) *Alias {
a := &Alias{
aa: map[string][]string{},
pathSep: "."}
for _, option := range options {
option.applyAliasOption(a)
}
return a
}
// WithAlias provides a decorator that calls the Getter, and falls back
// to a set of aliases if the lookup of the key fails.
func WithAlias(a *Alias) Decorator {
return func(g Getter) Getter {
return aliasDecorator{getterDecorator{g}, a}
}
}
type aliasDecorator struct {
getterDecorator
a *Alias
}
func (g aliasDecorator) Get(key string) (interface{}, bool) {
return g.a.Get(g.g, key)
}
// Alias provides a mapping from a key to a set of old or alternate keys.
type Alias struct {
getterDecorator
// mutex lock covering aa and the arrays it contains.
mu sync.RWMutex
aa map[string][]string
pathSep string
}
// Get calls the Getter, and if that fails tries aliases to other keys.
func (a *Alias) Get(g Getter, key string) (interface{}, bool) {
if v, ok := g.Get(key); ok {
return v, true
}
if v, ok := a.getLeaf(g, key); ok {
return v, true
}
if v, ok := a.getBranch(g, key); ok {
return v, true
}
return nil, false
}
// Append adds an alias from the new key to the old.
// If aliases already exist for the new key then this appended to the end
// of the existing list.
func (a *Alias) Append(new, old string) {
a.mu.Lock()
a.aa[new] = append(a.aa[new], old)
a.mu.Unlock()
}
// Insert adds an alias from the new key to the old.
// If aliases already exist for the new key then this inserted to the
// beginning of the existing list.
func (a *Alias) Insert(new, old string) {
a.mu.Lock()
a.aa[new] = append([]string{old}, a.aa[new]...)
a.mu.Unlock()
}
// NewRegexAlias creates a RegexAlias.
func NewRegexAlias() *RegexAlias {
return &RegexAlias{ra: []regex{}}
}
// WithRegexAlias provides a decorator that calls the Getter, and falls back
// to a set of regular expression aliases if the lookup of the key fails.
func WithRegexAlias(r *RegexAlias) Decorator {
return func(g Getter) Getter {
return regexDecorator{getterDecorator{g}, r}
}
}
type regexDecorator struct {
getterDecorator
r *RegexAlias
}
func (g regexDecorator) Get(key string) (interface{}, bool) {
return g.r.Get(g.g, key)
}
type regex struct {
re *regexp.Regexp
old string
}
// RegexAlias provides a mapping from a key to an old key.
// New keys are expected to contain regular expressions.
type RegexAlias struct {
mu sync.RWMutex
ra []regex
}
// Append adds an alias from a regular expression matching a new key to the old.
func (r *RegexAlias) Append(new, old string) error {
re, err := regexp.Compile(new)
if err != nil {
return err
}
r.mu.Lock()
r.ra = append(r.ra, regex{re, old})
r.mu.Unlock()
return nil
}
// Get calls the Getter, and if that fails tries aliases to other keys.
func (r *RegexAlias) Get(g Getter, key string) (interface{}, bool) {
if v, ok := g.Get(key); ok {
return v, true
}
r.mu.RLock()
for _, ra := range r.ra {
if ra.re.MatchString(key) {
k := ra.re.ReplaceAllString(key, ra.old)
if v, ok := g.Get(k); ok {
r.mu.RUnlock()
return v, ok
}
}
}
r.mu.RUnlock()
return nil, false
}
func (a *Alias) getLeaf(g Getter, key string) (interface{}, bool) {
a.mu.RLock()
if aliases, ok := a.aa[key]; ok {
for _, alias := range aliases {
if v, ok := g.Get(alias); ok {
a.mu.RUnlock()
return v, true
}
}
}
a.mu.RUnlock()
return nil, false
}
func (a *Alias) getBranch(g Getter, key string) (interface{}, bool) {
path := strings.Split(key, a.pathSep)
for plen := len(path) - 1; plen >= 0; plen-- {
nodeKey := strings.Join(path[:plen], a.pathSep)
if aliases, ok := a.aa[nodeKey]; ok {
for _, alias := range aliases {
if len(alias) > 0 {
alias = alias + a.pathSep
}
idx := len(nodeKey)
if idx > 0 {
idx += len(a.pathSep)
}
aliasKey := alias + key[idx:]
if v, ok := g.Get(aliasKey); ok {
return v, true
}
}
}
}
return nil, false
}
// aliasOption is a construction option for an Alias.
type aliasOption interface {
applyAliasOption(c *Alias)
}