From c4b6f239cea640f7ac8764a29905c39cd1cd327d Mon Sep 17 00:00:00 2001 From: Jens Erat Date: Thu, 29 Dec 2022 11:54:31 +0100 Subject: [PATCH] Support default values in placeholders Support variable expansion also in placeholders, like in `{unsetPlaceholder:default value}`. Implemented similarly to #3682, which provided defaults for environment variable expansion. Closes #1793. Signed-off-by: Jens Erat --- replacer.go | 40 +++++++++++++++++++++++++++------------- replacer_test.go | 25 ++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/replacer.go b/replacer.go index 7f97f34723d..759531f02df 100644 --- a/replacer.go +++ b/replacer.go @@ -188,27 +188,39 @@ scan: sb.WriteString(input[lastWriteCursor:i]) // trim opening bracket - key := input[i+1 : end] + keyString := input[i+1 : end] + + // split the string into a key and an optional default + keyParts := strings.SplitN(string(keyString), varDefaultDelimiter, 2) // try to get a value for this key, handle empty values accordingly - val, found := r.Get(key) + val, found := r.Get(keyParts[0]) if !found { - // placeholder is unknown (unrecognized); handle accordingly - if errOnUnknown { - return "", fmt.Errorf("unrecognized placeholder %s%s%s", - string(phOpen), key, string(phClose)) - } else if !treatUnknownAsEmpty { - // if treatUnknownAsEmpty is true, we'll handle an empty - // val later; so only continue otherwise - lastWriteCursor = i - continue + // replace with variable default, if one is defined + if len(keyParts) == 2 { + val = keyParts[1] + if val == "" { + return "", fmt.Errorf("evaluated placeholder %s%s%s and default are empty", + string(phOpen), keyString, string(phClose)) + } + } else { + // placeholder is unknown (unrecognized); handle accordingly + if errOnUnknown { + return "", fmt.Errorf("unrecognized placeholder %s%s%s", + string(phOpen), keyParts[0], string(phClose)) + } else if !treatUnknownAsEmpty { + // if treatUnknownAsEmpty is true, we'll handle an empty + // val later; so only continue otherwise + lastWriteCursor = i + continue + } } } // apply any transformations if f != nil { var err error - val, err = f(key, val) + val, err = f(keyParts[0], val) if err != nil { return "", err } @@ -222,7 +234,7 @@ scan: if valStr == "" { if errOnEmpty { return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", - string(phOpen), key, string(phClose)) + string(phOpen), keyParts[0], string(phClose)) } else if empty != "" { sb.WriteString(empty) } @@ -345,3 +357,5 @@ var nowFunc = time.Now const ReplacerCtxKey CtxKey = "replacer" const phOpen, phClose, phEscape = '{', '}', '\\' + +const varDefaultDelimiter = ":" diff --git a/replacer_test.go b/replacer_test.go index 41ada7d6da0..aacbb83263a 100644 --- a/replacer_test.go +++ b/replacer_test.go @@ -79,9 +79,13 @@ func TestReplacer(t *testing.T) { input: `\{\}`, expect: `{}`, }, - { + { // TODO currently failing, we might split using a regex or check last character of actual placeholder key not to be a backslash + input: `{"json"\: "object"}`, + expect: ``, + }, + { // TODO currently not failing, is this expected behavior? I guess users would just need to escape the colon in future, see test above input: `{"json": "object"}`, - expect: "", + expect: ` "object"`, // placeholder defaulting happened }, { input: `\{"json": "object"}`, @@ -123,7 +127,7 @@ func TestReplacer(t *testing.T) { input: `{{}`, expect: "", }, - { + { // TODO currently failing, is this a parser bug? not a finished placeholder sequence input: `{"json": "object"\}`, expect: "", }, @@ -294,6 +298,21 @@ func TestReplacerReplaceKnown(t *testing.T) { testInput: "{test1} {nope} {1} ", expected: "val1 {nope} test-123 ", }, + { + // test with default + testInput: "{nope} {nope:default} {test1:default}", + expected: "{nope} default val1", + }, + { + // test with empty default + testInput: "{nope:}", + expected: "", + }, + { + // should not chain variable expands + testInput: "{nope:$test1}", + expected: "$test1", + }, } { actual := rep.ReplaceKnown(tc.testInput, "EMPTY")