mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-27 22:09:53 +02:00
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
This commit is contained in:
57
common/hstrings/strings.go
Normal file
57
common/hstrings/strings.go
Normal file
@@ -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
|
||||
}
|
36
common/hstrings/strings_test.go
Normal file
36
common/hstrings/strings_test.go
Normal file
@@ -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)
|
||||
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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{})
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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{
|
||||
|
110
common/urls/baseURL.go
Normal file
110
common/urls/baseURL.go
Normal file
@@ -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
|
||||
}
|
67
common/urls/baseURL_test.go
Normal file
67
common/urls/baseURL_test.go
Normal file
@@ -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")
|
||||
}
|
Reference in New Issue
Block a user