-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathexpander.go
129 lines (113 loc) · 3.3 KB
/
expander.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
// (c) 2013 Rick Arnold. Licensed under the BSD license (see LICENSE).
package props
import (
"bytes"
"strings"
)
// Expander represents a property set that interprets special character
// sequences in property values as references to other property values for
// replacement.
//
// For example, the following properties:
// color.alert = red
// color.info = blue
// color.text = black
//
// css.alert = border: 1px solid ${color.alert}; color: ${color.text};
// css.info = border: 1px solid ${color.info}; color: ${color.text};
// Would result in the following values:
// "css.alert": "border: 1px solid red; color: black;"
// "css.info": "border: 1px solid blue; color: black;"
//
// Nested and recursive property expansions are permitted. If a property value
// does not exist, the property reference will be left unchanged.
type Expander struct {
// Prefix indicates the start of a property expansion.
Prefix string
// Suffix indicates the end of a property expansion.
Suffix string
// Limit the nesting depth; <= 0 allows for unlimited nesting
Limit int
// Source provides the properties to use for expansion
Source PropertyGetter
}
// NewExpander creates an empty property set with the default expansion
// Prefix "${" and Suffix "}".
func NewExpander(source PropertyGetter) *Expander {
e := &Expander{
Prefix: "${",
Suffix: "}",
Source: source,
}
return e
}
// Get retrieves the value of a property with all property references expanded.
// If the property does not exist, an empty string will be returned. The bool
// return value indicates whether the property was found.
func (e *Expander) Get(key string) (string, bool) {
v, ok := e.Source.Get(key)
return e.expand(v, make(map[string]struct{})), ok
}
// GetDefault retrieves the value of a property with all property references
// expanded. If the property does not exist, the default value will be returned
// with all its property references expanded.
func (e *Expander) GetDefault(key, defVal string) string {
v := e.Source.GetDefault(key, defVal)
return e.expand(v, make(map[string]struct{}))
}
// expand any embedded property references in a string
func (e *Expander) expand(v string, seen map[string]struct{}) string {
if v == "" || !strings.Contains(v, e.Prefix) || !strings.Contains(v, e.Suffix) {
return v
}
if _, ok := seen[v]; ok {
// cycle detected
return v
}
if e.Limit > 0 && len(seen) >= e.Limit {
return v
}
seen[v] = struct{}{}
var out bytes.Buffer
start := 0
nest := 0
for i := 0; i < len(v); i++ {
if !strings.HasPrefix(v[i:], e.Prefix) {
continue
}
out.WriteString(v[start:i])
start = i + len(e.Prefix)
for j := start; j < len(v); j++ {
if strings.HasPrefix(v[j:], e.Suffix) {
if nest == 0 {
exp := e.expand(v[start:j], seen)
val, _ := e.Source.Get(exp)
if len(val) == 0 {
out.WriteString(e.Prefix)
out.WriteString(exp)
out.WriteString(e.Suffix)
} else {
out.WriteString(val)
}
start = j + len(e.Suffix)
i = start - 1
break
} else {
nest--
}
} else if strings.HasPrefix(v[j:], e.Prefix) {
nest++
}
}
}
if start < len(v) {
out.WriteString(v[start:])
}
result := out.String()
// expand properties recursively
if v == result {
return out.String()
} else {
return e.expand(out.String(), seen)
}
}