tpl: Add replaceRE function
authorCameron Moore <moorereason@gmail.com>
Mon, 8 Feb 2016 22:57:52 +0000 (16:57 -0600)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 11 Mar 2016 18:59:18 +0000 (19:59 +0100)
This commit addes a `replaceRE` template function.  Regexp patterns are compiled
once and cached.

docs/content/templates/functions.md
tpl/template_funcs.go
tpl/template_funcs_test.go

index 599e61bda6a9de113faf38ec6cb118ec1c7e401f..8637656e38cdb8424bdd04dd2abf09f5d7179083 100644 (file)
@@ -451,6 +451,13 @@ Replaces all occurrences of the search string with the replacement string.
 e.g. `{{ replace "Batman and Robin" "Robin" "Catwoman" }}` → "Batman and Catwoman"
 
 
+### replaceRE
+Replaces all occurrences of a regular expression with the replacement pattern.
+
+e.g. `{{ replaceRE "^https?://([^/]+).*" "$1" "http://gohugo.io/docs" }}` → "gohugo.io"
+e.g. `{{ "http://gohugo.io/docs" | replaceRE "^https?://([^/]+).*" "$1" }}` → "gohugo.io"
+
+
 ### safeHTML
 Declares the provided string as a "safe" HTML document fragment
 so Go html/template will not filter it.  It should not be used
index 3e21e81d621d0203262fb661fa708a9fa27a720a..8444c592ae4358ce0b0e3cad3143ceb5e6cb43c0 100644 (file)
@@ -29,6 +29,7 @@ import (
        "math/rand"
        "os"
        "reflect"
+       "regexp"
        "sort"
        "strconv"
        "strings"
@@ -1230,6 +1231,37 @@ func replace(a, b, c interface{}) (string, error) {
        return strings.Replace(aStr, bStr, cStr, -1), nil
 }
 
+var regexpCache = make(map[string]*regexp.Regexp)
+
+// replaceRE exposes a regular expression replacement function to the templates.
+func replaceRE(pattern, repl, src interface{}) (_ string, err error) {
+       patternStr, err := cast.ToStringE(pattern)
+       if err != nil {
+               return
+       }
+
+       replStr, err := cast.ToStringE(repl)
+       if err != nil {
+               return
+       }
+
+       srcStr, err := cast.ToStringE(src)
+       if err != nil {
+               return
+       }
+
+       if _, ok := regexpCache[patternStr]; !ok {
+               re, err2 := regexp.Compile(patternStr)
+               if err2 != nil {
+                       return "", err2
+               }
+
+               regexpCache[patternStr] = re
+       }
+
+       return regexpCache[patternStr].ReplaceAllString(srcStr, replStr), err
+}
+
 // dateFormat converts the textual representation of the datetime string into
 // the other form or returns it of the time.Time value. These are formatted
 // with the layout string
@@ -1717,6 +1749,7 @@ func init() {
                "relURL":       func(a string) template.HTML { return template.HTML(helpers.RelURL(a)) },
                "relref":       relRef,
                "replace":      replace,
+               "replaceRE":    replaceRE,
                "safeCSS":      safeCSS,
                "safeHTML":     safeHTML,
                "safeJS":       safeJS,
index 96d0c013b708fee5f8bec727b178ae679766d065..02f5f7ba9ce042c737b369e347e67643ed9b6840 100644 (file)
@@ -1781,6 +1781,27 @@ func TestReplace(t *testing.T) {
        assert.NotNil(t, e, "tstNoStringer cannot be converted to string")
 }
 
+func TestReplaceRE(t *testing.T) {
+       for i, val := range []struct {
+               pattern string
+               repl    string
+               src     string
+               expect  string
+               ok      bool
+       }{
+               {"^https?://([^/]+).*", "$1", "http://gohugo.io/docs", "gohugo.io", true},
+               {"^https?://([^/]+).*", "$2", "http://gohugo.io/docs", "", true},
+               {"(ab)", "AB", "aabbaab", "aABbaAB", true},
+               {"(ab", "AB", "aabb", "", false}, // invalid re
+       } {
+               v, err := replaceRE(val.pattern, val.repl, val.src)
+               if (err == nil) != val.ok {
+                       t.Errorf("[%d] %s", i, err)
+               }
+               assert.Equal(t, val.expect, v)
+       }
+}
+
 func TestTrim(t *testing.T) {
 
        for i, this := range []struct {