-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
dyn.MapByPattern
to map a function to values with matching paths (
#1266) ## Changes The new `dyn.Pattern` type represents a path pattern that can match one or more paths in a configuration tree. Every `dyn.Path` can be converted to a `dyn.Pattern` that matches only a single path. To accommodate this change, the visit function needed to be modified to take a `dyn.Pattern` suffix. Every component in the pattern implements an interface to work with the visit function. This function can recurse on the visit function for one or more elements of the value being visited. For patterns derived from a `dyn.Path`, it will work as it did before and select the matching element. For the new pattern components (e.g. `dyn.AnyKey` or `dyn.AnyIndex`), it recurses on all the elements in the container. ## Tests Unit tests. Confirmed full coverage for the new code.
- Loading branch information
Showing
7 changed files
with
258 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
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 | ||
} | ||
|
||
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, prefix.Append(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, prefix.Append(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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package dyn_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/databricks/cli/libs/dyn" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewPattern(t *testing.T) { | ||
pat := dyn.NewPattern( | ||
dyn.Key("foo"), | ||
dyn.Index(1), | ||
) | ||
|
||
assert.Len(t, pat, 2) | ||
} | ||
|
||
func TestNewPatternFromPath(t *testing.T) { | ||
path := dyn.NewPath( | ||
dyn.Key("foo"), | ||
dyn.Index(1), | ||
) | ||
|
||
pat1 := dyn.NewPattern(dyn.Key("foo"), dyn.Index(1)) | ||
pat2 := dyn.NewPatternFromPath(path) | ||
assert.Equal(t, pat1, pat2) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters