diff --git a/tpl/internal/templatefuncsRegistry.go b/tpl/internal/templatefuncsRegistry.go index da9aa5d292b..84e0e25faf4 100644 --- a/tpl/internal/templatefuncsRegistry.go +++ b/tpl/internal/templatefuncsRegistry.go @@ -66,6 +66,11 @@ func (t *TemplateFuncsNamespace) AddMethodMapping(m any, aliases []string, examp name := methodToName(m) + // Rewrite §§ to ` in example commands. + for i, e := range examples { + examples[i][0] = strings.ReplaceAll(e[0], "§§", "`") + } + // sanity check for _, e := range examples { if e[0] == "" { diff --git a/tpl/strings/init.go b/tpl/strings/init.go index 0a4d8776267..503ec6a25e2 100644 --- a/tpl/strings/init.go +++ b/tpl/strings/init.go @@ -78,12 +78,22 @@ func init() { []string{"findRE"}, [][2]string{ { - `{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}`, + `{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." 1 }}`, `[go]`, }, }, ) + ns.AddMethodMapping(ctx.FindRESubmatch, + []string{"findRESubmatch"}, + [][2]string{ + { + `{{ findRESubmatch §§(.+?)§§ §§
  • Foo
  • Bar
  • §§ | print | safeHTML }}`, + "[[Foo #foo Foo] [Bar #bar Bar]]", + }, + }, + ) + ns.AddMethodMapping(ctx.HasPrefix, []string{"hasPrefix"}, [][2]string{ diff --git a/tpl/strings/regexp.go b/tpl/strings/regexp.go index 5b6a812d444..84b015ffb31 100644 --- a/tpl/strings/regexp.go +++ b/tpl/strings/regexp.go @@ -45,6 +45,31 @@ func (ns *Namespace) FindRE(expr string, content any, limit ...any) ([]string, e return re.FindAllString(conv, lim), nil } +// FindRESubmatch returns returns a slice of strings holding the text of the leftmost match of the regular expression in s and the matches, if any, of its subexpressions. +// +// By default all matches will be included. The number of matches can be limited with the optional limit parameter. A return value of nil indicates no match. +func (ns *Namespace) FindRESubmatch(expr string, content any, limit ...any) ([][]string, error) { + re, err := reCache.Get(expr) + if err != nil { + return nil, err + } + + conv, err := cast.ToStringE(content) + if err != nil { + return nil, err + } + n := -1 + if len(limit) > 0 { + n, err = cast.ToIntE(limit[0]) + if err != nil { + return nil, err + } + } + + return re.FindAllStringSubmatch(conv, n), nil + +} + // ReplaceRE returns a copy of s, replacing all matches of the regular // expression pattern with the replacement text repl. The number of replacements // can be limited with an optional fourth parameter. diff --git a/tpl/strings/regexp_test.go b/tpl/strings/regexp_test.go index 9ac098c171a..b6ec5cb9ed5 100644 --- a/tpl/strings/regexp_test.go +++ b/tpl/strings/regexp_test.go @@ -50,6 +50,39 @@ func TestFindRE(t *testing.T) { } } +func TestFindRESubmatch(t *testing.T) { + t.Parallel() + c := qt.New(t) + + for _, test := range []struct { + expr string + content any + limit any + expect any + }{ + {`(.+?)`, `
  • Foo
  • Bar
  • `, -1, [][]string{ + {"Foo", "#foo", "Foo"}, + {"Bar", "#bar", "Bar"}, + }}, + // Some simple cases. + {"([G|g]o)", "Hugo is a static site generator written in Go.", -1, [][]string{{"go", "go"}, {"Go", "Go"}}}, + {"([G|g]o)", "Hugo is a static site generator written in Go.", 1, [][]string{{"go", "go"}}}, + + // errors + {"([G|go", "Hugo is a static site generator written in Go.", nil, false}, + {"([G|g]o)", t, nil, false}, + } { + result, err := ns.FindRESubmatch(test.expr, test.content, test.limit) + + if b, ok := test.expect.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil)) + continue + } + + c.Assert(err, qt.IsNil) + c.Check(result, qt.DeepEquals, test.expect) + } +} func TestReplaceRE(t *testing.T) { t.Parallel() c := qt.New(t)