Skip to content

Commit

Permalink
tpl/urls: Add JoinPath template function
Browse files Browse the repository at this point in the history
  • Loading branch information
jmooring authored and bep committed May 19, 2023
1 parent 03cb38e commit 5b3e165
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/content/en/functions/urls.JoinPath.md
@@ -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/
8 changes: 8 additions & 0 deletions tpl/urls/init.go
Expand Up @@ -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
}

Expand Down
35 changes: 35 additions & 0 deletions tpl/urls/urls.go
Expand Up @@ -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
}
35 changes: 35 additions & 0 deletions tpl/urls/urls_test.go
Expand Up @@ -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)
}
}

0 comments on commit 5b3e165

Please sign in to comment.