-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterpolate.go
More file actions
149 lines (137 loc) · 3.66 KB
/
Copy pathinterpolate.go
File metadata and controls
149 lines (137 loc) · 3.66 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
package pom
import (
"strings"
)
const (
maxInterpolationPasses = 10
maxInterpolatedLength = 1 << 20 // 1 MiB
expressionStart = "${"
)
// interpolate substitutes ${name} expressions in s using props. It iterates
// until no further substitutions occur or maxInterpolationPasses is reached,
// so chained references like ${a} -> ${b} -> value resolve correctly.
func interpolate(s string, props map[string]string) string {
if !strings.Contains(s, expressionStart) {
return s
}
for range maxInterpolationPasses {
var changed, capped bool
s, changed, capped = interpolatePass(s, props)
if capped || !changed || !strings.Contains(s, expressionStart) {
break
}
}
return s
}
func interpolatePass(s string, props map[string]string) (string, bool, bool) {
first := strings.Index(s, expressionStart)
if first < 0 {
return s, false, false
}
// A property reference is commonly the whole value. Returning the map's
// string directly avoids building an identical intermediate string.
if v, ok := wholeExpression(s, props); ok {
if len(v) > maxInterpolatedLength {
return s, false, true
}
return v, v != s, false
}
baseLen := len(s)
growth := 0
search := 0
last := 0
changed := false
capped := false
var out strings.Builder
for {
open, close, ok := nextExpression(s, search)
if !ok {
break
}
if close == open+len(expressionStart) {
search = close + 1
continue
}
replacement, ok := lookup(props, s[open+len(expressionStart):close])
match := s[open : close+1]
if ok && !capped {
growth += len(replacement) - len(match)
if baseLen+growth > maxInterpolatedLength {
capped = true
} else if replacement != match {
if !changed {
out.Grow(baseLen)
}
out.WriteString(s[last:open])
out.WriteString(replacement)
last = close + 1
changed = true
}
}
search = close + 1
}
if !changed {
return s, false, capped
}
out.WriteString(s[last:])
return out.String(), true, capped
}
func wholeExpression(s string, props map[string]string) (string, bool) {
if !strings.HasPrefix(s, expressionStart) {
return "", false
}
close := strings.IndexByte(s[len(expressionStart):], '}')
if close < 0 || close != len(s)-len(expressionStart)-1 || close == 0 {
return "", false
}
return lookup(props, s[len(expressionStart):len(s)-1])
}
func nextExpression(s string, search int) (int, int, bool) {
relOpen := strings.Index(s[search:], expressionStart)
if relOpen < 0 {
return 0, 0, false
}
open := search + relOpen
relClose := strings.IndexByte(s[open+len(expressionStart):], '}')
if relClose < 0 {
return 0, 0, false
}
return open, open + len(expressionStart) + relClose, true
}
// lookup resolves a single property name, applying the alias rules Maven
// supports for legacy ${pom.*} and bare ${version}/${groupId} references.
func lookup(props map[string]string, name string) (string, bool) {
if v, ok := props[name]; ok {
return v, true
}
if strings.HasPrefix(name, "pom.") {
if v, ok := props["project."+name[len("pom."):]]; ok {
return v, true
}
}
switch name {
case elementVersion, elementGroupID, elementArtifactID:
if v, ok := props["project."+name]; ok {
return v, true
}
}
return "", false
}
// containsExpr reports whether s still contains an unresolved ${...}.
func containsExpr(s string) bool {
return strings.Contains(s, expressionStart)
}
// firstExpr returns the first ${name} property name in s, or "" if none.
func firstExpr(s string) string {
for search := 0; search < len(s); {
open, close, ok := nextExpression(s, search)
if !ok {
break
}
if close > open+len(expressionStart) {
return s[open+len(expressionStart) : close]
}
search = close + 1
}
return ""
}