core: Add ReplaceFunc method to Replacer to allow dynamic replacements

This commit is contained in:
Matthew Holt 2019-12-17 16:29:09 -07:00
parent 080a62d5c5
commit fe516575db
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
2 changed files with 38 additions and 13 deletions

View File

@ -27,10 +27,11 @@ import (
type Replacer interface { type Replacer interface {
Set(variable, value string) Set(variable, value string)
Delete(variable string) Delete(variable string)
Map(ReplacementFunc) Map(ReplacerFunc)
ReplaceAll(input, empty string) string ReplaceAll(input, empty string) string
ReplaceKnown(input, empty string) string ReplaceKnown(input, empty string) string
ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error)
ReplaceFunc(input string, f ReplacementFunc) (string, error)
} }
// NewReplacer returns a new Replacer. // NewReplacer returns a new Replacer.
@ -38,7 +39,7 @@ func NewReplacer() Replacer {
rep := &replacer{ rep := &replacer{
static: make(map[string]string), static: make(map[string]string),
} }
rep.providers = []ReplacementFunc{ rep.providers = []ReplacerFunc{
globalDefaultReplacements, globalDefaultReplacements,
rep.fromStatic, rep.fromStatic,
} }
@ -46,13 +47,13 @@ func NewReplacer() Replacer {
} }
type replacer struct { type replacer struct {
providers []ReplacementFunc providers []ReplacerFunc
static map[string]string static map[string]string
} }
// Map adds mapFunc to the list of value providers. // Map adds mapFunc to the list of value providers.
// mapFunc will be executed only at replace-time. // mapFunc will be executed only at replace-time.
func (r *replacer) Map(mapFunc ReplacementFunc) { func (r *replacer) Map(mapFunc ReplacerFunc) {
r.providers = append(r.providers, mapFunc) r.providers = append(r.providers, mapFunc)
} }
@ -77,14 +78,14 @@ func (r *replacer) fromStatic(key string) (val string, ok bool) {
// that are empty or not recognized will cause an error to // that are empty or not recognized will cause an error to
// be returned. // be returned.
func (r *replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) { func (r *replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) {
return r.replace(input, "", false, errOnEmpty, errOnUnknown) return r.replace(input, "", false, errOnEmpty, errOnUnknown, nil)
} }
// ReplaceKnown is like ReplaceAll but only replaces // ReplaceKnown is like ReplaceAll but only replaces
// placeholders that are known (recognized). Unrecognized // placeholders that are known (recognized). Unrecognized
// placeholders will remain in the output. // placeholders will remain in the output.
func (r *replacer) ReplaceKnown(input, empty string) string { func (r *replacer) ReplaceKnown(input, empty string) string {
out, _ := r.replace(input, empty, false, false, false) out, _ := r.replace(input, empty, false, false, false, nil)
return out return out
} }
@ -93,12 +94,21 @@ func (r *replacer) ReplaceKnown(input, empty string) string {
// whether they are recognized or not. Values that are empty // whether they are recognized or not. Values that are empty
// string will be substituted with empty. // string will be substituted with empty.
func (r *replacer) ReplaceAll(input, empty string) string { func (r *replacer) ReplaceAll(input, empty string) string {
out, _ := r.replace(input, empty, true, false, false) out, _ := r.replace(input, empty, true, false, false, nil)
return out return out
} }
// ReplaceFunc calls ReplaceAll efficiently replaces placeholders in input with
// their values. All placeholders are replaced in the output
// whether they are recognized or not. Values that are empty
// string will be substituted with empty.
func (r *replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) {
return r.replace(input, "", true, false, false, f)
}
func (r *replacer) replace(input, empty string, func (r *replacer) replace(input, empty string,
treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool) (string, error) { treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool,
f ReplacementFunc) (string, error) {
if !strings.Contains(input, string(phOpen)) { if !strings.Contains(input, string(phOpen)) {
return input, nil return input, nil
} }
@ -134,6 +144,13 @@ func (r *replacer) replace(input, empty string,
for _, mapFunc := range r.providers { for _, mapFunc := range r.providers {
if val, ok := mapFunc(key); ok { if val, ok := mapFunc(key); ok {
found = true found = true
if f != nil {
var err error
val, err = f(key, val)
if err != nil {
return "", err
}
}
if val == "" { if val == "" {
if errOnEmpty { if errOnEmpty {
return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", return "", fmt.Errorf("evaluated placeholder %s%s%s is empty",
@ -174,12 +191,12 @@ func (r *replacer) replace(input, empty string,
return sb.String(), nil return sb.String(), nil
} }
// ReplacementFunc is a function that returns a replacement // ReplacerFunc is a function that returns a replacement
// for the given key along with true if the function is able // for the given key along with true if the function is able
// to service that key (even if the value is blank). If the // to service that key (even if the value is blank). If the
// function does not recognize the key, false should be // function does not recognize the key, false should be
// returned. // returned.
type ReplacementFunc func(key string) (val string, ok bool) type ReplacerFunc func(key string) (val string, ok bool)
func globalDefaultReplacements(key string) (string, bool) { func globalDefaultReplacements(key string) (string, bool) {
// check environment variable // check environment variable
@ -206,6 +223,14 @@ func globalDefaultReplacements(key string) (string, bool) {
return "", false return "", false
} }
// ReplacementFunc is a function that is called when a
// replacement is being performed. It receives the
// variable (i.e. placeholder name) and the value that
// will be the replacement, and returns the value that
// will actually be the replacement, or an error. Note
// that errors are sometimes ignored by replacers.
type ReplacementFunc func(variable, val string) (string, error)
// nowFunc is a variable so tests can change it // nowFunc is a variable so tests can change it
// in order to obtain a deterministic time. // in order to obtain a deterministic time.
var nowFunc = time.Now var nowFunc = time.Now

View File

@ -133,7 +133,7 @@ func TestReplacerSet(t *testing.T) {
func TestReplacerReplaceKnown(t *testing.T) { func TestReplacerReplaceKnown(t *testing.T) {
rep := replacer{ rep := replacer{
providers: []ReplacementFunc{ providers: []ReplacerFunc{
// split our possible vars to two functions (to test if both functions are called) // split our possible vars to two functions (to test if both functions are called)
func(key string) (val string, ok bool) { func(key string) (val string, ok bool) {
switch key { switch key {
@ -239,7 +239,7 @@ func TestReplacerDelete(t *testing.T) {
func TestReplacerMap(t *testing.T) { func TestReplacerMap(t *testing.T) {
rep := testReplacer() rep := testReplacer()
for i, tc := range []ReplacementFunc{ for i, tc := range []ReplacerFunc{
func(key string) (val string, ok bool) { func(key string) (val string, ok bool) {
return "", false return "", false
}, },
@ -317,7 +317,7 @@ func TestReplacerNew(t *testing.T) {
func testReplacer() replacer { func testReplacer() replacer {
return replacer{ return replacer{
providers: make([]ReplacementFunc, 0), providers: make([]ReplacerFunc, 0),
static: make(map[string]string), static: make(map[string]string),
} }
} }