summaryrefslogtreecommitdiffstats
path: root/common/urls
diff options
context:
space:
mode:
Diffstat (limited to 'common/urls')
-rw-r--r--common/urls/baseURL.go110
-rw-r--r--common/urls/baseURL_test.go67
2 files changed, 177 insertions, 0 deletions
diff --git a/common/urls/baseURL.go b/common/urls/baseURL.go
new file mode 100644
index 000000000..df26730ec
--- /dev/null
+++ b/common/urls/baseURL.go
@@ -0,0 +1,110 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package urls
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+// A BaseURL in Hugo is normally on the form scheme://path, but the
+// form scheme: is also valid (mailto:hugo@rules.com).
+type BaseURL struct {
+ url *url.URL
+ WithPath string
+ WithoutPath string
+ BasePath string
+}
+
+func (b BaseURL) String() string {
+ return b.WithPath
+}
+
+func (b BaseURL) Path() string {
+ return b.url.Path
+}
+
+func (b BaseURL) Port() int {
+ p, _ := strconv.Atoi(b.url.Port())
+ return p
+}
+
+// HostURL returns the URL to the host root without any path elements.
+func (b BaseURL) HostURL() string {
+ return strings.TrimSuffix(b.String(), b.Path())
+}
+
+// WithProtocol returns the BaseURL prefixed with the given protocol.
+// The Protocol is normally of the form "scheme://", i.e. "webcal://".
+func (b BaseURL) WithProtocol(protocol string) (BaseURL, error) {
+ u := b.URL()
+
+ scheme := protocol
+ isFullProtocol := strings.HasSuffix(scheme, "://")
+ isOpaqueProtocol := strings.HasSuffix(scheme, ":")
+
+ if isFullProtocol {
+ scheme = strings.TrimSuffix(scheme, "://")
+ } else if isOpaqueProtocol {
+ scheme = strings.TrimSuffix(scheme, ":")
+ }
+
+ u.Scheme = scheme
+
+ if isFullProtocol && u.Opaque != "" {
+ u.Opaque = "//" + u.Opaque
+ } else if isOpaqueProtocol && u.Opaque == "" {
+ return BaseURL{}, fmt.Errorf("cannot determine BaseURL for protocol %q", protocol)
+ }
+
+ return newBaseURLFromURL(u)
+}
+
+func (b BaseURL) WithPort(port int) (BaseURL, error) {
+ u := b.URL()
+ u.Host = u.Hostname() + ":" + strconv.Itoa(port)
+ return newBaseURLFromURL(u)
+}
+
+// URL returns a copy of the internal URL.
+// The copy can be safely used and modified.
+func (b BaseURL) URL() *url.URL {
+ c := *b.url
+ return &c
+}
+
+func NewBaseURLFromString(b string) (BaseURL, error) {
+ u, err := url.Parse(b)
+ if err != nil {
+ return BaseURL{}, err
+ }
+ return newBaseURLFromURL(u)
+
+}
+
+func newBaseURLFromURL(u *url.URL) (BaseURL, error) {
+ baseURL := BaseURL{url: u, WithPath: u.String()}
+ var baseURLNoPath = baseURL.URL()
+ baseURLNoPath.Path = ""
+ baseURL.WithoutPath = baseURLNoPath.String()
+
+ basePath := u.Path
+ if basePath != "" && basePath != "/" {
+ baseURL.BasePath = basePath
+ }
+
+ return baseURL, nil
+}
diff --git a/common/urls/baseURL_test.go b/common/urls/baseURL_test.go
new file mode 100644
index 000000000..95dc73339
--- /dev/null
+++ b/common/urls/baseURL_test.go
@@ -0,0 +1,67 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package urls
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestBaseURL(t *testing.T) {
+ c := qt.New(t)
+ b, err := NewBaseURLFromString("http://example.com")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com")
+
+ p, err := b.WithProtocol("webcal://")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://example.com")
+
+ p, err = b.WithProtocol("webcal")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://example.com")
+
+ _, err = b.WithProtocol("mailto:")
+ c.Assert(err, qt.Not(qt.IsNil))
+
+ b, err = NewBaseURLFromString("mailto:hugo@rules.com")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "mailto:hugo@rules.com")
+
+ // These are pretty constructed
+ p, err = b.WithProtocol("webcal")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal:hugo@rules.com")
+
+ p, err = b.WithProtocol("webcal://")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://hugo@rules.com")
+
+ // Test with "non-URLs". Some people will try to use these as a way to get
+ // relative URLs working etc.
+ b, err = NewBaseURLFromString("/")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "/")
+
+ b, err = NewBaseURLFromString("")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "")
+
+ // BaseURL with sub path
+ b, err = NewBaseURLFromString("http://example.com/sub")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com/sub")
+ c.Assert(b.HostURL(), qt.Equals, "http://example.com")
+}