--- /dev/null
+---
+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/
},
)
+ 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
}
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
+}
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)
+ }
+}