Skip to content

Commit

Permalink
Rework replacer types to allow filtering out the file replacer
Browse files Browse the repository at this point in the history
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
  • Loading branch information
francislavoie and mohammed90 committed Oct 16, 2023
1 parent dd11035 commit a88151f
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 49 deletions.
70 changes: 40 additions & 30 deletions replacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ func NewReplacer() *Replacer {
rep := &Replacer{
static: make(map[string]any),
}
rep.providers = []ReplacerFunc{
globalDefaultReplacements,
fileReplacements,
rep.fromStatic,
rep.providers = []replacementProvider{
globalDefaultReplacementProvider{},
fileReplacementProvider{},
ReplacerFunc(rep.fromStatic),
}
return rep
}
Expand All @@ -47,8 +47,8 @@ func NewEmptyReplacer() *Replacer {
rep := &Replacer{
static: make(map[string]any),
}
rep.providers = []ReplacerFunc{
rep.fromStatic,
rep.providers = []replacementProvider{
ReplacerFunc(rep.fromStatic),
}
return rep
}
Expand All @@ -57,7 +57,7 @@ func NewEmptyReplacer() *Replacer {
// A default/empty Replacer is not valid;
// use NewReplacer to make one.
type Replacer struct {
providers []ReplacerFunc
providers []replacementProvider
static map[string]any
}

Expand All @@ -66,16 +66,12 @@ type Replacer struct {
// may be unsafe in some contexts.
func (r *Replacer) WithoutFile() *Replacer {
rep := &Replacer{static: r.static}

// Add a func at the front of the list that
// always skips file placeholders
rep.providers = append(
[]ReplacerFunc{func(key string) (any, bool) {
return nil, strings.HasPrefix(key, filePrefix)
}},
r.providers...,
)

for _, v := range r.providers {
if _, ok := v.(fileReplacementProvider); ok {
continue
}
rep.providers = append(rep.providers, v)
}
return rep
}

Expand All @@ -94,7 +90,7 @@ func (r *Replacer) Set(variable string, value any) {
// the value and whether the variable was known.
func (r *Replacer) Get(variable string) (any, bool) {
for _, mapFunc := range r.providers {
if val, ok := mapFunc(variable); ok {
if val, ok := mapFunc.replace(variable); ok {
return val, true
}
}
Expand Down Expand Up @@ -309,16 +305,28 @@ func ToString(val any) string {
}
}

// ReplacerFunc is a function that returns a replacement
// for the given key along with true if the function is able
// to service that key (even if the value is blank). If the
// function does not recognize the key, false should be
// returned.
// ReplacerFunc is a function that returns a replacement for the
// given key along with true if the function is able to service
// that key (even if the value is blank). If the function does
// not recognize the key, false should be returned.
type ReplacerFunc func(key string) (any, bool)

// fileReplacements handles {file.*} replacements, reading
// a file from disk and replacing with its contents.
func fileReplacements(key string) (any, bool) {
func (f ReplacerFunc) replace(key string) (any, bool) {
return f(key)
}

// replacementProvider is a type that can provide replacements
// for placeholders. Allows for type assertion to determine
// which type of provider it is.
type replacementProvider interface {
replace(key string) (any, bool)
}

// fileReplacementsProvider handles {file.*} replacements,
// reading a file from disk and replacing with its contents.
type fileReplacementProvider struct{}

func (f fileReplacementProvider) replace(key string) (any, bool) {
if !strings.HasPrefix(key, filePrefix) {
return nil, false
}
Expand All @@ -337,10 +345,12 @@ func fileReplacements(key string) (any, bool) {
return string(body), true
}

// globalDefaultReplacements handles replacements that can
// be used in any context, such as system variables, time,
// or environment variables.
func globalDefaultReplacements(key string) (any, bool) {
// globalDefaultReplacementsProvider handles replacements
// that can be used in any context, such as system variables,
// time, or environment variables.
type globalDefaultReplacementProvider struct{}

func (f globalDefaultReplacementProvider) replace(key string) (any, bool) {
// check environment variable
const envPrefix = "env."
if strings.HasPrefix(key, envPrefix) {
Expand Down
36 changes: 17 additions & 19 deletions replacer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ func TestReplacerSet(t *testing.T) {

func TestReplacerReplaceKnown(t *testing.T) {
rep := Replacer{
providers: []ReplacerFunc{
providers: []replacementProvider{
// split our possible vars to two functions (to test if both functions are called)
func(key string) (val any, ok bool) {
ReplacerFunc(func(key string) (val any, ok bool) {
switch key {
case "test1":
return "val1", true
Expand All @@ -249,8 +249,8 @@ func TestReplacerReplaceKnown(t *testing.T) {
default:
return "NOOO", false
}
},
func(key string) (val any, ok bool) {
}),
ReplacerFunc(func(key string) (val any, ok bool) {
switch key {
case "1":
return "test-123", true
Expand All @@ -261,7 +261,7 @@ func TestReplacerReplaceKnown(t *testing.T) {
default:
return "NOOO", false
}
},
}),
},
}

Expand Down Expand Up @@ -406,7 +406,7 @@ func TestReplacerNew(t *testing.T) {
value: "envtest",
},
} {
if val, ok := repl.providers[0](tc.variable); ok {
if val, ok := repl.providers[0].replace(tc.variable); ok {
if val != tc.value {
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
}
Expand All @@ -425,7 +425,7 @@ func TestReplacerNew(t *testing.T) {
value: "foo",
},
} {
if val, ok := repl.providers[1](tc.variable); ok {
if val, ok := repl.providers[1].replace(tc.variable); ok {
if val != tc.value {
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
}
Expand All @@ -439,27 +439,25 @@ func TestReplacerNewWithoutFile(t *testing.T) {
repl := NewReplacer().WithoutFile()

for _, tc := range []struct {
variable string
value string
expectNil bool
variable string
value string
notFound bool
}{
{
variable: "file.caddytest/integration/testdata/foo.txt",
expectNil: true,
variable: "file.caddytest/integration/testdata/foo.txt",
notFound: true,
},
{
variable: "system.os",
value: runtime.GOOS,
},
} {
if val, ok := repl.Get(tc.variable); ok {
if tc.expectNil && val != nil {
t.Errorf("Expected nil for key '%s' got '%v'", tc.variable, val)
} else if !tc.expectNil && val != tc.value {
if val, ok := repl.Get(tc.variable); ok && !tc.notFound {
if val != tc.value {
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
}
} else {
t.Errorf("Expected key '%s' to be recognized by second provider", tc.variable)
} else if !tc.notFound {
t.Errorf("Expected key '%s' to be recognized", tc.variable)
}
}
}
Expand Down Expand Up @@ -505,7 +503,7 @@ func BenchmarkReplacer(b *testing.B) {

func testReplacer() Replacer {
return Replacer{
providers: make([]ReplacerFunc, 0),
providers: make([]replacementProvider, 0),
static: make(map[string]any),
}
}

0 comments on commit a88151f

Please sign in to comment.