/
path.go
207 lines (181 loc) · 5.51 KB
/
path.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
// PathSeparator is the separator between path elements.
PathSeparator = "."
kvSeparatorRune = ':'
// InsertIndex is the index that means "insert" when setting values
InsertIndex = -1
// PathSeparatorRune is the separator between path elements, as a rune.
pathSeparatorRune = '.'
// EscapedPathSeparator is what to use when the path shouldn't separate
EscapedPathSeparator = "\\" + PathSeparator
)
// ValidKeyRegex is a regex for a valid path key element.
var ValidKeyRegex = regexp.MustCompile("^[a-zA-Z0-9_-]*$")
// Path is a path in slice form.
type Path []string
// PathFromString converts a string path of form a.b.c to a string slice representation.
func PathFromString(path string) Path {
path = filepath.Clean(path)
path = strings.TrimPrefix(path, PathSeparator)
path = strings.TrimSuffix(path, PathSeparator)
pv := splitEscaped(path, pathSeparatorRune)
var r []string
for _, str := range pv {
if str != "" {
str = strings.ReplaceAll(str, EscapedPathSeparator, PathSeparator)
// Is str of the form node[expr], convert to "node", "[expr]"?
nBracket := strings.IndexRune(str, '[')
if nBracket > 0 {
r = append(r, str[:nBracket], str[nBracket:])
} else {
// str is "[expr]" or "node"
r = append(r, str)
}
}
}
return r
}
// String converts a string slice path representation of form ["a", "b", "c"] to a string representation like "a.b.c".
func (p Path) String() string {
return strings.Join(p, PathSeparator)
}
func (p Path) Equals(p2 Path) bool {
if len(p) != len(p2) {
return false
}
for i, pp := range p {
if pp != p2[i] {
return false
}
}
return true
}
// ToYAMLPath converts a path string to path such that the first letter of each path element is lower case.
func ToYAMLPath(path string) Path {
p := PathFromString(path)
for i := range p {
p[i] = firstCharToLowerCase(p[i])
}
return p
}
// ToYAMLPathString converts a path string such that the first letter of each path element is lower case.
func ToYAMLPathString(path string) string {
return ToYAMLPath(path).String()
}
// IsValidPathElement reports whether pe is a valid path element.
func IsValidPathElement(pe string) bool {
return ValidKeyRegex.MatchString(pe)
}
// IsKVPathElement report whether pe is a key/value path element.
func IsKVPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
kv := splitEscaped(pe, kvSeparatorRune)
if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 {
return false
}
return IsValidPathElement(kv[0])
}
// IsVPathElement report whether pe is a value path element.
func IsVPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
return len(pe) > 1 && pe[0] == ':'
}
// IsNPathElement report whether pe is an index path element.
func IsNPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
n, err := strconv.Atoi(pe)
return err == nil && n >= InsertIndex
}
// PathKV returns the key and value string parts of the entire key/value path element.
// It returns an error if pe is not a key/value path element.
func PathKV(pe string) (k, v string, err error) {
if !IsKVPathElement(pe) {
return "", "", fmt.Errorf("%s is not a valid key:value path element", pe)
}
pe, _ = RemoveBrackets(pe)
kv := splitEscaped(pe, kvSeparatorRune)
return kv[0], kv[1], nil
}
// PathV returns the value string part of the entire value path element.
// It returns an error if pe is not a value path element.
func PathV(pe string) (string, error) {
// For :val, return the value only
if IsVPathElement(pe) {
v, _ := RemoveBrackets(pe)
return v[1:], nil
}
// For key:val, return the whole thing
v, _ := RemoveBrackets(pe)
if len(v) > 0 {
return v, nil
}
return "", fmt.Errorf("%s is not a valid value path element", pe)
}
// PathN returns the index part of the entire value path element.
// It returns an error if pe is not an index path element.
func PathN(pe string) (int, error) {
if !IsNPathElement(pe) {
return -1, fmt.Errorf("%s is not a valid index path element", pe)
}
v, _ := RemoveBrackets(pe)
return strconv.Atoi(v)
}
// RemoveBrackets removes the [] around pe and returns the resulting string. It returns false if pe is not surrounded
// by [].
func RemoveBrackets(pe string) (string, bool) {
if !strings.HasPrefix(pe, "[") || !strings.HasSuffix(pe, "]") {
return "", false
}
return pe[1 : len(pe)-1], true
}
// splitEscaped splits a string using the rune r as a separator. It does not split on r if it's prefixed by \.
func splitEscaped(s string, r rune) []string {
var prev rune
if len(s) == 0 {
return []string{}
}
prevIdx := 0
var out []string
for i, c := range s {
if c == r && (i == 0 || (i > 0 && prev != '\\')) {
out = append(out, s[prevIdx:i])
prevIdx = i + 1
}
prev = c
}
out = append(out, s[prevIdx:])
return out
}
func firstCharToLowerCase(s string) string {
return strings.ToLower(s[0:1]) + s[1:]
}