From b8d3651242947bbea46df8d6a9f0477406070019 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Mon, 8 Feb 2016 16:57:52 -0600 Subject: [PATCH] tpl: Add replaceRE function This commit addes a `replaceRE` template function. Regexp patterns are compiled once and cached. --- docs/content/templates/functions.md | 7 ++++++ tpl/template_funcs.go | 33 +++++++++++++++++++++++++++++ tpl/template_funcs_test.go | 21 ++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index 599e61bda..8637656e3 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -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 diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 3e21e81d6..8444c592a 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -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, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 96d0c013b..02f5f7ba9 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -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 {