shithub: hugo

Download patch

ref: cdfd1c99baa22d69e865294dfcd783811f96c880
parent: 047af7cfe5e9aa740b85e0f9974a2d31a0ef4c08
author: Cameron Moore <moorereason@gmail.com>
date: Fri Aug 28 05:29:26 EDT 2020

tpl: Add limit support to replaceRE

Go stdlib doesn't contain a limited replace in the regexp package, but
we can accomplish the same thing with ReplaceAllStringFunc.

Fixes #7586

--- a/tpl/strings/init.go
+++ b/tpl/strings/init.go
@@ -99,7 +99,16 @@
 
 		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,
--- a/tpl/strings/regexp.go
+++ b/tpl/strings/regexp.go
@@ -46,8 +46,9 @@
 }
 
 // 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 @@
 		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.
--- a/tpl/strings/regexp_test.go
+++ b/tpl/strings/regexp_test.go
@@ -46,7 +46,7 @@
 		}
 
 		c.Assert(err, qt.IsNil)
-		c.Assert(result, qt.DeepEquals, test.expect)
+		c.Check(result, qt.DeepEquals, test.expect)
 	}
 }
 
@@ -58,19 +58,29 @@
 		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 @@
 		}
 
 		c.Assert(err, qt.IsNil)
-		c.Assert(result, qt.Equals, test.expect)
+		c.Check(result, qt.Equals, test.expect)
 	}
 }