mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
tpl/urls: Add JoinPath template function
See https://pkg.go.dev/net/url#JoinPath Closes #9694
This commit is contained in:
parent
03cb38e6c6
commit
5b3e165bad
4 changed files with 102 additions and 0 deletions
24
docs/content/en/functions/urls.JoinPath.md
Normal file
24
docs/content/en/functions/urls.JoinPath.md
Normal 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/
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue