diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index 8e3fdd953..7cb749d05 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -474,6 +474,36 @@ Pluralize the given word with a set of common English pluralization rules. e.g. `{{ "cat" | pluralize }}` → "cats" +### findRE +Returns a list of strings that match the regular expression. By default all matches will be included. The number of matches can be limitted with an optional third parameter. + +The example below returns a list of all second level headers (`

`) in the content: + + {{ findRE "(.|\n)*?

" .Content }} + +We can limit the number of matches in that list with a third parameter. Let's say we want to have at most one match (or none if no substring matched): + + {{ findRE "(.|\n)*?" .Content 1 }} + + +`findRe` allows us to build an automatically generated table of contents that could be used for a simple scrollspy: + + {{ $headers := findRE "(.|\n)*?" .Content }} + + {{ if ge (len $headers) 1 }} + + {{ end }} + +First, we try to find all second-level headers and generate a list if at least one header was found. `plainify` strips the HTML and `urlize` converts the header into an a valid URL. + ### replace Replaces all occurrences of the search string with the replacement string. diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index cd7fff26c..c3e807176 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -437,6 +437,22 @@ func first(limit interface{}, seq interface{}) (interface{}, error) { return seqv.Slice(0, limitv).Interface(), nil } +// findRE returns a list of strings that match the regular expression. By default all matches +// will be included. The number of matches can be limitted with an optional third parameter. +func findRE(expr string, content interface{}, limit ...int) ([]string, error) { + re, err := reCache.Get(expr) + if err != nil { + return nil, err + } + + conv := cast.ToString(content) + if len(limit) > 0 { + return re.FindAllString(conv, limit[0]), nil + } + + return re.FindAllString(conv, -1), nil +} + // last returns the last N items in a rangeable list. func last(limit interface{}, seq interface{}) (interface{}, error) { if limit == nil || seq == nil { @@ -1729,6 +1745,7 @@ func init() { "echoParam": returnWhenSet, "emojify": emojify, "eq": eq, + "findRE": findRE, "first": first, "ge": ge, "getCSV": getCSV, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index c16dfb804..7e83542fb 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -89,6 +89,7 @@ delimit: {{ delimit (slice "A" "B" "C") ", " " and " }} div: {{div 6 3}} emojify: {{ "I :heart: Hugo" | emojify }} eq: {{ if eq .Section "blog" }}current{{ end }} +findRE: {{ findRE "[G|g]o" "Hugo is a static side generator written in Go." 1 }} hasPrefix 1: {{ hasPrefix "Hugo" "Hu" }} hasPrefix 2: {{ hasPrefix "Hugo" "Fu" }} in: {{ if in "this string contains a substring" "substring" }}Substring found!{{ end }} @@ -138,6 +139,7 @@ delimit: A, B and C div: 2 emojify: I ❤️ Hugo eq: current +findRE: [go] hasPrefix 1: true hasPrefix 2: false in: Substring found! @@ -1801,6 +1803,30 @@ func TestReplaceRE(t *testing.T) { } } +func TestFindRE(t *testing.T) { + for i, this := range []struct { + expr string + content string + limit int + expect []string + ok bool + }{ + {"[G|g]o", "Hugo is a static side generator written in Go.", 2, []string{"go", "Go"}, true}, + {"[G|g]o", "Hugo is a static side generator written in Go.", -1, []string{"go", "Go"}, true}, + {"[G|g]o", "Hugo is a static side generator written in Go.", 1, []string{"go"}, true}, + {"[G|g]o", "Hugo is a static side generator written in Go.", 0, []string(nil), true}, + {"[G|go", "Hugo is a static side generator written in Go.", 0, []string(nil), false}, + } { + res, err := findRE(this.expr, this.content, this.limit) + + if err != nil && this.ok { + t.Errorf("[%d] returned an unexpected error: %s", i, err) + } + + assert.Equal(t, this.expect, res) + } +} + func TestTrim(t *testing.T) { for i, this := range []struct {