Skip to content

Commit

Permalink
tpl: Add limit support to replaceRE
Browse files Browse the repository at this point in the history
Go stdlib doesn't contain a limited replace in the regexp package, but
we can accomplish the same thing with ReplaceAllStringFunc.

Fixes #7586
  • Loading branch information
moorereason authored and bep committed Aug 28, 2020
1 parent 047af7c commit cdfd1c9
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 14 deletions.
11 changes: 10 additions & 1 deletion tpl/strings/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,16 @@ func init() {

ns.AddMethodMapping(ctx.ReplaceRE,
[]string{"replaceRE"},
[][2]string{},
[][2]string{
{
`{{ replaceRE "a+b" "X" "aabbaabbab" }}`,
`XbXbX`,
},
{
`{{ replaceRE "a+b" "X" "aabbaabbab" 1 }}`,
`Xbaabbab`,
},
},
)

ns.AddMethodMapping(ctx.SliceString,
Expand Down
22 changes: 19 additions & 3 deletions tpl/strings/regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ func (ns *Namespace) FindRE(expr string, content interface{}, limit ...interface
}

// ReplaceRE returns a copy of s, replacing all matches of the regular
// expression pattern with the replacement text repl.
func (ns *Namespace) ReplaceRE(pattern, repl, s interface{}) (_ string, err error) {
// expression pattern with the replacement text repl. The number of replacements
// can be limited with an optional fourth parameter.
func (ns *Namespace) ReplaceRE(pattern, repl, s interface{}, n ...interface{}) (_ string, err error) {
sp, err := cast.ToStringE(pattern)
if err != nil {
return
Expand All @@ -63,12 +64,27 @@ func (ns *Namespace) ReplaceRE(pattern, repl, s interface{}) (_ string, err erro
return
}

nn := -1
if len(n) > 0 {
nn, err = cast.ToIntE(n[0])
if err != nil {
return
}
}

re, err := reCache.Get(sp)
if err != nil {
return "", err
}

return re.ReplaceAllString(ss, sr), nil
return re.ReplaceAllStringFunc(ss, func(str string) string {
if nn == 0 {
return str
}

nn -= 1
return re.ReplaceAllString(str, sr)
}), nil
}

// regexpCache represents a cache of regexp objects protected by a mutex.
Expand Down
30 changes: 20 additions & 10 deletions tpl/strings/regexp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestFindRE(t *testing.T) {
}

c.Assert(err, qt.IsNil)
c.Assert(result, qt.DeepEquals, test.expect)
c.Check(result, qt.DeepEquals, test.expect)
}
}

Expand All @@ -58,26 +58,36 @@ func TestReplaceRE(t *testing.T) {
pattern interface{}
repl interface{}
s interface{}
n []interface{}
expect interface{}
}{
{"^https?://([^/]+).*", "$1", "http://gohugo.io/docs", "gohugo.io"},
{"^https?://([^/]+).*", "$2", "http://gohugo.io/docs", ""},
{"(ab)", "AB", "aabbaab", "aABbaAB"},
{"^https?://([^/]+).*", "$1", "http://gohugo.io/docs", nil, "gohugo.io"},
{"^https?://([^/]+).*", "$2", "http://gohugo.io/docs", nil, ""},
{"(ab)", "AB", "aabbaab", nil, "aABbaAB"},
{"(ab)", "AB", "aabbaab", []interface{}{1}, "aABbaab"},
// errors
{"(ab", "AB", "aabb", false}, // invalid re
{tstNoStringer{}, "$2", "http://gohugo.io/docs", false},
{"^https?://([^/]+).*", tstNoStringer{}, "http://gohugo.io/docs", false},
{"^https?://([^/]+).*", "$2", tstNoStringer{}, false},
{"(ab", "AB", "aabb", nil, false}, // invalid re
{tstNoStringer{}, "$2", "http://gohugo.io/docs", nil, false},
{"^https?://([^/]+).*", tstNoStringer{}, "http://gohugo.io/docs", nil, false},
{"^https?://([^/]+).*", "$2", tstNoStringer{}, nil, false},
} {

result, err := ns.ReplaceRE(test.pattern, test.repl, test.s)
var (
result string
err error
)
if len(test.n) > 0 {
result, err = ns.ReplaceRE(test.pattern, test.repl, test.s, test.n...)
} else {
result, err = ns.ReplaceRE(test.pattern, test.repl, test.s)
}

if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
continue
}

c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
c.Check(result, qt.Equals, test.expect)
}
}

0 comments on commit cdfd1c9

Please sign in to comment.