summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Glover <bill@billglover.co.uk>2020-03-08 21:36:59 +0000
committerGitHub <noreply@github.com>2020-03-08 15:36:59 -0600
commit36a6c7daf0f45353efe860e254aa148b7574b04e (patch)
tree4f1aefd250dcbef27a060047663da20a48eb8e91
parentca6e54bbb8ab2bad3cc9192bd4857be7e9501c6c (diff)
Rework Replacer loop to handle escaped braces (#3121)
Fixes #3116 * Rework Replacer loop to ignore escaped braces * Add benchmark tests for replacer * Optimise handling of escaped braces * Handle escaped closing braces * Remove additional check for closing brace This commit removes the additional check for input in which the closing brace appears before the opening brace. This check has been removed for performance reasons as it is deemed an unlikely edge case. * Check for escaped closing braces in placeholder name
-rw-r--r--replacer.go15
-rw-r--r--replacer_test.go113
2 files changed, 127 insertions, 1 deletions
diff --git a/replacer.go b/replacer.go
index aad13e2..8823404 100644
--- a/replacer.go
+++ b/replacer.go
@@ -125,6 +125,14 @@ func (r *Replacer) replace(input, empty string,
// iterate the input to find each placeholder
var lastWriteCursor int
for i := 0; i < len(input); i++ {
+
+ // check for escaped braces
+ if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
+ sb.WriteString(input[lastWriteCursor : i-1])
+ lastWriteCursor = i
+ continue
+ }
+
if input[i] != phOpen {
continue
}
@@ -135,6 +143,11 @@ func (r *Replacer) replace(input, empty string,
continue
}
+ // if necessary look for the first closing brace that is not escaped
+ for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
+ end = strings.Index(input[end+1:], string(phClose)) + end + 1
+ }
+
// write the substring from the last cursor to this point
sb.WriteString(input[lastWriteCursor:i])
@@ -237,4 +250,4 @@ var nowFunc = time.Now
// ReplacerCtxKey is the context key for a replacer.
const ReplacerCtxKey CtxKey = "replacer"
-const phOpen, phClose = '{', '}'
+const phOpen, phClose, phEscape = '{', '}', '\\'
diff --git a/replacer_test.go b/replacer_test.go
index 4b56194..66bb537 100644
--- a/replacer_test.go
+++ b/replacer_test.go
@@ -36,30 +36,86 @@ func TestReplacer(t *testing.T) {
expect: "{",
},
{
+ input: `\{`,
+ expect: `{`,
+ },
+ {
input: "foo{",
expect: "foo{",
},
{
+ input: `foo\{`,
+ expect: `foo{`,
+ },
+ {
input: "foo{bar",
expect: "foo{bar",
},
{
+ input: `foo\{bar`,
+ expect: `foo{bar`,
+ },
+ {
input: "foo{bar}",
expect: "foo",
},
{
+ input: `foo\{bar\}`,
+ expect: `foo{bar}`,
+ },
+ {
input: "}",
expect: "}",
},
{
+ input: `\}`,
+ expect: `\}`,
+ },
+ {
input: "{}",
expect: "",
},
{
+ input: `\{\}`,
+ expect: `{}`,
+ },
+ {
input: `{"json": "object"}`,
expect: "",
},
{
+ input: `\{"json": "object"}`,
+ expect: `{"json": "object"}`,
+ },
+ {
+ input: `\{"json": "object"\}`,
+ expect: `{"json": "object"}`,
+ },
+ {
+ input: `\{"json": "object{bar}"\}`,
+ expect: `{"json": "object"}`,
+ },
+ {
+ input: `\{"json": \{"nested": "object"\}\}`,
+ expect: `{"json": {"nested": "object"}}`,
+ },
+ {
+ input: `\{"json": \{"nested": "{bar}"\}\}`,
+ expect: `{"json": {"nested": ""}}`,
+ },
+ {
+ input: `pre \{"json": \{"nested": "{bar}"\}\}`,
+ expect: `pre {"json": {"nested": ""}}`,
+ },
+ {
+ input: `\{"json": \{"nested": "{bar}"\}\} post`,
+ expect: `{"json": {"nested": ""}} post`,
+ },
+ {
+ input: `pre \{"json": \{"nested": "{bar}"\}\} post`,
+ expect: `pre {"json": {"nested": ""}} post`,
+ },
+ {
input: `{{`,
expect: "{{",
},
@@ -68,10 +124,38 @@ func TestReplacer(t *testing.T) {
expect: "",
},
{
+ input: `{"json": "object"\}`,
+ expect: "",
+ },
+ {
input: `{unknown}`,
empty: "-",
expect: "-",
},
+ {
+ input: `back\slashes`,
+ expect: `back\slashes`,
+ },
+ {
+ input: `double back\\slashes`,
+ expect: `double back\\slashes`,
+ },
+ {
+ input: `placeholder {with \{ brace} in name`,
+ expect: `placeholder in name`,
+ },
+ {
+ input: `placeholder {with \} brace} in name`,
+ expect: `placeholder in name`,
+ },
+ {
+ input: `placeholder {with \} \} braces} in name`,
+ expect: `placeholder in name`,
+ },
+ {
+ input: `\{'group':'default','max_age':3600,'endpoints':[\{'url':'https://some.domain.local/a/d/g'\}],'include_subdomains':true\}`,
+ expect: `{'group':'default','max_age':3600,'endpoints':[{'url':'https://some.domain.local/a/d/g'}],'include_subdomains':true}`,
+ },
} {
actual := rep.ReplaceAll(tc.input, tc.empty)
if actual != tc.expect {
@@ -81,6 +165,35 @@ func TestReplacer(t *testing.T) {
}
}
+func BenchmarkReplacer(b *testing.B) {
+ type testCase struct {
+ name, input, empty string
+ }
+
+ rep := testReplacer()
+
+ for _, bm := range []testCase{
+ {
+ name: "no placeholder",
+ input: `simple string`,
+ },
+ {
+ name: "placeholder",
+ input: `{"json": "object"}`,
+ },
+ {
+ name: "escaped placeholder",
+ input: `\{"json": \{"nested": "{bar}"\}\}`,
+ },
+ } {
+ b.Run(bm.name, func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ rep.ReplaceAll(bm.input, bm.empty)
+ }
+ })
+ }
+}
+
func TestReplacerSet(t *testing.T) {
rep := testReplacer()