databricks-cli/libs/dyn/pattern.go

105 lines
3.0 KiB
Go

package dyn
import (
"fmt"
"maps"
"slices"
)
// Pattern represents a matcher for paths in a [Value] configuration tree.
// It is used by [MapByPattern] to apply a function to the values whose paths match the pattern.
// Every [Path] is a valid [Pattern] that matches a single unique path.
// The reverse is not true; not every [Pattern] is a valid [Path], as patterns may contain wildcards.
type Pattern []patternComponent
// A pattern component can visit a [Value] and recursively call into [visit] for matching elements.
// Fixed components can match a single key or index, while wildcards can match any key or index.
type patternComponent interface {
visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error)
}
// NewPattern returns a new pattern from the given components.
// The individual components may be created with [Key], [Index], or [Any].
func NewPattern(cs ...patternComponent) Pattern {
return cs
}
// NewPatternFromPath returns a new pattern from the given path.
func NewPatternFromPath(p Path) Pattern {
cs := make(Pattern, len(p))
for i, c := range p {
cs[i] = c
}
return cs
}
// Append appends the given components to the pattern.
func (p Pattern) Append(cs ...patternComponent) Pattern {
out := make(Pattern, len(p)+len(cs))
copy(out, p)
copy(out[len(p):], cs)
return out
}
type anyKeyComponent struct{}
// AnyKey returns a pattern component that matches any key.
func AnyKey() patternComponent {
return anyKeyComponent{}
}
// This function implements the patternComponent interface.
func (c anyKeyComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
m, ok := v.AsMap()
if !ok {
return InvalidValue, fmt.Errorf("expected a map at %q, found %s", prefix, v.Kind())
}
m = maps.Clone(m)
for key, value := range m {
var err error
nv, err := visit(value, append(prefix, Key(key)), suffix, opts)
if err != nil {
// Leave the value intact if the suffix pattern didn't match any value.
if IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) {
continue
}
return InvalidValue, err
}
m[key] = nv
}
return NewValue(m, v.Location()), nil
}
type anyIndexComponent struct{}
// AnyIndex returns a pattern component that matches any index.
func AnyIndex() patternComponent {
return anyIndexComponent{}
}
// This function implements the patternComponent interface.
func (c anyIndexComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
s, ok := v.AsSequence()
if !ok {
return InvalidValue, fmt.Errorf("expected a sequence at %q, found %s", prefix, v.Kind())
}
s = slices.Clone(s)
for i, value := range s {
var err error
nv, err := visit(value, append(prefix, Index(i)), suffix, opts)
if err != nil {
// Leave the value intact if the suffix pattern didn't match any value.
if IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) {
continue
}
return InvalidValue, err
}
s[i] = nv
}
return NewValue(s, v.Location()), nil
}