tpl: Add limit support to replaceRE
authorCameron Moore <moorereason@gmail.com>
Fri, 28 Aug 2020 14:29:26 +0000 (09:29 -0500)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 28 Aug 2020 16:57:56 +0000 (18:57 +0200)
Go stdlib doesn't contain a limited replace in the regexp package, but
we can accomplish the same thing with ReplaceAllStringFunc.

Fixes #7586

tpl/strings/init.go
tpl/strings/regexp.go
tpl/strings/regexp_test.go

index 09419a51f285ae8b726228c2d7f0c012f693a453..26d63519a0e8e0d307c6335ec4f81fafd005f713 100644 (file)
@@ -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,
index 7b52c9f6e059b14e8c4068fd771c3b331e238696..c6d731a0d6f744209c727e222d5c83b7b7e90737 100644 (file)
@@ -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
@@ -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.
index e05b00fb1000e251e54689f544f9ae325b7ec7cf..433181f67ee846eadc20782e096304e441fdae80 100644 (file)
@@ -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)
        }
 }
 
@@ -58,19 +58,29 @@ 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))
@@ -78,6 +88,6 @@ func TestReplaceRE(t *testing.T) {
                }
 
                c.Assert(err, qt.IsNil)
-               c.Assert(result, qt.Equals, test.expect)
+               c.Check(result, qt.Equals, test.expect)
        }
 }