summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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()