tpl/urls: Add JoinPath template function

See https://pkg.go.dev/net/url#JoinPath

Closes #9694
This commit is contained in:
Joe Mooring 2023-04-14 13:27:16 -07:00 committed by Bjørn Erik Pedersen
parent 03cb38e6c6
commit 5b3e165bad
4 changed files with 102 additions and 0 deletions

View file

@ -0,0 +1,24 @@
---
title: urls.JoinPath
description: Joins the provided elements into a URL string and cleans the result of any ./ or ../ elements.
categories: [functions]
menu:
docs:
parent: functions
keywords: [urls,path,join]
signature: ["urls.JoinPath ELEMENT..."]
---
```go-html-template
{{ urls.JoinPath "" }} → "/"
{{ urls.JoinPath "a" }} → "a"
{{ urls.JoinPath "a" "b" }} → "a/b"
{{ urls.JoinPath "/a" "b" }} → "/a/b"
{{ urls.JoinPath "https://example.org" "b" }} → "https://example.org/b"
{{ urls.JoinPath (slice "a" "b") }} → "a/b"
```
Unlike the [`path.Join`] function, `urls.JoinPath` retains consecutive leading slashes.
[`path.Join`]: /functions/path.join/

View file

@ -68,6 +68,14 @@ func init() {
},
)
ns.AddMethodMapping(ctx.JoinPath,
nil,
[][2]string{
{`{{ urls.JoinPath "https://example.org" "foo" }}`, `https://example.org/foo`},
{`{{ urls.JoinPath (slice "a" "b") }}`, `a/b`},
},
)
return ns
}

View file

@ -185,3 +185,38 @@ func (ns *Namespace) AbsLangURL(s any) (template.HTML, error) {
return template.HTML(ns.deps.PathSpec.AbsURL(ss, !ns.multihost)), nil
}
// JoinPath joins the provided elements into a URL string and cleans the result
// of any ./ or ../ elements.
func (ns *Namespace) JoinPath(elements ...any) (string, error) {
var selements []string
for _, e := range elements {
switch v := e.(type) {
case []string:
for _, e := range v {
selements = append(selements, e)
}
case []any:
for _, e := range v {
se, err := cast.ToStringE(e)
if err != nil {
return "", err
}
selements = append(selements, se)
}
default:
se, err := cast.ToStringE(e)
if err != nil {
return "", err
}
selements = append(selements, se)
}
}
result, err := url.JoinPath(selements[0], selements[1:]...)
if err != nil {
return "", err
}
return result, nil
}

View file

@ -69,3 +69,38 @@ func TestParse(t *testing.T) {
qt.CmpEquals(hqt.DeepAllowUnexported(&url.URL{}, url.Userinfo{})), test.expect)
}
}
func TestJoinPath(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
elements any
expect any
}{
{"", `/`},
{"a", `a`},
{"/a/b", `/a/b`},
{"./../a/b", `a/b`},
{[]any{""}, `/`},
{[]any{"a"}, `a`},
{[]any{"/a", "b"}, `/a/b`},
{[]any{".", "..", "/a", "b"}, `a/b`},
{[]any{"https://example.org", "a"}, `https://example.org/a`},
{[]any{nil}, `/`},
// errors
{tstNoStringer{}, false},
{[]any{tstNoStringer{}}, false},
} {
result, err := ns.JoinPath(test.elements)
if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
}
}