113 lines
2.2 KiB
Go
113 lines
2.2 KiB
Go
package vars
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
|
|
|
|
//counterfeiter:generate . Variables
|
|
type Variables interface {
|
|
Get(Reference) (interface{}, bool, error)
|
|
List() ([]Reference, error)
|
|
}
|
|
|
|
type Reference struct {
|
|
Source string
|
|
Path string
|
|
Fields []string
|
|
}
|
|
|
|
func (r Reference) WithoutSource() Reference {
|
|
return Reference{
|
|
Path: r.Path,
|
|
Fields: r.Fields,
|
|
}
|
|
}
|
|
|
|
func ParseReference(name string) (Reference, error) {
|
|
var ref Reference
|
|
|
|
input := name
|
|
if i, ok := findUnquoted(input, ':'); ok {
|
|
ref.Source = input[:i]
|
|
if strings.ContainsAny(ref.Source, `"`) {
|
|
return Reference{}, fmt.Errorf("invalid var '%s': source must not be quoted", name)
|
|
}
|
|
input = input[i+1:]
|
|
}
|
|
var fields []string
|
|
hasNextSegment := true
|
|
for hasNextSegment {
|
|
var field string
|
|
field, input, hasNextSegment = readPathSegment(input)
|
|
if field == "" {
|
|
return Reference{}, fmt.Errorf("invalid var '%s': empty field", name)
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
|
|
if len(fields) == 0 {
|
|
// Should be impossible (since we'd error that the var is empty), but better safe than sorry
|
|
return Reference{}, fmt.Errorf("invalid var '%s': no fields", name)
|
|
}
|
|
|
|
ref.Path = fields[0]
|
|
ref.Fields = fields[1:]
|
|
|
|
return ref, nil
|
|
}
|
|
|
|
func findUnquoted(s string, r rune) (int, bool) {
|
|
quoted := false
|
|
for i, c := range s {
|
|
switch c {
|
|
case r:
|
|
if !quoted {
|
|
return i, true
|
|
}
|
|
case '"':
|
|
quoted = !quoted
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func readPathSegment(raw string) (string, string, bool) {
|
|
var field string
|
|
var rest string
|
|
i, hasNextSegment := findUnquoted(raw, '.')
|
|
if hasNextSegment {
|
|
field = raw[:i]
|
|
rest = raw[i+1:]
|
|
} else {
|
|
field = raw
|
|
}
|
|
field = strings.TrimSpace(field)
|
|
field = strings.ReplaceAll(field, `"`, "")
|
|
return field, rest, hasNextSegment
|
|
}
|
|
|
|
func (r Reference) String() string {
|
|
var s strings.Builder
|
|
if r.Source != "" {
|
|
s.WriteString(r.Source + ":")
|
|
}
|
|
s.WriteString(refSegmentString(r.Path))
|
|
fields := r.Fields
|
|
for len(fields) > 0 {
|
|
s.WriteRune('.')
|
|
s.WriteString(refSegmentString(fields[0]))
|
|
fields = fields[1:]
|
|
}
|
|
return s.String()
|
|
}
|
|
|
|
func refSegmentString(seg string) string {
|
|
if strings.ContainsAny(seg, ",.:/ ") {
|
|
return fmt.Sprintf("%q", seg)
|
|
}
|
|
return seg
|
|
}
|