summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /common
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'common')
-rw-r--r--common/hstrings/strings.go57
-rw-r--r--common/hstrings/strings_test.go36
-rw-r--r--common/htime/time.go9
-rw-r--r--common/hugo/hugo.go27
-rw-r--r--common/loggers/ignorableLogger.go10
-rw-r--r--common/maps/maps.go35
-rw-r--r--common/maps/maps_test.go8
-rw-r--r--common/maps/params.go98
-rw-r--r--common/maps/params_test.go16
-rw-r--r--common/urls/baseURL.go110
-rw-r--r--common/urls/baseURL_test.go67
11 files changed, 413 insertions, 60 deletions
diff --git a/common/hstrings/strings.go b/common/hstrings/strings.go
new file mode 100644
index 000000000..6c0f820fe
--- /dev/null
+++ b/common/hstrings/strings.go
@@ -0,0 +1,57 @@
+// 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 hstrings
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gohugoio/hugo/compare"
+)
+
+var _ compare.Eqer = StringEqualFold("")
+
+// StringEqualFold is a string that implements the compare.Eqer interface and considers
+// two strings equal if they are equal when folded to lower case.
+// The compare.Eqer interface is used in Hugo to compare values in templates (e.g. using the eq template function).
+type StringEqualFold string
+
+func (s StringEqualFold) EqualFold(s2 string) bool {
+ return strings.EqualFold(string(s), s2)
+}
+
+func (s StringEqualFold) String() string {
+ return string(s)
+}
+
+func (s StringEqualFold) Eq(s2 any) bool {
+ switch ss := s2.(type) {
+ case string:
+ return s.EqualFold(ss)
+ case fmt.Stringer:
+ return s.EqualFold(ss.String())
+ }
+
+ return false
+}
+
+// EqualAny returns whether a string is equal to any of the given strings.
+func EqualAny(a string, b ...string) bool {
+ for _, s := range b {
+ if a == s {
+ return true
+ }
+ }
+ return false
+}
diff --git a/common/hstrings/strings_test.go b/common/hstrings/strings_test.go
new file mode 100644
index 000000000..dc2eae6f2
--- /dev/null
+++ b/common/hstrings/strings_test.go
@@ -0,0 +1,36 @@
+// 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 hstrings
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestStringEqualFold(t *testing.T) {
+ c := qt.New(t)
+
+ s1 := "A"
+ s2 := "a"
+
+ c.Assert(StringEqualFold(s1).EqualFold(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).EqualFold(s1), qt.Equals, true)
+ c.Assert(StringEqualFold(s2).EqualFold(s1), qt.Equals, true)
+ c.Assert(StringEqualFold(s2).EqualFold(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
+ c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)
+
+}
diff --git a/common/htime/time.go b/common/htime/time.go
index d30ecf7e1..961962b60 100644
--- a/common/htime/time.go
+++ b/common/htime/time.go
@@ -14,6 +14,7 @@
package htime
import (
+ "log"
"strings"
"time"
@@ -163,3 +164,11 @@ func Since(t time.Time) time.Duration {
type AsTimeProvider interface {
AsTime(zone *time.Location) time.Time
}
+
+// StopWatch is a simple helper to measure time during development.
+func StopWatch(name string) func() {
+ start := time.Now()
+ return func() {
+ log.Printf("StopWatch %q took %s", name, time.Since(start))
+ }
+}
diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go
index efcb470a3..6402d7b88 100644
--- a/common/hugo/hugo.go
+++ b/common/hugo/hugo.go
@@ -46,8 +46,8 @@ var (
vendorInfo string
)
-// Info contains information about the current Hugo environment
-type Info struct {
+// HugoInfo contains information about the current Hugo environment
+type HugoInfo struct {
CommitHash string
BuildDate string
@@ -64,30 +64,30 @@ type Info struct {
}
// Version returns the current version as a comparable version string.
-func (i Info) Version() VersionString {
+func (i HugoInfo) Version() VersionString {
return CurrentVersion.Version()
}
// Generator a Hugo meta generator HTML tag.
-func (i Info) Generator() template.HTML {
+func (i HugoInfo) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
}
-func (i Info) IsProduction() bool {
+func (i HugoInfo) IsProduction() bool {
return i.Environment == EnvironmentProduction
}
-func (i Info) IsExtended() bool {
+func (i HugoInfo) IsExtended() bool {
return IsExtended
}
// Deps gets a list of dependencies for this Hugo build.
-func (i Info) Deps() []*Dependency {
+func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
// NewInfo creates a new Hugo Info object.
-func NewInfo(environment string, deps []*Dependency) Info {
+func NewInfo(environment string, deps []*Dependency) HugoInfo {
if environment == "" {
environment = EnvironmentProduction
}
@@ -104,7 +104,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
goVersion = bi.GoVersion
}
- return Info{
+ return HugoInfo{
CommitHash: commitHash,
BuildDate: buildDate,
Environment: environment,
@@ -115,7 +115,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
// GetExecEnviron creates and gets the common os/exec environment used in the
// external programs we interact with via os/exec, e.g. postcss.
-func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
+func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []string {
var env []string
nodepath := filepath.Join(workDir, "node_modules")
if np := os.Getenv("NODE_PATH"); np != "" {
@@ -123,10 +123,9 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
}
config.SetEnvVars(&env, "NODE_PATH", nodepath)
config.SetEnvVars(&env, "PWD", workDir)
- config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment"))
- config.SetEnvVars(&env, "HUGO_ENV", cfg.GetString("environment"))
-
- config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.GetString("publishDirOrig")))
+ config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.Environment())
+ config.SetEnvVars(&env, "HUGO_ENV", cfg.Environment())
+ config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.BaseConfig().PublishDir))
if fs != nil {
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
diff --git a/common/loggers/ignorableLogger.go b/common/loggers/ignorableLogger.go
index 5040d1036..c8aba560e 100644
--- a/common/loggers/ignorableLogger.go
+++ b/common/loggers/ignorableLogger.go
@@ -15,7 +15,6 @@ package loggers
import (
"fmt"
- "strings"
)
// IgnorableLogger is a logger that ignores certain log statements.
@@ -31,14 +30,13 @@ type ignorableLogger struct {
}
// NewIgnorableLogger wraps the given logger and ignores the log statement IDs given.
-func NewIgnorableLogger(logger Logger, statements ...string) IgnorableLogger {
- statementsSet := make(map[string]bool)
- for _, s := range statements {
- statementsSet[strings.ToLower(s)] = true
+func NewIgnorableLogger(logger Logger, statements map[string]bool) IgnorableLogger {
+ if statements == nil {
+ statements = make(map[string]bool)
}
return ignorableLogger{
Logger: logger,
- statements: statementsSet,
+ statements: statements,
}
}
diff --git a/common/maps/maps.go b/common/maps/maps.go
index 2d8a122ca..6aefde927 100644
--- a/common/maps/maps.go
+++ b/common/maps/maps.go
@@ -43,25 +43,25 @@ func ToStringMapE(in any) (map[string]any, error) {
// ToParamsAndPrepare converts in to Params and prepares it for use.
// If in is nil, an empty map is returned.
// See PrepareParams.
-func ToParamsAndPrepare(in any) (Params, bool) {
+func ToParamsAndPrepare(in any) (Params, error) {
if types.IsNil(in) {
- return Params{}, true
+ return Params{}, nil
}
m, err := ToStringMapE(in)
if err != nil {
- return nil, false
+ return nil, err
}
PrepareParams(m)
- return m, true
+ return m, nil
}
// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
func MustToParamsAndPrepare(in any) Params {
- if p, ok := ToParamsAndPrepare(in); ok {
- return p
- } else {
- panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
+ p, err := ToParamsAndPrepare(in)
+ if err != nil {
+ panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
}
+ return p
}
// ToStringMap converts in to map[string]interface{}.
@@ -96,6 +96,8 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
switch v := in.(type) {
case []map[string]any:
return v, nil
+ case Params:
+ return []map[string]any{v}, nil
case []any:
var s []map[string]any
for _, entry := range v {
@@ -123,6 +125,23 @@ func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
return s, false
}
+// MergeShallow merges src into dst, but only if the key does not already exist in dst.
+// The keys are compared case insensitively.
+func MergeShallow(dst, src map[string]any) {
+ for k, v := range src {
+ found := false
+ for dk := range dst {
+ if strings.EqualFold(dk, k) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ dst[k] = v
+ }
+ }
+}
+
type keyRename struct {
pattern glob.Glob
newKey string
diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go
index 0b84d2dd7..0e8589d34 100644
--- a/common/maps/maps_test.go
+++ b/common/maps/maps_test.go
@@ -116,11 +116,11 @@ func TestToSliceStringMap(t *testing.T) {
func TestToParamsAndPrepare(t *testing.T) {
c := qt.New(t)
- _, ok := ToParamsAndPrepare(map[string]any{"A": "av"})
- c.Assert(ok, qt.IsTrue)
+ _, err := ToParamsAndPrepare(map[string]any{"A": "av"})
+ c.Assert(err, qt.IsNil)
- params, ok := ToParamsAndPrepare(nil)
- c.Assert(ok, qt.IsTrue)
+ params, err := ToParamsAndPrepare(nil)
+ c.Assert(err, qt.IsNil)
c.Assert(params, qt.DeepEquals, Params{})
}
diff --git a/common/maps/params.go b/common/maps/params.go
index 4bf95f43b..eb60fbbfc 100644
--- a/common/maps/params.go
+++ b/common/maps/params.go
@@ -23,30 +23,37 @@ import (
// Params is a map where all keys are lower case.
type Params map[string]any
-// Get does a lower case and nested search in this map.
+// KeyParams is an utility struct for the WalkParams method.
+type KeyParams struct {
+ Key string
+ Params Params
+}
+
+// GetNested does a lower case and nested search in this map.
// It will return nil if none found.
-func (p Params) Get(indices ...string) any {
+// Make all of these methods internal somehow.
+func (p Params) GetNested(indices ...string) any {
v, _, _ := getNested(p, indices)
return v
}
-// Set overwrites values in p with values in pp for common or new keys.
+// Set overwrites values in dst with values in src for common or new keys.
// This is done recursively.
-func (p Params) Set(pp Params) {
- for k, v := range pp {
- vv, found := p[k]
+func SetParams(dst, src Params) {
+ for k, v := range src {
+ vv, found := dst[k]
if !found {
- p[k] = v
+ dst[k] = v
} else {
switch vvv := vv.(type) {
case Params:
if pv, ok := v.(Params); ok {
- vvv.Set(pv)
+ SetParams(vvv, pv)
} else {
- p[k] = v
+ dst[k] = v
}
default:
- p[k] = v
+ dst[k] = v
}
}
}
@@ -70,18 +77,17 @@ func (p Params) IsZero() bool {
}
-// Merge transfers values from pp to p for new keys.
+// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
// This is done recursively.
-func (p Params) Merge(pp Params) {
- p.merge("", pp)
+func MergeParamsWithStrategy(strategy string, dst, src Params) {
+ dst.merge(ParamsMergeStrategy(strategy), src)
}
-// MergeRoot transfers values from pp to p for new keys where p is the
-// root of the tree.
+// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge encoded in dst.
// This is done recursively.
-func (p Params) MergeRoot(pp Params) {
- ms, _ := p.GetMergeStrategy()
- p.merge(ms, pp)
+func MergeParams(dst, src Params) {
+ ms, _ := dst.GetMergeStrategy()
+ dst.merge(ms, src)
}
func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
@@ -116,6 +122,7 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
}
}
+// For internal use.
func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
if v, found := p[mergeStrategyKey]; found {
if s, ok := v.(ParamsMergeStrategy); ok {
@@ -125,6 +132,7 @@ func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
return ParamsMergeStrategyShallow, false
}
+// For internal use.
func (p Params) DeleteMergeStrategy() bool {
if _, found := p[mergeStrategyKey]; found {
delete(p, mergeStrategyKey)
@@ -133,7 +141,8 @@ func (p Params) DeleteMergeStrategy() bool {
return false
}
-func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
+// For internal use.
+func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
switch s {
case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
default:
@@ -187,7 +196,7 @@ func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error)
keySegments := strings.Split(keyStr, separator)
for _, m := range candidates {
- if v := m.Get(keySegments...); v != nil {
+ if v := m.GetNested(keySegments...); v != nil {
return v, nil
}
}
@@ -236,6 +245,55 @@ const (
mergeStrategyKey = "_merge"
)
+// CleanConfigStringMapString removes any processing instructions from m,
+// m will never be modified.
+func CleanConfigStringMapString(m map[string]string) map[string]string {
+ if m == nil || len(m) == 0 {
+ return m
+ }
+ if _, found := m[mergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]string, len(m)-1)
+ for k, v := range m {
+ if k != mergeStrategyKey {
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// CleanConfigStringMap is the same as CleanConfigStringMapString but for
+// map[string]any.
+func CleanConfigStringMap(m map[string]any) map[string]any {
+ if m == nil || len(m) == 0 {
+ return m
+ }
+ if _, found := m[mergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]any, len(m)-1)
+ for k, v := range m {
+ if k != mergeStrategyKey {
+ m2[k] = v
+ }
+ switch v2 := v.(type) {
+ case map[string]any:
+ m2[k] = CleanConfigStringMap(v2)
+ case Params:
+ var p Params = CleanConfigStringMap(v2)
+ m2[k] = p
+ case map[string]string:
+ m2[k] = CleanConfigStringMapString(v2)
+ }
+
+ }
+ return m2
+
+}
+
func toMergeStrategy(v any) ParamsMergeStrategy {
s := ParamsMergeStrategy(cast.ToString(v))
switch s {
diff --git a/common/maps/params_test.go b/common/maps/params_test.go
index a070e6f60..7e1dbbae7 100644
--- a/common/maps/params_test.go
+++ b/common/maps/params_test.go
@@ -81,7 +81,7 @@ func TestParamsSetAndMerge(t *testing.T) {
p1, p2 := createParamsPair()
- p1.Set(p2)
+ SetParams(p1, p2)
c.Assert(p1, qt.DeepEquals, Params{
"a": "abv",
@@ -97,7 +97,7 @@ func TestParamsSetAndMerge(t *testing.T) {
p1, p2 = createParamsPair()
- p1.Merge(p2)
+ MergeParamsWithStrategy("", p1, p2)
// Default is to do a shallow merge.
c.Assert(p1, qt.DeepEquals, Params{
@@ -111,8 +111,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyNone)
- p1.Merge(p2)
+ p1.SetMergeStrategy(ParamsMergeStrategyNone)
+ MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
@@ -125,8 +125,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyShallow)
- p1.Merge(p2)
+ p1.SetMergeStrategy(ParamsMergeStrategyShallow)
+ MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
@@ -140,8 +140,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyDeep)
- p1.Merge(p2)
+ p1.SetMergeStrategy(ParamsMergeStrategyDeep)
+ MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
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")
+}