Allow multiple lookup functions for interpolation (#128)

This commit is contained in:
Pieter Noordhuis 2022-12-12 10:48:52 +01:00 committed by GitHub
parent 3f8e233a18
commit 94a86972e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 17 deletions

View File

@ -2,6 +2,7 @@ package interpolation
import (
"context"
"errors"
"fmt"
"reflect"
"regexp"
@ -39,16 +40,23 @@ func (s *stringField) dependsOn() []string {
return out
}
func (s *stringField) interpolate(fn LookupFunction, lookup map[string]string) {
func (s *stringField) interpolate(fns []LookupFunction, lookup map[string]string) {
out := re.ReplaceAllStringFunc(s.Get(), func(s string) string {
// Turn the whole match into the submatch.
match := re.FindStringSubmatch(s)
for _, fn := range fns {
v, err := fn(match[1], lookup)
if errors.Is(err, ErrSkipInterpolation) {
continue
}
if err != nil {
panic(err)
}
return v
}
// No substitution.
return s
})
s.Set(out)
@ -163,7 +171,7 @@ func (a *accumulator) start(v any) {
a.walk([]string{}, rv, nilSetter{})
}
func (a *accumulator) expand(fn LookupFunction) error {
func (a *accumulator) expand(fns ...LookupFunction) error {
for path, v := range a.strings {
ds := v.dependsOn()
if len(ds) == 0 {
@ -176,24 +184,24 @@ func (a *accumulator) expand(fn LookupFunction) error {
return fmt.Errorf("cannot interpolate %s: %w", path, err)
}
v.interpolate(fn, m)
v.interpolate(fns, m)
}
return nil
}
type interpolate struct {
fn LookupFunction
fns []LookupFunction
}
func (m *interpolate) expand(v any) error {
a := accumulator{}
a.start(v)
return a.expand(m.fn)
return a.expand(m.fns...)
}
func Interpolate(fn LookupFunction) bundle.Mutator {
return &interpolate{fn: fn}
func Interpolate(fns ...LookupFunction) bundle.Mutator {
return &interpolate{fns: fns}
}
func (m *interpolate) Name() string {

View File

@ -1,6 +1,7 @@
package interpolation
import (
"errors"
"fmt"
"strings"
@ -10,6 +11,9 @@ import (
// LookupFunction returns the value to rewrite a path expression to.
type LookupFunction func(path string, depends map[string]string) (string, error)
// ErrSkipInterpolation can be used to fall through from [LookupFunction].
var ErrSkipInterpolation = errors.New("skip interpolation")
// DefaultLookup looks up the specified path in the map.
// It returns an error if it doesn't exist.
func DefaultLookup(path string, lookup map[string]string) (string, error) {
@ -29,7 +33,7 @@ func pathPrefixMatches(prefix []string, path string) bool {
func ExcludeLookupsInPath(exclude ...string) LookupFunction {
return func(path string, lookup map[string]string) (string, error) {
if pathPrefixMatches(exclude, path) {
return fmt.Sprintf("${%s}", path), nil
return "", ErrSkipInterpolation
}
return DefaultLookup(path, lookup)
@ -39,10 +43,10 @@ func ExcludeLookupsInPath(exclude ...string) LookupFunction {
// IncludeLookupsInPath is a lookup function that limits lookups to the specified path.
func IncludeLookupsInPath(include ...string) LookupFunction {
return func(path string, lookup map[string]string) (string, error) {
if pathPrefixMatches(include, path) {
return DefaultLookup(path, lookup)
if !pathPrefixMatches(include, path) {
return "", ErrSkipInterpolation
}
return fmt.Sprintf("${%s}", path), nil
return DefaultLookup(path, lookup)
}
}

View File

@ -31,7 +31,9 @@ func fixture() interpolationFixture {
func TestExcludePath(t *testing.T) {
tmp := fixture()
m := interpolate{
fn: ExcludeLookupsInPath("a"),
fns: []LookupFunction{
ExcludeLookupsInPath("a"),
},
}
err := m.expand(&tmp)
@ -46,7 +48,9 @@ func TestExcludePath(t *testing.T) {
func TestIncludePath(t *testing.T) {
tmp := fixture()
m := interpolate{
fn: IncludeLookupsInPath("a"),
fns: []LookupFunction{
IncludeLookupsInPath("a"),
},
}
err := m.expand(&tmp)
@ -57,3 +61,21 @@ func TestIncludePath(t *testing.T) {
assert.Equal(t, "1", tmp.C["ax"])
assert.Equal(t, "${b.x}", tmp.C["bx"])
}
func TestIncludePathMultiple(t *testing.T) {
tmp := fixture()
m := interpolate{
fns: []LookupFunction{
IncludeLookupsInPath("a"),
IncludeLookupsInPath("b"),
},
}
err := m.expand(&tmp)
require.NoError(t, err)
assert.Equal(t, "1", tmp.A["x"])
assert.Equal(t, "2", tmp.B["x"])
assert.Equal(t, "1", tmp.C["ax"])
assert.Equal(t, "2", tmp.C["bx"])
}