Misc config loading fixes

The main motivation behind this is simplicity and correctnes, but the new small config library is also faster:

```
BenchmarkDefaultConfigProvider/Viper-16         	  252418	      4546 ns/op	    2720 B/op	      30 allocs/op
BenchmarkDefaultConfigProvider/Custom-16        	  450756	      2651 ns/op	    1008 B/op	       6 allocs/op
```

Fixes #8633
Fixes #8618
Fixes #8630
Updates #8591
Closes #6680
Closes #5192
This commit is contained in:
Bjørn Erik Pedersen 2021-06-09 10:58:18 +02:00
parent a886dd53b8
commit d392893cd7
No known key found for this signature in database
GPG key ID: 330E6E2BD4859D8F
107 changed files with 2159 additions and 1060 deletions

View file

@ -19,6 +19,8 @@ import (
"strings"
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
@ -123,6 +125,9 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
if _, ok := v.(maps.Params); !ok {
continue
}
cc := defaultCacheConfig
dc := &mapstructure.DecoderConfig{
@ -137,7 +142,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}
if err := decoder.Decode(v); err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to decode filecache config")
}
if cc.Dir == "" {

View file

@ -25,7 +25,6 @@ import (
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestDecodeConfig(t *testing.T) {
@ -178,8 +177,8 @@ dir = "/"
c.Assert(err, qt.Not(qt.IsNil))
}
func newTestConfig() *viper.Viper {
cfg := viper.New()
func newTestConfig() config.Provider {
cfg := config.New()
cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data")

View file

@ -410,7 +410,5 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
config.Set("cacheDir", cacheDir)
cfg.Logger.Infoln("Using config file:", config.ConfigFileUsed())
return nil
}

View file

@ -20,6 +20,8 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero"
@ -29,7 +31,6 @@ import (
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
@ -166,7 +167,7 @@ func TestFlags(t *testing.T) {
name: "ignoreVendor as bool",
args: []string{"server", "--ignoreVendor"},
check: func(c *qt.C, cmd *serverCmd) {
cfg := viper.New()
cfg := config.New()
cmd.flagsToConfig(cfg)
c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true)
},
@ -176,7 +177,7 @@ func TestFlags(t *testing.T) {
name: "ignoreVendorPaths",
args: []string{"server", "--ignoreVendorPaths=github.com/**"},
check: func(c *qt.C, cmd *serverCmd) {
cfg := viper.New()
cfg := config.New()
cmd.flagsToConfig(cfg)
c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
},
@ -216,7 +217,7 @@ func TestFlags(t *testing.T) {
c.Assert(sc.serverPort, qt.Equals, 1366)
c.Assert(sc.environment, qt.Equals, "testing")
cfg := viper.New()
cfg := config.New()
sc.flagsToConfig(cfg)
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")

View file

@ -22,13 +22,14 @@ import (
"sort"
"strings"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var _ cmder = (*configCmd)(nil)
@ -81,7 +82,7 @@ func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
return err
}
allSettings := cfg.Cfg.(*viper.Viper).AllSettings()
allSettings := cfg.Cfg.Get("").(maps.Params)
// We need to clean up this, but we store objects in the config that
// isn't really interesting to the end user, so filter these.

View file

@ -19,6 +19,7 @@ import (
"path/filepath"
"strings"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/parser/metadecoders"
_errors "github.com/pkg/errors"
@ -29,7 +30,6 @@ import (
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
var _ cmder = (*newSiteCmd)(nil)
@ -123,7 +123,7 @@ func (n *newSiteCmd) newSite(cmd *cobra.Command, args []string) error {
forceNew, _ := cmd.Flags().GetBool("force")
return n.doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew)
return n.doNewSite(hugofs.NewDefault(config.New()), createpath, forceNew)
}
func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {

View file

@ -22,10 +22,10 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestServer(t *testing.T) {
@ -101,7 +101,7 @@ func TestFixURL(t *testing.T) {
t.Run(test.TestName, func(t *testing.T) {
b := newCommandsBuilder()
s := b.newServerCmd()
v := viper.New()
v := config.New()
baseURL := test.CLIBaseURL
v.Set("baseURL", test.CfgBaseURL)
s.serverAppend = test.AppendPort

View file

@ -18,53 +18,65 @@ import (
"strings"
"github.com/gobwas/glob"
"github.com/spf13/cast"
)
// ToLower makes all the keys in the given map lower cased and will do so
// recursively.
// Notes:
// * This will modify the map given.
// * Any nested map[interface{}]interface{} will be converted to Params.
func ToLower(m Params) {
for k, v := range m {
var retyped bool
switch v.(type) {
case map[interface{}]interface{}:
var p Params = cast.ToStringMap(v)
v = p
ToLower(p)
retyped = true
case map[string]interface{}:
var p Params = v.(map[string]interface{})
v = p
ToLower(p)
retyped = true
}
lKey := strings.ToLower(k)
if retyped || k != lKey {
delete(m, k)
m[lKey] = v
}
}
}
// ToStringMapE converts in to map[string]interface{}.
func ToStringMapE(in interface{}) (map[string]interface{}, error) {
switch in.(type) {
switch vv := in.(type) {
case Params:
return in.(Params), nil
return vv, nil
case map[string]string:
var m = map[string]interface{}{}
for k, v := range vv {
m[k] = v
}
return m, nil
default:
return cast.ToStringMapE(in)
}
}
// ToParamsAndPrepare converts in to Params and prepares it for use.
// See PrepareParams.
func ToParamsAndPrepare(in interface{}) (Params, bool) {
m, err := ToStringMapE(in)
if err != nil {
return nil, false
}
PrepareParams(m)
return m, true
}
// ToStringMap converts in to map[string]interface{}.
func ToStringMap(in interface{}) map[string]interface{} {
m, _ := ToStringMapE(in)
return m
}
// ToStringMapStringE converts in to map[string]string.
func ToStringMapStringE(in interface{}) (map[string]string, error) {
m, err := ToStringMapE(in)
if err != nil {
return nil, err
}
return cast.ToStringMapStringE(m)
}
// ToStringMapString converts in to map[string]string.
func ToStringMapString(in interface{}) map[string]string {
m, _ := ToStringMapStringE(in)
return m
}
// ToStringMapBool converts in to bool.
func ToStringMapBool(in interface{}) map[string]bool {
m, _ := ToStringMapE(in)
return cast.ToStringMapBool(m)
}
// ToSliceStringMap converts in to []map[string]interface{}.
func ToSliceStringMap(in interface{}) ([]map[string]interface{}, error) {
switch v := in.(type) {
case []map[string]interface{}:
@ -127,9 +139,8 @@ func (KeyRenamer) keyPath(k1, k2 string) string {
k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
if k1 == "" {
return k2
} else {
return k1 + "/" + k2
}
return k1 + "/" + k2
}
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {

View file

@ -67,7 +67,7 @@ func TestToLower(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
// ToLower modifies input.
ToLower(test.input)
PrepareParams(test.input)
if !reflect.DeepEqual(test.expected, test.input) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
}

View file

@ -14,6 +14,7 @@
package maps
import (
"fmt"
"strings"
"github.com/spf13/cast"
@ -29,6 +30,95 @@ func (p Params) Get(indices ...string) interface{} {
return v
}
// Set overwrites values in p with values in pp for common or new keys.
// This is done recursively.
func (p Params) Set(pp Params) {
for k, v := range pp {
vv, found := p[k]
if !found {
p[k] = v
} else {
switch vvv := vv.(type) {
case Params:
if pv, ok := v.(Params); ok {
vvv.Set(pv)
} else {
p[k] = v
}
default:
p[k] = v
}
}
}
}
// Merge transfers values from pp to p for new keys.
// This is done recursively.
func (p Params) Merge(pp Params) {
p.merge("", pp)
}
func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
ns, found := p.GetMergeStrategy()
var ms = ns
if !found && ps != "" {
ms = ps
}
noUpdate := ms == ParamsMergeStrategyNone
noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
for k, v := range pp {
if k == mergeStrategyKey {
continue
}
vv, found := p[k]
if found {
// Key matches, if both sides are Params, we try to merge.
if vvv, ok := vv.(Params); ok {
if pv, ok := v.(Params); ok {
vvv.merge(ms, pv)
}
}
} else if !noUpdate {
p[k] = v
}
}
}
func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
if v, found := p[mergeStrategyKey]; found {
if s, ok := v.(ParamsMergeStrategy); ok {
return s, true
}
}
return ParamsMergeStrategyShallow, false
}
func (p Params) DeleteMergeStrategy() bool {
if _, found := p[mergeStrategyKey]; found {
delete(p, mergeStrategyKey)
return true
}
return false
}
func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
switch s {
case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
default:
panic(fmt.Sprintf("invalid merge strategy %q", s))
}
p[mergeStrategyKey] = s
}
func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) {
if len(indices) == 0 {
return nil, "", nil
@ -108,3 +198,61 @@ func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interf
return nil, "", nil, nil
}
// ParamsMergeStrategy tells what strategy to use in Params.Merge.
type ParamsMergeStrategy string
const (
// Do not merge.
ParamsMergeStrategyNone ParamsMergeStrategy = "none"
// Only add new keys.
ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
// Add new keys, merge existing.
ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
mergeStrategyKey = "_merge"
)
func toMergeStrategy(v interface{}) ParamsMergeStrategy {
s := ParamsMergeStrategy(cast.ToString(v))
switch s {
case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
return s
default:
return ParamsMergeStrategyDeep
}
}
// PrepareParams
// * makes all the keys in the given map lower cased and will do so
// * This will modify the map given.
// * Any nested map[interface{}]interface{} will be converted to Params.
// * Any _merge value will be converted to proper type and value.
func PrepareParams(m Params) {
for k, v := range m {
var retyped bool
lKey := strings.ToLower(k)
if lKey == mergeStrategyKey {
v = toMergeStrategy(v)
retyped = true
} else {
switch v.(type) {
case map[interface{}]interface{}:
var p Params = cast.ToStringMap(v)
v = p
PrepareParams(p)
retyped = true
case map[string]interface{}:
var p Params = v.(map[string]interface{})
v = p
PrepareParams(p)
retyped = true
}
}
if retyped || k != lKey {
delete(m, k)
m[lKey] = v
}
}
}

View file

@ -69,3 +69,90 @@ func TestGetNestedParamFnNestedNewKey(t *testing.T) {
c.Assert(nestedKey, qt.Equals, "new")
c.Assert(owner, qt.DeepEquals, nested)
}
func TestParamsSetAndMerge(t *testing.T) {
c := qt.New(t)
createParamsPair := func() (Params, Params) {
p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, mergeStrategyKey: ParamsMergeStrategyDeep}
return p1, p2
}
p1, p2 := createParamsPair()
p1.Set(p2)
c.Assert(p1, qt.DeepEquals, Params{
"a": "abv",
"c": "cv",
"nested": Params{
"al2": "al2bv",
"cl2": "cl2v",
"bl2": "bl2v",
},
"b": "bv",
mergeStrategyKey: ParamsMergeStrategyDeep,
})
p1, p2 = createParamsPair()
p1.Merge(p2)
// Default is to do a shallow merge.
c.Assert(p1, qt.DeepEquals, Params{
"c": "cv",
"nested": Params{
"al2": "al2v",
"cl2": "cl2v",
},
"b": "bv",
"a": "av",
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyNone)
p1.Merge(p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
"a": "av",
"c": "cv",
"nested": Params{
"al2": "al2v",
"cl2": "cl2v",
},
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyShallow)
p1.Merge(p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
"a": "av",
"c": "cv",
"nested": Params{
"al2": "al2v",
"cl2": "cl2v",
},
"b": "bv",
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyDeep)
p1.Merge(p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
"nested": Params{
"al2": "al2v",
"cl2": "cl2v",
"bl2": "bl2v",
},
"b": "bv",
"a": "av",
"c": "cv",
})
}

View file

@ -21,14 +21,12 @@ import (
"github.com/gohugoio/hugo/common/types"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestBuild(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := New()
v.Set("build", map[string]interface{}{
"useResourceCacheWhen": "always",
})

113
config/compositeConfig.go Normal file
View file

@ -0,0 +1,113 @@
// Copyright 2021 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 config
import (
"github.com/gohugoio/hugo/common/maps"
)
// NewCompositeConfig creates a new composite Provider with a read-only base
// and a writeable layer.
func NewCompositeConfig(base, layer Provider) Provider {
return &compositeConfig{
base: base,
layer: layer,
}
}
// compositeConfig contains a read only config base with
// a possibly writeable config layer on top.
type compositeConfig struct {
base Provider
layer Provider
}
func (c *compositeConfig) GetBool(key string) bool {
if c.layer.IsSet(key) {
return c.layer.GetBool(key)
}
return c.base.GetBool(key)
}
func (c *compositeConfig) GetInt(key string) int {
if c.layer.IsSet(key) {
return c.layer.GetInt(key)
}
return c.base.GetInt(key)
}
func (c *compositeConfig) Merge(key string, value interface{}) {
c.layer.Merge(key, value)
}
func (c *compositeConfig) GetParams(key string) maps.Params {
if c.layer.IsSet(key) {
return c.layer.GetParams(key)
}
return c.base.GetParams(key)
}
func (c *compositeConfig) GetStringMap(key string) map[string]interface{} {
if c.layer.IsSet(key) {
return c.layer.GetStringMap(key)
}
return c.base.GetStringMap(key)
}
func (c *compositeConfig) GetStringMapString(key string) map[string]string {
if c.layer.IsSet(key) {
return c.layer.GetStringMapString(key)
}
return c.base.GetStringMapString(key)
}
func (c *compositeConfig) GetStringSlice(key string) []string {
if c.layer.IsSet(key) {
return c.layer.GetStringSlice(key)
}
return c.base.GetStringSlice(key)
}
func (c *compositeConfig) Get(key string) interface{} {
if c.layer.IsSet(key) {
return c.layer.Get(key)
}
return c.base.Get(key)
}
func (c *compositeConfig) IsSet(key string) bool {
if c.layer.IsSet(key) {
return true
}
return c.base.IsSet(key)
}
func (c *compositeConfig) GetString(key string) string {
if c.layer.IsSet(key) {
return c.layer.GetString(key)
}
return c.base.GetString(key)
}
func (c *compositeConfig) Set(key string, value interface{}) {
c.layer.Set(key, value)
}
func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
panic("not supported")
}
func (c *compositeConfig) SetDefaultMergeStrategy() {
panic("not supported")
}

View file

@ -0,0 +1,40 @@
// Copyright 2021 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 config
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestCompositeConfig(t *testing.T) {
c := qt.New(t)
c.Run("Set and get", func(c *qt.C) {
base, layer := New(), New()
cfg := NewCompositeConfig(base, layer)
layer.Set("a1", "av")
base.Set("b1", "bv")
cfg.Set("c1", "cv")
c.Assert(cfg.Get("a1"), qt.Equals, "av")
c.Assert(cfg.Get("b1"), qt.Equals, "bv")
c.Assert(cfg.Get("c1"), qt.Equals, "cv")
c.Assert(cfg.IsSet("c1"), qt.IsTrue)
c.Assert(layer.IsSet("c1"), qt.IsTrue)
c.Assert(base.IsSet("c1"), qt.IsFalse)
})
}

View file

@ -20,7 +20,6 @@ import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
var (
@ -43,15 +42,11 @@ func IsValidConfigFilename(filename string) bool {
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
func FromConfigString(config, configType string) (Provider, error) {
v := newViper()
m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
if err != nil {
return nil, err
}
v.MergeConfigMap(m)
return v, nil
return NewFrom(m), nil
}
// FromFile loads the configuration from the given filename.
@ -60,15 +55,7 @@ func FromFile(fs afero.Fs, filename string) (Provider, error) {
if err != nil {
return nil, err
}
v := newViper()
err = v.MergeConfigMap(m)
if err != nil {
return nil, err
}
return v, nil
return NewFrom(m), nil
}
// FromFileToMap is the same as FromFile, but it returns the config values
@ -116,9 +103,3 @@ func init() {
func RenameKeys(m map[string]interface{}) {
keyAliases.Rename(m)
}
func newViper() *viper.Viper {
v := viper.New()
return v
}

View file

@ -14,6 +14,7 @@
package config
import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
)
@ -22,11 +23,15 @@ type Provider interface {
GetString(key string) string
GetInt(key string) int
GetBool(key string) bool
GetParams(key string) maps.Params
GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
Get(key string) interface{}
Set(key string, value interface{})
Merge(key string, value interface{})
SetDefaultMergeStrategy()
WalkParams(walkFn func(params ...KeyParams) bool)
IsSet(key string) bool
}

View file

@ -17,12 +17,11 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestGetStringSlicePreserveString(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := New()
s := "This is a string"
sSlice := []string{"This", "is", "a", "slice"}

View file

@ -0,0 +1,372 @@
// Copyright 2021 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 config
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/maps"
)
var (
// ConfigRootKeysSet contains all of the config map root keys.
// TODO(bep) use this for something (docs etc.)
ConfigRootKeysSet = map[string]bool{
"build": true,
"caches": true,
"frontmatter": true,
"languages": true,
"imaging": true,
"markup": true,
"mediatypes": true,
"menus": true,
"minify": true,
"module": true,
"outputformats": true,
"params": true,
"permalinks": true,
"related": true,
"sitemap": true,
"taxonomies": true,
}
// ConfigRootKeys is a sorted version of ConfigRootKeysSet.
ConfigRootKeys []string
)
func init() {
for k := range ConfigRootKeysSet {
ConfigRootKeys = append(ConfigRootKeys, k)
}
sort.Strings(ConfigRootKeys)
}
// New creates a Provider backed by an empty maps.Params.
func New() Provider {
return &defaultConfigProvider{
root: make(maps.Params),
}
}
// NewFrom creates a Provider backed by params.
func NewFrom(params maps.Params) Provider {
maps.PrepareParams(params)
return &defaultConfigProvider{
root: params,
}
}
// defaultConfigProvider is a Provider backed by a map where all keys are lower case.
// All methods are thread safe.
type defaultConfigProvider struct {
mu sync.RWMutex
root maps.Params
keyCache sync.Map
}
func (c *defaultConfigProvider) Get(k string) interface{} {
if k == "" {
return c.root
}
c.mu.RLock()
key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
if m == nil {
return nil
}
v := m[key]
c.mu.RUnlock()
return v
}
func (c *defaultConfigProvider) GetBool(k string) bool {
v := c.Get(k)
return cast.ToBool(v)
}
func (c *defaultConfigProvider) GetInt(k string) int {
v := c.Get(k)
return cast.ToInt(v)
}
func (c *defaultConfigProvider) IsSet(k string) bool {
var found bool
c.mu.RLock()
key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
if m != nil {
_, found = m[key]
}
c.mu.RUnlock()
return found
}
func (c *defaultConfigProvider) GetString(k string) string {
v := c.Get(k)
return cast.ToString(v)
}
func (c *defaultConfigProvider) GetParams(k string) maps.Params {
v := c.Get(k)
if v == nil {
return nil
}
return v.(maps.Params)
}
func (c *defaultConfigProvider) GetStringMap(k string) map[string]interface{} {
v := c.Get(k)
return maps.ToStringMap(v)
}
func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
v := c.Get(k)
return maps.ToStringMapString(v)
}
func (c *defaultConfigProvider) GetStringSlice(k string) []string {
v := c.Get(k)
return cast.ToStringSlice(v)
}
func (c *defaultConfigProvider) Set(k string, v interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
k = strings.ToLower(k)
if k == "" {
if p, ok := maps.ToParamsAndPrepare(v); ok {
// Set the values directly in root.
c.root.Set(p)
} else {
c.root[k] = v
}
return
}
switch vv := v.(type) {
case map[string]interface{}:
var p maps.Params = vv
v = p
maps.PrepareParams(p)
}
key, m := c.getNestedKeyAndMap(k, true)
if existing, found := m[key]; found {
if p1, ok := existing.(maps.Params); ok {
if p2, ok := v.(maps.Params); ok {
p1.Set(p2)
return
}
}
}
m[key] = v
}
func (c *defaultConfigProvider) Merge(k string, v interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
k = strings.ToLower(k)
if k == "" {
rs, f := c.root.GetMergeStrategy()
if f && rs == maps.ParamsMergeStrategyNone {
// The user has set a "no merge" strategy on this,
// nothing more to do.
return
}
if p, ok := maps.ToParamsAndPrepare(v); ok {
// As there may be keys in p not in root, we need to handle
// those as a special case.
for kk, vv := range p {
if pp, ok := vv.(maps.Params); ok {
if ppp, ok := c.root[kk]; ok {
ppp.(maps.Params).Merge(pp)
} else {
// We need to use the default merge strategy for
// this key.
np := make(maps.Params)
strategy := c.determineMergeStrategy(KeyParams{Key: "", Params: c.root}, KeyParams{Key: kk, Params: np})
np.SetDefaultMergeStrategy(strategy)
np.Merge(pp)
if len(np) > 0 {
c.root[kk] = np
}
}
}
}
// Merge the rest.
c.root.Merge(p)
} else {
panic(fmt.Sprintf("unsupported type %T received in Merge", v))
}
return
}
switch vv := v.(type) {
case map[string]interface{}:
var p maps.Params = vv
v = p
maps.PrepareParams(p)
}
key, m := c.getNestedKeyAndMap(k, true)
if existing, found := m[key]; found {
if p1, ok := existing.(maps.Params); ok {
if p2, ok := v.(maps.Params); ok {
p1.Merge(p2)
}
}
} else {
m[key] = v
}
}
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...KeyParams) bool) {
var walk func(params ...KeyParams)
walk = func(params ...KeyParams) {
if walkFn(params...) {
return
}
p1 := params[len(params)-1]
i := len(params)
for k, v := range p1.Params {
if p2, ok := v.(maps.Params); ok {
paramsplus1 := make([]KeyParams, i+1)
copy(paramsplus1, params)
paramsplus1[i] = KeyParams{Key: k, Params: p2}
walk(paramsplus1...)
}
}
}
walk(KeyParams{Key: "", Params: c.root})
}
func (c *defaultConfigProvider) determineMergeStrategy(params ...KeyParams) maps.ParamsMergeStrategy {
if len(params) == 0 {
return maps.ParamsMergeStrategyNone
}
var (
strategy maps.ParamsMergeStrategy
prevIsRoot bool
curr = params[len(params)-1]
)
if len(params) > 1 {
prev := params[len(params)-2]
prevIsRoot = prev.Key == ""
// Inherit from parent (but not from the root unless it's set by user).
s, found := prev.Params.GetMergeStrategy()
if !prevIsRoot && !found {
panic("invalid state, merge strategy not set on parent")
}
if found || !prevIsRoot {
strategy = s
}
}
switch curr.Key {
case "":
// Don't set a merge strategy on the root unless set by user.
// This will be handled as a special case.
case "params":
strategy = maps.ParamsMergeStrategyDeep
case "outputformats", "mediatypes":
if prevIsRoot {
strategy = maps.ParamsMergeStrategyShallow
}
case "menus":
isMenuKey := prevIsRoot
if !isMenuKey {
// Can also be set below languages.
// root > languages > en > menus
if len(params) == 4 && params[1].Key == "languages" {
isMenuKey = true
}
}
if isMenuKey {
strategy = maps.ParamsMergeStrategyShallow
}
default:
if strategy == "" {
strategy = maps.ParamsMergeStrategyNone
}
}
return strategy
}
type KeyParams struct {
Key string
Params maps.Params
}
func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
c.WalkParams(func(params ...KeyParams) bool {
if len(params) == 0 {
return false
}
p := params[len(params)-1].Params
var found bool
if _, found = p.GetMergeStrategy(); found {
// Set by user.
return false
}
strategy := c.determineMergeStrategy(params...)
if strategy != "" {
p.SetDefaultMergeStrategy(strategy)
}
return false
})
}
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
var parts []string
v, ok := c.keyCache.Load(key)
if ok {
parts = v.([]string)
} else {
parts = strings.Split(key, ".")
c.keyCache.Store(key, parts)
}
current := c.root
for i := 0; i < len(parts)-1; i++ {
next, found := current[parts[i]]
if !found {
if create {
next = make(maps.Params)
current[parts[i]] = next
} else {
return "", nil
}
}
current = next.(maps.Params)
}
return parts[len(parts)-1], current
}

View file

@ -0,0 +1,315 @@
// Copyright 2021 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 config
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/maps"
qt "github.com/frankban/quicktest"
)
func TestDefaultConfigProvider(t *testing.T) {
c := qt.New(t)
c.Run("Set and get", func(c *qt.C) {
cfg := New()
var k string
var v interface{}
k, v = "foo", "bar"
cfg.Set(k, v)
c.Assert(cfg.Get(k), qt.Equals, v)
c.Assert(cfg.Get(strings.ToUpper(k)), qt.Equals, v)
c.Assert(cfg.GetString(k), qt.Equals, v)
k, v = "foo", 42
cfg.Set(k, v)
c.Assert(cfg.Get(k), qt.Equals, v)
c.Assert(cfg.GetInt(k), qt.Equals, v)
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"foo": 42,
})
})
c.Run("Set and get map", func(c *qt.C) {
cfg := New()
cfg.Set("foo", map[string]interface{}{
"bar": "baz",
})
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
"bar": "baz",
})
c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]interface{}{"bar": string("baz")})
c.Assert(cfg.GetStringMapString("foo"), qt.DeepEquals, map[string]string{"bar": string("baz")})
})
c.Run("Set and get nested", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
"B": "bv",
})
cfg.Set("a.c", "cv")
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
"b": "bv",
"c": "cv",
})
c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
cfg.Set("b.a", "av")
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
"a": "av",
})
cfg.Set("b", map[string]interface{}{
"b": "bv",
})
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
"a": "av",
"b": "bv",
})
cfg = New()
cfg.Set("a", "av")
cfg.Set("", map[string]interface{}{
"a": "av2",
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"a": "av2",
"b": "bv2",
})
cfg = New()
cfg.Set("a", "av")
cfg.Set("", map[string]interface{}{
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"a": "av",
"b": "bv2",
})
cfg = New()
cfg.Set("", map[string]interface{}{
"foo": map[string]interface{}{
"a": "av",
},
})
cfg.Set("", map[string]interface{}{
"foo": map[string]interface{}{
"b": "bv2",
},
})
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
"a": "av",
"b": "bv2",
})
})
c.Run("Merge default strategy", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
"B": "bv",
})
cfg.Merge("a", map[string]interface{}{
"B": "bv2",
"c": "cv2",
})
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
"b": "bv",
"c": "cv2",
})
cfg = New()
cfg.Set("a", "av")
cfg.Merge("", map[string]interface{}{
"a": "av2",
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"a": "av",
"b": "bv2",
})
})
c.Run("Merge shallow", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
"_merge": "shallow",
"B": "bv",
"c": map[string]interface{}{
"b": "bv",
},
})
cfg.Merge("a", map[string]interface{}{
"c": map[string]interface{}{
"d": "dv2",
},
"e": "ev2",
})
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
"e": "ev2",
"_merge": maps.ParamsMergeStrategyShallow,
"b": "bv",
"c": maps.Params{
"b": "bv",
},
})
})
c.Run("IsSet", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
"B": "bv",
})
c.Assert(cfg.IsSet("A"), qt.IsTrue)
c.Assert(cfg.IsSet("a.b"), qt.IsTrue)
c.Assert(cfg.IsSet("z"), qt.IsFalse)
})
c.Run("Para", func(c *qt.C) {
cfg := New()
p := para.New(4)
r, _ := p.Start(context.Background())
setAndGet := func(k string, v int) error {
vs := strconv.Itoa(v)
cfg.Set(k, v)
err := errors.New("get failed")
if cfg.Get(k) != v {
return err
}
if cfg.GetInt(k) != v {
return err
}
if cfg.GetString(k) != vs {
return err
}
if !cfg.IsSet(k) {
return err
}
return nil
}
for i := 0; i < 20; i++ {
i := i
r.Run(func() error {
const v = 42
k := fmt.Sprintf("k%d", i)
if err := setAndGet(k, v); err != nil {
return err
}
m := maps.Params{
"new": 42,
}
cfg.Merge("", m)
return nil
})
}
c.Assert(r.Wait(), qt.IsNil)
})
}
func BenchmarkDefaultConfigProvider(b *testing.B) {
type cfger interface {
Get(key string) interface{}
Set(key string, value interface{})
IsSet(key string) bool
}
newMap := func() map[string]interface{} {
return map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": 32,
"d": 43,
},
},
"b": 62,
}
}
runMethods := func(b *testing.B, cfg cfger) {
m := newMap()
cfg.Set("mymap", m)
cfg.Set("num", 32)
if !(cfg.IsSet("mymap") && cfg.IsSet("mymap.a") && cfg.IsSet("mymap.a.b") && cfg.IsSet("mymap.a.b.c")) {
b.Fatal("IsSet failed")
}
if cfg.Get("num") != 32 {
b.Fatal("Get failed")
}
if cfg.Get("mymap.a.b.c") != 32 {
b.Fatal("Get failed")
}
}
b.Run("Viper", func(b *testing.B) {
v := viper.New()
for i := 0; i < b.N; i++ {
runMethods(b, v)
}
})
b.Run("Custom", func(b *testing.B) {
cfg := New()
for i := 0; i < b.N; i++ {
runMethods(b, cfg)
}
})
}

45
config/docshelper.go Normal file
View file

@ -0,0 +1,45 @@
// Copyright 2021 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 config
import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/docshelper"
)
// This is is just some helpers used to create some JSON used in the Hugo docs.
func init() {
docsProvider := func() docshelper.DocProvider {
cfg := New()
for _, configRoot := range ConfigRootKeys {
cfg.Set(configRoot, make(maps.Params))
}
lang := maps.Params{
"en": maps.Params{
"menus": maps.Params{},
"params": maps.Params{},
},
}
cfg.Set("languages", lang)
cfg.SetDefaultMergeStrategy()
configHelpers := map[string]interface{}{
"mergeStrategy": cfg.Get(""),
}
return docshelper.DocProvider{"config": configHelpers}
}
docshelper.AddDocProviderFunc(docsProvider)
}

View file

@ -18,7 +18,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/spf13/viper"
)
func TestDecodeConfigFromTOML(t *testing.T) {
@ -94,7 +93,7 @@ PrivacyENhanced = true
func TestDecodeConfigDefault(t *testing.T) {
c := qt.New(t)
pc, err := DecodeConfig(viper.New())
pc, err := DecodeConfig(config.New())
c.Assert(err, qt.IsNil)
c.Assert(pc, qt.Not(qt.IsNil))
c.Assert(pc.YouTube.PrivacyEnhanced, qt.Equals, false)

View file

@ -18,7 +18,7 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/spf13/viper"
)
func TestDecodeConfigFromTOML(t *testing.T) {
@ -55,7 +55,7 @@ disableInlineCSS = true
func TestUseSettingsFromRootIfSet(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
cfg.Set("disqusShortname", "root_short")
cfg.Set("googleAnalytics", "ga_root")

View file

@ -20,6 +20,8 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugolib"
@ -30,7 +32,6 @@ import (
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestNewContent(t *testing.T) {
@ -245,7 +246,7 @@ func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
return string(b)
}
func newTestCfg(c *qt.C, mm afero.Fs) (*viper.Viper, *hugofs.Fs) {
func newTestCfg(c *qt.C, mm afero.Fs) (config.Provider, *hugofs.Fs) {
cfg := `
theme = "mytheme"

View file

@ -21,7 +21,7 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/spf13/viper"
)
func TestDecodeConfigFromTOML(t *testing.T) {
@ -164,7 +164,7 @@ Pattern = "[" # invalid regular expression
func TestDecodeConfigDefault(t *testing.T) {
c := qt.New(t)
dcfg, err := decodeConfig(viper.New())
dcfg, err := decodeConfig(config.New())
c.Assert(err, qt.IsNil)
c.Assert(len(dcfg.Targets), qt.Equals, 0)
c.Assert(len(dcfg.Matchers), qt.Equals, 0)

View file

@ -7,7 +7,7 @@ footnotereturnlinkcontents = "↩"
languageCode = "en-us"
title = "Hugo"
ignoreErrors = ["error-remote-getjson"]
ignoreErrors = ["error-remote-getjson", "err-missing-instagram-accesstoken"]
googleAnalytics = "UA-7131036-4"

View file

@ -80,6 +80,26 @@ Considering the structure above, when running `hugo --environment staging`, Hugo
{{% note %}}
Default environments are __development__ with `hugo server` and __production__ with `hugo`.
{{%/ note %}}
## Merge Configuration from Themes
{{< new-in "0.84.0" >}} The configuration merge described below was improved in Hugo 0.84.0 and made fully configurable. The big change/improvement was that we now, by default, do deep merging of `params` maps from themes.
The configuration value for `_merge` can be one of:
none
: No merge.
shallow
: Only add values for new keys.
shallow
: Add values for new keys, merge existing.
Note that you don't need to be so verbose as in the default setup below; a `_merge` value higher up will be inherited if not set.
{{< code-toggle config="mergeStrategy" skipHeader=true />}}
## All Configuration Settings
The following is the full list of Hugo-defined variables with their default

View file

@ -1587,6 +1587,65 @@
"preserveTOC": false
}
},
"mergeStrategy": {
"build": {
"_merge": "none"
},
"caches": {
"_merge": "none"
},
"frontmatter": {
"_merge": "none"
},
"imaging": {
"_merge": "none"
},
"languages": {
"_merge": "none",
"en": {
"_merge": "none",
"menus": {
"_merge": "shallow"
},
"params": {
"_merge": "deep"
}
}
},
"markup": {
"_merge": "none"
},
"mediatypes": {
"_merge": "shallow"
},
"menus": {
"_merge": "shallow"
},
"minify": {
"_merge": "none"
},
"module": {
"_merge": "none"
},
"outputformats": {
"_merge": "shallow"
},
"params": {
"_merge": "deep"
},
"permalinks": {
"_merge": "none"
},
"related": {
"_merge": "none"
},
"sitemap": {
"_merge": "none"
},
"taxonomies": {
"_merge": "none"
}
},
"minify": {
"minifyOutput": false,
"disableHTML": false,

View file

@ -1,34 +1,41 @@
{{ $file := .Get "file" }}
{{ $code := "" }}
{{ with .Get "config" }}
{{ $file = $file | default "config" }}
{{ $sections := (split . ".") }}
{{ $configSection := index $.Site.Data.docs.config $sections }}
{{ $code = dict $sections $configSection }}
{{ $file = $file | default "config" }}
{{ $sections := (split . ".") }}
{{ $configSection := index $.Site.Data.docs.config $sections }}
{{ $code = dict $sections $configSection }}
{{ if $.Get "skipHeader"}}
{{ $code = $configSection }}
{{ end }}
{{ else }}
{{ $code = $.Inner }}
{{ $code = $.Inner }}
{{ end }}
{{ $langs := (slice "yaml" "toml" "json") }}
<div class="code relative" {{ with $file }}id="{{ . | urlize}}"{{ end }}>
<div class="code-nav flex flex-nowrap items-stretch">
{{- with $file -}}
<div class="san-serif f6 dib lh-solid pl2 pv2 mr2">{{ . }}.</div>
{{- end -}}
{{ range $langs }}
<button data-toggle-tab="{{ . }}" class="tab-button {{ cond (eq . "yaml") "active" ""}} ba san-serif f6 dib lh-solid ph2 pv2">{{ . }}</button>&nbsp;
{{ end }}
</div>
<div class="tab-content">
{{ range $langs }}
<div data-pane="{{ . }}" class="code-copy-content nt3 tab-pane {{ cond (eq . "yaml") "active" ""}}">
{{ highlight ($code | transform.Remarshal . | safeHTML) . ""}}
</div>
{{ if ne ($.Get "copy") "false" }}
<button class="needs-js copy copy-toggle bg-accent-color-dark f6 absolute top-0 right-0 lh-solid hover-bg-primary-color-dark bn white ph3 pv2" title="Copy this code to your clipboard." data-clipboard-action="copy" aria-label="copy button">
</button>
{{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
{{end}}
{{ end }}
</div>
<div class="code-nav flex flex-nowrap items-stretch">
{{- with $file -}}
<div class="san-serif f6 dib lh-solid pl2 pv2 mr2">
{{ . }}.
</div>
{{- end -}}
{{ range $langs }}
<button data-toggle-tab="{{ . }}" class="tab-button {{ cond (eq . "yaml") "active" ""}} ba san-serif f6 dib lh-solid ph2 pv2">
{{ . }}
</button>
&nbsp;
{{ end }}
</div>
<div class="tab-content">
{{ range $langs }}
<div data-pane="{{ . }}" class="code-copy-content nt3 tab-pane {{ cond (eq . "yaml") "active" ""}}">
{{ highlight ($code | transform.Remarshal . | safeHTML) . ""}}
</div>
{{ if ne ($.Get "copy") "false" }}
<button class="needs-js copy copy-toggle bg-accent-color-dark f6 absolute top-0 right-0 lh-solid hover-bg-primary-color-dark bn white ph3 pv2" title="Copy this code to your clipboard." data-clipboard-action="copy" aria-label="copy button"></button>
{{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
{{end}}
{{ end }}
</div>
</div>

2
go.mod
View file

@ -55,7 +55,7 @@ require (
github.com/spf13/fsync v0.9.0
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
github.com/spf13/viper v1.7.0
github.com/tdewolff/minify/v2 v2.9.16
github.com/yuin/goldmark v1.3.5
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691

1
go.sum
View file

@ -642,6 +642,7 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=

View file

@ -19,12 +19,11 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
@ -103,7 +102,7 @@ func TestBytesToHTML(t *testing.T) {
}
func TestNewContentSpec(t *testing.T) {
cfg := viper.New()
cfg := config.New()
c := qt.New(t)
cfg.Set("summaryLength", 32)

View file

@ -19,7 +19,7 @@ import (
"strings"
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers"
@ -29,7 +29,7 @@ import (
func TestResolveMarkup(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs())
c.Assert(err, qt.IsNil)

View file

@ -31,7 +31,6 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestMakePath(t *testing.T) {
@ -490,8 +489,6 @@ func TestExists(t *testing.T) {
}
func TestAbsPathify(t *testing.T) {
defer viper.Reset()
type test struct {
inPath, workingDir, expected string
}
@ -511,7 +508,6 @@ func TestAbsPathify(t *testing.T) {
}
for i, d := range data {
viper.Reset()
// todo see comment in AbsPathify
ps := newTestDefaultPathSpec("workingDir", d.workingDir)

View file

@ -2,24 +2,24 @@ package helpers
import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules"
)
func newTestPathSpec(fs *hugofs.Fs, v *viper.Viper) *PathSpec {
func newTestPathSpec(fs *hugofs.Fs, v config.Provider) *PathSpec {
l := langs.NewDefaultLanguage(v)
ps, _ := NewPathSpec(fs, l, nil)
return ps
}
func newTestDefaultPathSpec(configKeyValues ...interface{}) *PathSpec {
v := viper.New()
v := config.New()
fs := hugofs.NewMem(v)
cfg := newTestCfgFor(fs)
cfg := newTestCfg()
for i := 0; i < len(configKeyValues); i += 2 {
cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
@ -27,15 +27,8 @@ func newTestDefaultPathSpec(configKeyValues ...interface{}) *PathSpec {
return newTestPathSpec(fs, cfg)
}
func newTestCfgFor(fs *hugofs.Fs) *viper.Viper {
v := newTestCfg()
v.SetFs(fs.Source)
return v
}
func newTestCfg() *viper.Viper {
v := viper.New()
func newTestCfg() config.Provider {
v := config.New()
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")
@ -56,7 +49,7 @@ func newTestCfg() *viper.Viper {
}
func newTestContentSpec() *ContentSpec {
v := viper.New()
v := config.New()
spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs())
if err != nil {
panic(err)

View file

@ -16,15 +16,16 @@ package hugofs
import (
"testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestNewDefault(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
f := NewDefault(v)
c.Assert(f.Source, qt.Not(qt.IsNil))
@ -35,7 +36,7 @@ func TestNewDefault(t *testing.T) {
func TestNewMem(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
f := NewMem(v)
c.Assert(f.Source, qt.Not(qt.IsNil))
@ -48,7 +49,7 @@ func TestNewMem(t *testing.T) {
func TestWorkingDir(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("workingDir", "/a/b/")

View file

@ -20,7 +20,7 @@ import (
"sort"
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting"
@ -29,7 +29,7 @@ import (
func TestLanguageRootMapping(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
fs := NewBaseFileDecorator(afero.NewMemMapFs())

View file

@ -43,34 +43,136 @@ import (
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
// SiteConfig represents the config in .Site.Config.
type SiteConfig struct {
// This contains all privacy related settings that can be used to
// make the YouTube template etc. GDPR compliant.
Privacy privacy.Config
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
// Services contains config for services such as Google Analytics etc.
Services services.Config
// LoadConfig loads Hugo configuration into a new Viper and then adds
// a set of defaults.
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (config.Provider, []string, error) {
if d.Environment == "" {
d.Environment = hugo.EnvironmentProduction
}
if len(d.Environ) == 0 {
d.Environ = os.Environ()
}
var configFiles []string
l := configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
if err := l.applyConfigDefaults(); err != nil {
return l.cfg, configFiles, err
}
for _, name := range d.configFilenames() {
var filename string
filename, err := l.loadConfig(name)
if err == nil {
configFiles = append(configFiles, filename)
} else if err != ErrNoConfigFile {
return nil, nil, err
}
}
if d.AbsConfigDir != "" {
dirnames, err := l.loadConfigFromConfigDir()
if err == nil {
configFiles = append(configFiles, dirnames...)
} else if err != ErrNoConfigFile {
return nil, nil, err
}
}
// TODO(bep) improve this. This is currently needed to get the merge correctly.
if l.cfg.IsSet("languages") {
langs := l.cfg.GetParams("languages")
for _, lang := range langs {
langp := lang.(maps.Params)
if _, ok := langp["menus"]; !ok {
langp["menus"] = make(maps.Params)
}
if _, ok := langp["params"]; !ok {
langp["params"] = make(maps.Params)
}
}
}
l.cfg.SetDefaultMergeStrategy()
// We create languages based on the settings, so we need to make sure that
// all configuration is loaded/set before doing that.
for _, d := range doWithConfig {
if err := d(l.cfg); err != nil {
return l.cfg, configFiles, err
}
}
// We made this a Glob pattern in Hugo 0.75, we don't need both.
if l.cfg.GetBool("ignoreVendor") {
helpers.Deprecated("--ignoreVendor", "--ignoreVendorPaths **", false)
l.cfg.Set("ignoreVendorPaths", "**")
}
// Some settings are used before we're done collecting all settings,
// so apply OS environment both before and after.
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
return l.cfg, configFiles, err
}
modulesConfig, err := l.loadModulesConfig()
if err != nil {
return l.cfg, configFiles, err
}
// Need to run these after the modules are loaded, but before
// they are finalized.
collectHook := func(m *modules.ModulesConfig) error {
// We don't need the merge strategy configuration anymore,
// remove it so it doesn't accidentaly show up in other settings.
l.cfg.WalkParams(func(params ...config.KeyParams) bool {
params[len(params)-1].Params.DeleteMergeStrategy()
return false
})
if err := l.loadLanguageSettings(nil); err != nil {
return err
}
mods := m.ActiveModules
// Apply default project mounts.
if err := modules.ApplyProjectConfigDefaults(l.cfg, mods[0]); err != nil {
return err
}
return nil
}
_, modulesConfigFiles, err := l.collectModules(modulesConfig, l.cfg, collectHook)
if err != nil {
return l.cfg, configFiles, err
}
configFiles = append(configFiles, modulesConfigFiles...)
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
return l.cfg, configFiles, err
}
if err = l.applyConfigAliases(); err != nil {
return l.cfg, configFiles, err
}
return l.cfg, configFiles, err
}
func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
privacyConfig, err := privacy.DecodeConfig(cfg)
if err != nil {
return
}
servicesConfig, err := services.DecodeConfig(cfg)
if err != nil {
return
}
scfg.Privacy = privacyConfig
scfg.Services = servicesConfig
return
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
func LoadConfigDefault(fs afero.Fs) (config.Provider, error) {
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
return v, err
}
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
@ -98,13 +200,6 @@ type ConfigSourceDescriptor struct {
Environ []string
}
func (d ConfigSourceDescriptor) configFilenames() []string {
if d.Filename == "" {
return []string{"config"}
}
return strings.Split(d.Filename, ",")
}
func (d ConfigSourceDescriptor) configFileDir() string {
if d.Path != "" {
return d.Path
@ -112,178 +207,226 @@ func (d ConfigSourceDescriptor) configFileDir() string {
return d.WorkingDir
}
// LoadConfigDefault is a convenience method to load the default "config.toml" config.
func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
return v, err
func (d ConfigSourceDescriptor) configFilenames() []string {
if d.Filename == "" {
return []string{"config"}
}
return strings.Split(d.Filename, ",")
}
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
// SiteConfig represents the config in .Site.Config.
type SiteConfig struct {
// This contains all privacy related settings that can be used to
// make the YouTube template etc. GDPR compliant.
Privacy privacy.Config
// LoadConfig loads Hugo configuration into a new Viper and then adds
// a set of defaults.
func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
if d.Environment == "" {
d.Environment = hugo.EnvironmentProduction
}
if len(d.Environ) == 0 {
d.Environ = os.Environ()
}
var configFiles []string
v := viper.New()
l := configLoader{ConfigSourceDescriptor: d}
for _, name := range d.configFilenames() {
var filename string
filename, err := l.loadConfig(name, v)
if err == nil {
configFiles = append(configFiles, filename)
} else if err != ErrNoConfigFile {
return nil, nil, err
}
}
if d.AbsConfigDir != "" {
dirnames, err := l.loadConfigFromConfigDir(v)
if err == nil {
configFiles = append(configFiles, dirnames...)
} else if err != ErrNoConfigFile {
return nil, nil, err
}
}
if err := loadDefaultSettingsFor(v); err != nil {
return v, configFiles, err
}
// We create languages based on the settings, so we need to make sure that
// all configuration is loaded/set before doing that.
for _, d := range doWithConfig {
if err := d(v); err != nil {
return v, configFiles, err
}
}
// This is invoked both after we load the main config and at the end
// to support OS env override of config options used in the module collector.
applyOsEnvOverrides := func() error {
if d.Environ == nil {
return nil
}
const delim = "__env__delim"
// Extract all that start with the HUGO prefix.
// The delimiter is the following rune, usually "_".
const hugoEnvPrefix = "HUGO"
var hugoEnv []types.KeyValueStr
for _, v := range d.Environ {
key, val := config.SplitEnvVar(v)
if strings.HasPrefix(key, hugoEnvPrefix) {
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
if len(delimiterAndKey) < 2 {
continue
}
// Allow delimiters to be case sensitive.
// It turns out there isn't that many allowed special
// chars in environment variables when used in Bash and similar,
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
key = strings.ToLower(key)
hugoEnv = append(hugoEnv, types.KeyValueStr{
Key: key,
Value: val,
})
}
}
for _, env := range hugoEnv {
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, v.Get)
if err != nil {
return err
}
if existing != nil {
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
if err != nil {
continue
}
if owner != nil {
owner[nestedKey] = val
} else {
v.Set(env.Key, val)
}
} else if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
v.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
}
}
return nil
}
if err := applyOsEnvOverrides(); err != nil {
return v, configFiles, err
}
// We made this a Glob pattern in Hugo 0.75, we don't need both.
if v.GetBool("ignoreVendor") {
helpers.Deprecated("--ignoreVendor", "--ignoreVendorPaths **", false)
v.Set("ignoreVendorPaths", "**")
}
modulesConfig, err := l.loadModulesConfig(v)
if err != nil {
return v, configFiles, err
}
// Need to run these after the modules are loaded, but before
// they are finalized.
collectHook := func(m *modules.ModulesConfig) error {
if err := loadLanguageSettings(v, nil); err != nil {
return err
}
mods := m.ActiveModules
// Apply default project mounts.
if err := modules.ApplyProjectConfigDefaults(v, mods[0]); err != nil {
return err
}
return nil
}
_, modulesConfigFiles, err := l.collectModules(modulesConfig, v, collectHook)
if err == nil && len(modulesConfigFiles) > 0 {
configFiles = append(configFiles, modulesConfigFiles...)
}
if err := applyOsEnvOverrides(); err != nil {
return v, configFiles, err
}
return v, configFiles, err
}
func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
_, err := langs.LoadLanguageSettings(cfg, oldLangs)
return err
// Services contains config for services such as Google Analytics etc.
Services services.Config
}
type configLoader struct {
cfg config.Provider
ConfigSourceDescriptor
}
func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, error) {
// Handle some legacy values.
func (l configLoader) applyConfigAliases() error {
aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
for _, alias := range aliases {
if l.cfg.IsSet(alias.Key) {
vv := l.cfg.Get(alias.Key)
l.cfg.Set(alias.Value, vv)
}
}
return nil
}
func (l configLoader) applyConfigDefaults() error {
defaultSettings := maps.Params{
"cleanDestinationDir": false,
"watch": false,
"resourceDir": "resources",
"publishDir": "public",
"themesDir": "themes",
"buildDrafts": false,
"buildFuture": false,
"buildExpired": false,
"environment": hugo.EnvironmentProduction,
"uglyURLs": false,
"verbose": false,
"ignoreCache": false,
"canonifyURLs": false,
"relativeURLs": false,
"removePathAccents": false,
"titleCaseStyle": "AP",
"taxonomies": map[string]string{"tag": "tags", "category": "categories"},
"permalinks": make(map[string]string),
"sitemap": config.Sitemap{Priority: -1, Filename: "sitemap.xml"},
"disableLiveReload": false,
"pluralizeListTitles": true,
"forceSyncStatic": false,
"footnoteAnchorPrefix": "",
"footnoteReturnLinkContents": "",
"newContentEditor": "",
"paginate": 10,
"paginatePath": "page",
"summaryLength": 70,
"rssLimit": -1,
"sectionPagesMenu": "",
"disablePathToLower": false,
"hasCJKLanguage": false,
"enableEmoji": false,
"pygmentsCodeFencesGuessSyntax": false,
"defaultContentLanguage": "en",
"defaultContentLanguageInSubdir": false,
"enableMissingTranslationPlaceholders": false,
"enableGitInfo": false,
"ignoreFiles": make([]string, 0),
"disableAliases": false,
"debug": false,
"disableFastRender": false,
"timeout": "30s",
"enableInlineShortcodes": false,
}
l.cfg.Merge("", defaultSettings)
return nil
}
func (l configLoader) applyOsEnvOverrides(environ []string) error {
if len(environ) == 0 {
return nil
}
const delim = "__env__delim"
// Extract all that start with the HUGO prefix.
// The delimiter is the following rune, usually "_".
const hugoEnvPrefix = "HUGO"
var hugoEnv []types.KeyValueStr
for _, v := range environ {
key, val := config.SplitEnvVar(v)
if strings.HasPrefix(key, hugoEnvPrefix) {
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
if len(delimiterAndKey) < 2 {
continue
}
// Allow delimiters to be case sensitive.
// It turns out there isn't that many allowed special
// chars in environment variables when used in Bash and similar,
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
key = strings.ToLower(key)
hugoEnv = append(hugoEnv, types.KeyValueStr{
Key: key,
Value: val,
})
}
}
for _, env := range hugoEnv {
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
if err != nil {
return err
}
if existing != nil {
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
if err != nil {
continue
}
if owner != nil {
owner[nestedKey] = val
} else {
l.cfg.Set(env.Key, val)
}
} else if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
// The container does not exist yet.
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
}
}
return nil
}
func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provider, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
workingDir := l.WorkingDir
if workingDir == "" {
workingDir = v1.GetString("workingDir")
}
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
var ignoreVendor glob.Glob
if s := v1.GetString("ignoreVendorPaths"); s != "" {
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
if err != nil {
return nil, nil, err
}
v1.Set("filecacheConfigs", filecacheConfigs)
var configFilenames []string
hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.ActiveModules {
if tc.ConfigFilename() != "" {
if tc.Watch() {
configFilenames = append(configFilenames, tc.ConfigFilename())
}
// Merge from theme config into v1 based on configured
// merge strategy.
v1.Merge("", tc.Cfg().Get(""))
}
}
if hookBeforeFinalize != nil {
return hookBeforeFinalize(m)
}
return nil
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: l.Fs,
Logger: l.Logger,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
CacheDir: filecacheConfigs.CacheDirModules(),
ModuleConfig: modConfig,
IgnoreVendor: ignoreVendor,
})
v1.Set("modulesClient", modulesClient)
moduleConfig, err := modulesClient.Collect()
// Avoid recreating these later.
v1.Set("allModules", moduleConfig.ActiveModules)
if moduleConfig.GoModulesFilename != "" {
// We want to watch this for changes and trigger rebuild on version
// changes etc.
configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
}
return moduleConfig.ActiveModules, configFilenames, err
}
func (l configLoader) loadConfig(configName string) (string, error) {
baseDir := l.configFileDir()
var baseFilename string
if filepath.IsAbs(configName) {
@ -318,24 +461,13 @@ func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, err
return "", l.wrapFileError(err, filename)
}
if err = v.MergeConfigMap(m); err != nil {
return "", l.wrapFileError(err, filename)
}
// Set overwrites keys of the same name, recursively.
l.cfg.Set("", m)
return filename, nil
}
func (l configLoader) wrapFileError(err error, filename string) error {
err, _ = herrors.WithFileContextForFile(
err,
filename,
filename,
l.Fs,
herrors.SimpleLineMatcher)
return err
}
func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error) {
func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
sourceFs := l.Fs
configDir := l.AbsConfigDir
@ -421,9 +553,8 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
// Migrate menu => menus etc.
config.RenameKeys(root)
if err := v.MergeConfigMap(root); err != nil {
return l.wrapFileError(err, path)
}
// Set will overwrite keys with the same name, recursively.
l.cfg.Set("", root)
return nil
})
@ -436,8 +567,13 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
return dirnames, nil
}
func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error) {
modConfig, err := modules.DecodeConfig(v1)
func (l configLoader) loadLanguageSettings(oldLangs langs.Languages) error {
_, err := langs.LoadLanguageSettings(l.cfg, oldLangs)
return err
}
func (l configLoader) loadModulesConfig() (modules.Config, error) {
modConfig, err := modules.DecodeConfig(l.cfg)
if err != nil {
return modules.Config{}, err
}
@ -445,211 +581,29 @@ func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error)
return modConfig, nil
}
func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
workingDir := l.WorkingDir
if workingDir == "" {
workingDir = v1.GetString("workingDir")
}
themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
var ignoreVendor glob.Glob
if s := v1.GetString("ignoreVendorPaths"); s != "" {
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
func (configLoader) loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
privacyConfig, err := privacy.DecodeConfig(cfg)
if err != nil {
return nil, nil, err
}
v1.Set("filecacheConfigs", filecacheConfigs)
var configFilenames []string
hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.ActiveModules {
if tc.ConfigFilename() != "" {
if tc.Watch() {
configFilenames = append(configFilenames, tc.ConfigFilename())
}
if err := l.applyThemeConfig(v1, tc); err != nil {
return err
}
}
}
if hookBeforeFinalize != nil {
return hookBeforeFinalize(m)
}
return nil
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: l.Fs,
Logger: l.Logger,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
CacheDir: filecacheConfigs.CacheDirModules(),
ModuleConfig: modConfig,
IgnoreVendor: ignoreVendor,
})
v1.Set("modulesClient", modulesClient)
moduleConfig, err := modulesClient.Collect()
// Avoid recreating these later.
v1.Set("allModules", moduleConfig.ActiveModules)
if moduleConfig.GoModulesFilename != "" {
// We want to watch this for changes and trigger rebuild on version
// changes etc.
configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
}
return moduleConfig.ActiveModules, configFilenames, err
}
func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme modules.Module) error {
const (
paramsKey = "params"
languagesKey = "languages"
menuKey = "menus"
)
v2 := theme.Cfg()
for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
l.mergeStringMapKeepLeft("", key, v1, v2)
}
// Only add params and new menu entries, we do not add language definitions.
if v1.IsSet(languagesKey) && v2.IsSet(languagesKey) {
v1Langs := v1.GetStringMap(languagesKey)
for k := range v1Langs {
langParamsKey := languagesKey + "." + k + "." + paramsKey
l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
}
v2Langs := v2.GetStringMap(languagesKey)
for k := range v2Langs {
if k == "" {
continue
}
langMenuKey := languagesKey + "." + k + "." + menuKey
if v2.IsSet(langMenuKey) {
// Only add if not in the main config.
v2menus := v2.GetStringMap(langMenuKey)
for k, v := range v2menus {
menuEntry := menuKey + "." + k
menuLangEntry := langMenuKey + "." + k
if !v1.IsSet(menuEntry) && !v1.IsSet(menuLangEntry) {
v1.Set(menuLangEntry, v)
}
}
}
}
}
// Add menu definitions from theme not found in project
if v2.IsSet(menuKey) {
v2menus := v2.GetStringMap(menuKey)
for k, v := range v2menus {
menuEntry := menuKey + "." + k
if !v1.IsSet(menuEntry) {
v1.SetDefault(menuEntry, v)
}
}
}
return nil
}
func (configLoader) mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
if !v2.IsSet(key) {
return
}
if !v1.IsSet(key) && !(rootKey != "" && rootKey != key && v1.IsSet(rootKey)) {
v1.Set(key, v2.Get(key))
servicesConfig, err := services.DecodeConfig(cfg)
if err != nil {
return
}
m1 := v1.GetStringMap(key)
m2 := v2.GetStringMap(key)
scfg.Privacy = privacyConfig
scfg.Services = servicesConfig
for k, v := range m2 {
if _, found := m1[k]; !found {
if rootKey != "" && v1.IsSet(rootKey+"."+k) {
continue
}
m1[k] = v
}
}
return
}
func loadDefaultSettingsFor(v *viper.Viper) error {
v.RegisterAlias("indexes", "taxonomies")
/*
TODO(bep) from 0.56 these are configured as module mounts.
v.SetDefault("contentDir", "content")
v.SetDefault("layoutDir", "layouts")
v.SetDefault("assetDir", "assets")
v.SetDefault("staticDir", "static")
v.SetDefault("dataDir", "data")
v.SetDefault("i18nDir", "i18n")
v.SetDefault("archetypeDir", "archetypes")
*/
v.SetDefault("cleanDestinationDir", false)
v.SetDefault("watch", false)
v.SetDefault("resourceDir", "resources")
v.SetDefault("publishDir", "public")
v.SetDefault("themesDir", "themes")
v.SetDefault("buildDrafts", false)
v.SetDefault("buildFuture", false)
v.SetDefault("buildExpired", false)
v.SetDefault("environment", hugo.EnvironmentProduction)
v.SetDefault("uglyURLs", false)
v.SetDefault("verbose", false)
v.SetDefault("ignoreCache", false)
v.SetDefault("canonifyURLs", false)
v.SetDefault("relativeURLs", false)
v.SetDefault("removePathAccents", false)
v.SetDefault("titleCaseStyle", "AP")
v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
v.SetDefault("permalinks", make(map[string]string))
v.SetDefault("sitemap", config.Sitemap{Priority: -1, Filename: "sitemap.xml"})
v.SetDefault("disableLiveReload", false)
v.SetDefault("pluralizeListTitles", true)
v.SetDefault("forceSyncStatic", false)
v.SetDefault("footnoteAnchorPrefix", "")
v.SetDefault("footnoteReturnLinkContents", "")
v.SetDefault("newContentEditor", "")
v.SetDefault("paginate", 10)
v.SetDefault("paginatePath", "page")
v.SetDefault("summaryLength", 70)
v.SetDefault("rssLimit", -1)
v.SetDefault("sectionPagesMenu", "")
v.SetDefault("disablePathToLower", false)
v.SetDefault("hasCJKLanguage", false)
v.SetDefault("enableEmoji", false)
v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
v.SetDefault("defaultContentLanguage", "en")
v.SetDefault("defaultContentLanguageInSubdir", false)
v.SetDefault("enableMissingTranslationPlaceholders", false)
v.SetDefault("enableGitInfo", false)
v.SetDefault("ignoreFiles", make([]string, 0))
v.SetDefault("disableAliases", false)
v.SetDefault("debug", false)
v.SetDefault("disableFastRender", false)
v.SetDefault("timeout", "30s")
v.SetDefault("enableInlineShortcodes", false)
return nil
func (l configLoader) wrapFileError(err error, filename string) error {
err, _ = herrors.WithFileContextForFile(
err,
filename,
filename,
l.Fs,
herrors.SimpleLineMatcher)
return err
}

View file

@ -17,11 +17,15 @@ import (
"bytes"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/gohugoio/hugo/media"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/maps"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestLoadConfig(t *testing.T) {
@ -77,12 +81,7 @@ func TestLoadConfigFromTheme(t *testing.T) {
c := qt.New(t)
mainConfigBasic := `
theme = "test-theme"
baseURL = "https://example.com/"
`
mainConfig := `
mainConfigTemplate := `
theme = "test-theme"
baseURL = "https://example.com/"
@ -90,9 +89,12 @@ baseURL = "https://example.com/"
date = ["date","publishDate"]
[params]
MERGE_PARAMS
p1 = "p1 main"
p2 = "p2 main"
top = "top"
[params.b]
b1 = "b1 main"
[params.b.c]
bc1 = "bc1 main"
[mediaTypes]
[mediaTypes."text/m1"]
@ -130,7 +132,14 @@ expiryDate = ["date"]
[params]
p1 = "p1 theme"
p2 = "p2 theme"
p3 = "p3 theme"
[params.b]
b1 = "b1 theme"
b2 = "b2 theme"
[params.b.c]
bc1 = "bc1 theme"
bc2 = "bc2 theme"
[params.b.c.d]
bcd1 = "bcd1 theme"
[mediaTypes]
[mediaTypes."text/m1"]
@ -176,190 +185,137 @@ name = "menu-theme"
`
b := newTestSitesBuilder(t)
b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig)
b.CreateSites().Build(BuildCfg{})
got := b.Cfg.(*viper.Viper).AllSettings()
b.AssertObject(`
map[string]interface {}{
"p1": "p1 main",
"p2": "p2 main",
"p3": "p3 theme",
"top": "top",
}`, got["params"])
b.AssertObject(`
map[string]interface {}{
"date": []interface {}{
"date",
"publishDate",
},
}`, got["frontmatter"])
b.AssertObject(`
map[string]interface {}{
"text/m1": map[string]interface {}{
"suffixes": []interface {}{
"m1main",
},
},
"text/m2": map[string]interface {}{
"suffixes": []interface {}{
"m2theme",
},
},
}`, got["mediatypes"])
b.AssertObject(`
map[string]interface {}{
"o1": map[string]interface {}{
"basename": "o1main",
"mediatype": Type{
MainType: "text",
SubType: "m1",
Delimiter: ".",
FirstSuffix: SuffixInfo{
Suffix: "m1main",
FullSuffix: ".m1main",
},
},
},
"o2": map[string]interface {}{
"basename": "o2theme",
"mediatype": Type{
MainType: "text",
SubType: "m2",
Delimiter: ".",
FirstSuffix: SuffixInfo{
Suffix: "m2theme",
FullSuffix: ".m2theme",
},
},
},
}`, got["outputformats"])
b.AssertObject(`map[string]interface {}{
"en": map[string]interface {}{
"languagename": "English",
"menus": map[string]interface {}{
"theme": []map[string]interface {}{
map[string]interface {}{
"name": "menu-lang-en-theme",
},
},
},
"params": map[string]interface {}{
"pl1": "p1-en-main",
"pl2": "p2-en-theme",
},
},
"nb": map[string]interface {}{
"languagename": "Norsk",
"menus": map[string]interface {}{
"theme": []map[string]interface {}{
map[string]interface {}{
"name": "menu-lang-nb-theme",
},
},
},
"params": map[string]interface {}{
"pl1": "p1-nb-main",
"pl2": "p2-nb-theme",
},
},
}
`, got["languages"])
b.AssertObject(`
map[string]interface {}{
"main": []map[string]interface {}{
map[string]interface {}{
"name": "menu-main-main",
},
},
"thememenu": []map[string]interface {}{
map[string]interface {}{
"name": "menu-theme",
},
},
"top": []map[string]interface {}{
map[string]interface {}{
"name": "menu-top-main",
},
},
}
`, got["menus"])
c.Assert(got["baseurl"], qt.Equals, "https://example.com/")
if true {
return
buildForStrategy := func(t testing.TB, s string) *sitesBuilder {
mainConfig := strings.ReplaceAll(mainConfigTemplate, "MERGE_PARAMS", s)
b := newTestSitesBuilder(t)
b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig)
return b.CreateSites().Build(BuildCfg{})
}
// Test variants with only values from theme
b = newTestSitesBuilder(t)
b.WithConfigFile("toml", mainConfigBasic).WithThemeConfigFile("toml", themeConfig)
b.CreateSites().Build(BuildCfg{})
got = b.Cfg.(*viper.Viper).AllSettings()
c.Run("Merge default", func(c *qt.C) {
b := buildForStrategy(c, "")
b.AssertObject(`map[string]interface {}{
"p1": "p1 theme",
"p2": "p2 theme",
"p3": "p3 theme",
"test-theme": map[string]interface {}{
"p1": "p1 theme",
"p2": "p2 theme",
"p3": "p3 theme",
},
}`, got["params"])
got := b.Cfg.Get("").(maps.Params)
c.Assert(got["languages"], qt.IsNil)
b.AssertObject(`
map[string]interface {}{
"text/m1": map[string]interface {}{
"suffix": "m1theme",
},
"text/m2": map[string]interface {}{
"suffix": "m2theme",
},
}`, got["mediatypes"])
b.Assert(got["params"], qt.DeepEquals, maps.Params{
"b": maps.Params{
"b1": "b1 main",
"c": maps.Params{
"bc1": "bc1 main",
"bc2": "bc2 theme",
"d": maps.Params{"bcd1": string("bcd1 theme")},
},
"b2": "b2 theme",
},
"p2": "p2 theme",
"p1": "p1 main",
})
b.Assert(got["mediatypes"], qt.DeepEquals, maps.Params{
"text/m2": maps.Params{
"suffixes": []interface{}{
"m2theme",
},
},
"text/m1": maps.Params{
"suffixes": []interface{}{
"m1main",
},
},
})
var eq = qt.CmpEquals(
cmp.Comparer(func(m1, m2 media.Type) bool {
if m1.SubType != m2.SubType {
return false
}
return m1.FirstSuffix == m2.FirstSuffix
}),
)
mediaTypes := b.H.Sites[0].mediaTypesConfig
m1, _ := mediaTypes.GetByType("text/m1")
m2, _ := mediaTypes.GetByType("text/m2")
b.Assert(got["outputformats"], eq, maps.Params{
"o1": maps.Params{
"mediatype": m1,
"basename": "o1main",
},
"o2": maps.Params{
"basename": "o2theme",
"mediatype": m2,
},
})
b.Assert(got["languages"], qt.DeepEquals, maps.Params{
"en": maps.Params{
"languagename": "English",
"params": maps.Params{
"pl2": "p2-en-theme",
"pl1": "p1-en-main",
},
"menus": maps.Params{
"main": []map[string]interface{}{
{
"name": "menu-lang-en-main",
},
},
"theme": []map[string]interface{}{
{
"name": "menu-lang-en-theme",
},
},
},
},
"nb": maps.Params{
"languagename": "Norsk",
"params": maps.Params{
"top": "top-nb-theme",
"pl1": "p1-nb-main",
"pl2": "p2-nb-theme",
},
"menus": maps.Params{
"main": []map[string]interface{}{
{
"name": "menu-lang-nb-main",
},
},
"theme": []map[string]interface{}{
{
"name": "menu-lang-nb-theme",
},
},
"top": []map[string]interface{}{
{
"name": "menu-lang-nb-top",
},
},
},
},
})
c.Assert(got["baseurl"], qt.Equals, "https://example.com/")
})
c.Run("Merge shallow", func(c *qt.C) {
b := buildForStrategy(c, fmt.Sprintf("_merge=%q", "shallow"))
got := b.Cfg.Get("").(maps.Params)
// Shallow merge, only add new keys to params.
b.Assert(got["params"], qt.DeepEquals, maps.Params{
"p1": "p1 main",
"b": maps.Params{
"b1": "b1 main",
"c": maps.Params{
"bc1": "bc1 main",
},
},
"p2": "p2 theme",
})
})
b.AssertObject(`
map[string]interface {}{
"o1": map[string]interface {}{
"basename": "o1theme",
"mediatype": Type{
MainType: "text",
SubType: "m1",
Suffix: "m1theme",
Delimiter: ".",
},
},
"o2": map[string]interface {}{
"basename": "o2theme",
"mediatype": Type{
MainType: "text",
SubType: "m2",
Suffix: "m2theme",
Delimiter: ".",
},
},
}`, got["outputformats"])
b.AssertObject(`
map[string]interface {}{
"main": []interface {}{
map[string]interface {}{
"name": "menu-main-theme",
},
},
"thememenu": []interface {}{
map[string]interface {}{
"name": "menu-theme",
},
},
}`, got["menu"])
}
func TestPrivacyConfig(t *testing.T) {
@ -490,7 +446,12 @@ intSlice = [5,7,9]
floatSlice = [3.14, 5.19]
stringSlice = ["a", "b"]
[outputFormats]
[outputFormats.ofbase]
mediaType = "text/plain"
[params]
paramWithNoEnvOverride="nooverride"
[params.api_config]
api_key="default_key"
another_key="default another_key"
@ -504,9 +465,16 @@ quality = 75
b.WithSourceFile("themes/mytheme/config.toml", `
[outputFormats]
[outputFormats.oftheme]
mediaType = "text/plain"
[outputFormats.ofbase]
mediaType = "application/xml"
[params]
[params.mytheme_section]
theme_param="themevalue"
theme_param_nooverride="nooverride"
[params.mytheme_section2]
theme_param="themevalue2"
@ -530,14 +498,16 @@ theme_param="themevalue2"
"HUGOxPARAMSxMYTHEME_SECTION2xTHEME_PARAM", "themevalue2_changed",
"HUGO_PARAMS_EMPTY", ``,
"HUGO_PARAMS_HTML", `<a target="_blank" />`,
//
// Issue #8618
"HUGO_SERVICES_GOOGLEANALYTICS_ID", `gaid`,
"HUGO_PARAMS_A_B_C", "abc",
)
b.Build(BuildCfg{})
cfg := b.H.Cfg
scfg := b.H.Sites[0].siteConfigConfig.Services
s := b.H.Sites[0]
scfg := s.siteConfigConfig.Services
c.Assert(cfg.Get("environment"), qt.Equals, "test")
c.Assert(cfg.GetBool("enablegitinfo"), qt.Equals, false)
@ -551,9 +521,23 @@ theme_param="themevalue2"
c.Assert(cfg.Get("params.api_config.api_key"), qt.Equals, "new_key")
c.Assert(cfg.Get("params.api_config.another_key"), qt.Equals, "default another_key")
c.Assert(cfg.Get("params.mytheme_section.theme_param"), qt.Equals, "themevalue_changed")
c.Assert(cfg.Get("params.mytheme_section.theme_param_nooverride"), qt.Equals, "nooverride")
c.Assert(cfg.Get("params.mytheme_section2.theme_param"), qt.Equals, "themevalue2_changed")
c.Assert(cfg.Get("params.empty"), qt.Equals, ``)
c.Assert(cfg.Get("params.html"), qt.Equals, `<a target="_blank" />`)
params := cfg.Get("params").(maps.Params)
c.Assert(params["paramwithnoenvoverride"], qt.Equals, "nooverride")
c.Assert(cfg.Get("params.paramwithnoenvoverride"), qt.Equals, "nooverride")
c.Assert(scfg.GoogleAnalytics.ID, qt.Equals, "gaid")
c.Assert(cfg.Get("params.a.b"), qt.DeepEquals, maps.Params{
"c": "abc",
})
ofBase, _ := s.outputFormatsConfig.GetByName("ofbase")
ofTheme, _ := s.outputFormatsConfig.GetByName("oftheme")
c.Assert(ofBase.MediaType, qt.Equals, media.TextType)
c.Assert(ofTheme.MediaType, qt.Equals, media.TextType)
}

View file

@ -33,7 +33,7 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/modules"
"github.com/spf13/viper"
)
func initConfig(fs afero.Fs, cfg config.Provider) error {
@ -76,7 +76,7 @@ func initConfig(fs afero.Fs, cfg config.Provider) error {
func TestNewBaseFs(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
fs := hugofs.NewMem(v)
@ -181,8 +181,8 @@ theme = ["atheme"]
}
}
func createConfig() *viper.Viper {
v := viper.New()
func createConfig() config.Provider {
v := config.New()
v.Set("contentDir", "mycontent")
v.Set("i18nDir", "myi18n")
v.Set("staticDir", "mystatic")
@ -453,7 +453,7 @@ func countFilesAndGetFilenames(fs afero.Fs, dirname string) (int, []string, erro
return counter, filenames, nil
}
func setConfigAndWriteSomeFilesTo(fs afero.Fs, v *viper.Viper, key, val string, num int) {
func setConfigAndWriteSomeFilesTo(fs afero.Fs, v config.Provider, key, val string, num int) {
workingDir := v.GetString("workingDir")
v.Set(key, val)
fs.Mkdir(val, 0755)

View file

@ -22,6 +22,7 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/modules/npm"
"github.com/gohugoio/hugo/common/loggers"
@ -37,7 +38,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/testmodBuilder/mods"
"github.com/spf13/viper"
)
func TestHugoModulesVariants(t *testing.T) {
@ -45,7 +45,7 @@ func TestHugoModulesVariants(t *testing.T) {
t.Skip("skip (relative) long running modules test when running locally")
}
config := `
tomlConfig := `
baseURL="https://example.org"
workingDir = %q
@ -56,7 +56,7 @@ path="github.com/gohugoio/hugoTestModule2"
`
createConfig := func(workingDir, moduleOpts string) string {
return fmt.Sprintf(config, workingDir, moduleOpts)
return fmt.Sprintf(tomlConfig, workingDir, moduleOpts)
}
newTestBuilder := func(t testing.TB, moduleOpts string) (*sitesBuilder, func()) {
@ -65,7 +65,7 @@ path="github.com/gohugoio/hugoTestModule2"
b.Assert(err, qt.IsNil)
workingDir := filepath.Join(tempDir, "myhugosite")
b.Assert(os.MkdirAll(workingDir, 0777), qt.IsNil)
b.Fs = hugofs.NewDefault(viper.New())
b.Fs = hugofs.NewDefault(config.New())
b.WithWorkingDir(workingDir).WithConfigFile("toml", createConfig(workingDir, moduleOpts))
b.WithTemplates(
"index.html", `
@ -333,7 +333,7 @@ func TestHugoModulesMatrix(t *testing.T) {
for _, m := range testmods[:2] {
c := qt.New(t)
v := viper.New()
v := config.New()
workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-test")
c.Assert(err, qt.IsNil)
@ -671,7 +671,7 @@ func TestModulesSymlinks(t *testing.T) {
c := qt.New(t)
// We need to use the OS fs for this.
cfg := viper.New()
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mod-sym")
@ -839,13 +839,13 @@ workingDir = %q
`
config := fmt.Sprintf(configTemplate, workingDir)
tomlConfig := fmt.Sprintf(configTemplate, workingDir)
b := newTestSitesBuilder(t).Running()
b.Fs = hugofs.NewDefault(viper.New())
b.Fs = hugofs.NewDefault(config.New())
b.WithWorkingDir(workingDir).WithConfigFile("toml", config)
b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig)
b.WithTemplatesAdded("index.html", `
{{ .Title }}
{{ .Content }}
@ -960,16 +960,16 @@ workingDir = %q
%s
`
config := fmt.Sprintf(configTemplate, workingDir, mounts)
config = strings.Replace(config, "WORKING_DIR", workingDir, -1)
tomlConfig := fmt.Sprintf(configTemplate, workingDir, mounts)
tomlConfig = strings.Replace(tomlConfig, "WORKING_DIR", workingDir, -1)
b := newTestSitesBuilder(c).Running()
b.Fs = hugofs.NewDefault(viper.New())
b.Fs = hugofs.NewDefault(config.New())
os.MkdirAll(filepath.Join(workingDir, "content", "blog"), 0777)
b.WithWorkingDir(workingDir).WithConfigFile("toml", config)
b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig)
return test{
b: b,
@ -1064,7 +1064,7 @@ func TestSiteWithGoModButNoModules(t *testing.T) {
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod")
c.Assert(err, qt.IsNil)
cfg := viper.New()
cfg := config.New()
cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg)
@ -1090,7 +1090,7 @@ func TestModuleAbsMount(t *testing.T) {
absContentDir, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-content")
c.Assert(err, qt.IsNil)
cfg := viper.New()
cfg := config.New()
cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg)

View file

@ -374,7 +374,8 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
s.h = h
}
if err := applyDeps(cfg, sites...); err != nil {
var l configLoader
if err := l.applyDeps(cfg, sites...); err != nil {
return nil, errors.Wrap(err, "add site dependencies")
}
@ -407,7 +408,7 @@ func (h *HugoSites) loadGitInfo() error {
return nil
}
func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
func (l configLoader) applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
if cfg.TemplateProvider == nil {
cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
}
@ -446,7 +447,7 @@ func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
d.Site = s.Info
siteConfig, err := loadSiteConfig(s.language)
siteConfig, err := l.loadSiteConfig(s.language)
if err != nil {
return errors.Wrap(err, "load site config")
}
@ -607,11 +608,12 @@ func (h *HugoSites) withSite(fn func(s *Site) error) error {
func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error {
oldLangs, _ := h.Cfg.Get("languagesSorted").(langs.Languages)
if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil {
l := configLoader{cfg: h.Cfg}
if err := l.loadLanguageSettings(oldLangs); err != nil {
return err
}
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: cfg}
depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: l.cfg}
sites, err := createSitesFromConfig(depsCfg)
if err != nil {
@ -629,7 +631,8 @@ func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error {
s.h = h
}
if err := applyDeps(depsCfg, sites...); err != nil {
var cl configLoader
if err := cl.applyDeps(depsCfg, sites...); err != nil {
return err
}

View file

@ -5,9 +5,7 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/fortytw2/leaktest"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
@ -318,7 +316,7 @@ Some content.
// https://github.com/gohugoio/hugo/issues/5375
func TestSiteBuildTimeout(t *testing.T) {
if !htesting.IsCI() {
defer leaktest.CheckTimeout(t, 10*time.Second)()
//defer leaktest.CheckTimeout(t, 10*time.Second)()
}
b := newTestSitesBuilder(t)

View file

@ -21,11 +21,11 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
)
// We have many tests for the different resize operations etc. in the resource package,
@ -38,7 +38,7 @@ func TestImageOps(t *testing.T) {
defer clean()
newBuilder := func(timeout interface{}) *sitesBuilder {
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
v.Set("baseURL", "https://example.org")
v.Set("timeout", timeout)

View file

@ -21,11 +21,10 @@ import (
"testing"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
@ -88,7 +87,7 @@ document.body.textContent = greeter(user);`
c.Assert(err, qt.IsNil)
defer clean()
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
v.Set("disableKinds", []string{"taxonomy", "term", "page"})
b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
@ -162,7 +161,7 @@ func TestJSBuild(t *testing.T) {
c.Assert(err, qt.IsNil)
defer clean()
config := fmt.Sprintf(`
tomlConfig := fmt.Sprintf(`
baseURL = "https://example.org"
workingDir = %q
@ -177,8 +176,8 @@ path="github.com/gohugoio/hugoTestProjectJSModImports"
`, workDir)
b := newTestSitesBuilder(t)
b.Fs = hugofs.NewDefault(viper.New())
b.WithWorkingDir(workDir).WithConfigFile("toml", config).WithLogger(loggers.NewInfoLogger())
b.Fs = hugofs.NewDefault(config.New())
b.WithWorkingDir(workDir).WithConfigFile("toml", tomlConfig).WithLogger(loggers.NewInfoLogger())
b.WithSourceFile("go.mod", `module github.com/gohugoio/tests/testHugoModules
go 1.15

View file

@ -16,13 +16,13 @@ package hugolib
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
)
func TestMinifyPublisher(t *testing.T) {
t.Parallel()
v := viper.New()
v := config.New()
v.Set("minify", true)
v.Set("baseURL", "https://example.org/")

View file

@ -336,7 +336,7 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
if frontmatter != nil {
// Needed for case insensitive fetching of params values
maps.ToLower(frontmatter)
maps.PrepareParams(frontmatter)
if p.bucket != nil {
// Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found {

View file

@ -37,7 +37,6 @@ import (
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
@ -786,7 +785,7 @@ func TestPageWithLastmodFromGitInfo(t *testing.T) {
c := qt.New(t)
// We need to use the OS fs for this.
cfg := viper.New()
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
fs.Destination = &afero.MemMapFs{}
@ -1066,7 +1065,7 @@ func TestChompBOM(t *testing.T) {
func TestPageWithEmoji(t *testing.T) {
for _, enableEmoji := range []bool{true, false} {
v := viper.New()
v := config.New()
v.Set("enableEmoji", enableEmoji)
b := newTestSitesBuilder(t).WithViper(v)

View file

@ -23,6 +23,8 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/helpers"
@ -35,7 +37,6 @@ import (
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/deps"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
@ -352,12 +353,11 @@ func TestMultilingualDisableDefaultLanguage(t *testing.T) {
c := qt.New(t)
_, cfg := newTestBundleSourcesMultilingual(t)
cfg.Set("disableLanguages", []string{"en"})
err := loadDefaultSettingsFor(cfg)
l := configLoader{cfg: cfg}
err := l.applyConfigDefaults()
c.Assert(err, qt.IsNil)
err = loadLanguageSettings(cfg, nil)
err = l.loadLanguageSettings(nil)
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err.Error(), qt.Contains, "cannot disable default language")
}
@ -397,7 +397,7 @@ func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
c := qt.New(t)
// We need to use the OS fs for this.
cfg := viper.New()
cfg := config.New()
fs := hugofs.NewFrom(hugofs.Os, cfg)
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym")
@ -696,7 +696,7 @@ Single content.
b.AssertFileContent("public/section-not-bundle/single/index.html", "Section Single", "|<p>Single content.</p>")
}
func newTestBundleSources(t testing.TB) (*hugofs.Fs, *viper.Viper) {
func newTestBundleSources(t testing.TB) (*hugofs.Fs, config.Provider) {
cfg, fs := newTestCfgBasic()
c := qt.New(t)
@ -863,7 +863,7 @@ Content for 은행.
return fs, cfg
}
func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, *viper.Viper) {
func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, config.Provider) {
cfg, fs := newTestCfgBasic()
workDir := "/work"
@ -1319,7 +1319,7 @@ func TestPageBundlerHome(t *testing.T) {
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home")
c.Assert(err, qt.IsNil)
cfg := viper.New()
cfg := config.New()
cfg.Set("workingDir", workDir)
fs := hugofs.NewFrom(hugofs.Os, cfg)

View file

@ -130,7 +130,7 @@ func (c *pagesCollector) isCascadingEdit(dir contentDirKey) (bool, string) {
section = s
maps.ToLower(pf.FrontMatter)
maps.PrepareParams(pf.FrontMatter)
cascade1, ok := pf.FrontMatter["cascade"]
hasCascade := n.p.bucket.cascade != nil && len(n.p.bucket.cascade) > 0
if !ok {

View file

@ -16,17 +16,16 @@ package paths
import (
"testing"
"github.com/gohugoio/hugo/langs"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/langs"
)
func TestNewPaths(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
fs := hugofs.NewMem(v)
v.Set("languages", map[string]interface{}{

View file

@ -19,14 +19,14 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/hexec"
jww "github.com/spf13/jwalterweatherman"
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
@ -91,7 +91,7 @@ class Car2 {
var logBuf bytes.Buffer
logger := loggers.NewBasicLoggerForWriter(jww.LevelInfo, &logBuf)
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
v.Set("disableKinds", []string{"taxonomy", "term", "page"})
b := newTestSitesBuilder(t).WithLogger(logger)

View file

@ -20,6 +20,8 @@ import (
"math/rand"
"os"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"path/filepath"
@ -35,8 +37,6 @@ import (
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
@ -65,7 +65,7 @@ func TestSCSSWithIncludePaths(t *testing.T) {
c.Assert(err, qt.IsNil)
defer clean()
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
// Need to use OS fs for this.
@ -130,7 +130,7 @@ func TestSCSSWithRegularCSSImport(t *testing.T) {
c.Assert(err, qt.IsNil)
defer clean()
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
// Need to use OS fs for this.
@ -230,7 +230,7 @@ func TestSCSSWithThemeOverrides(t *testing.T) {
theme := "mytheme"
themesDir := filepath.Join(workDir, "themes")
themeDirs := filepath.Join(themesDir, theme)
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
v.Set("theme", theme)
b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
@ -345,7 +345,7 @@ func TestSCSSWithIncludePathsSass(t *testing.T) {
c.Assert(err, qt.IsNil)
defer clean1()
v := viper.New()
v := config.New()
v.Set("workingDir", workDir)
v.Set("theme", "mytheme")
b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
@ -974,7 +974,7 @@ h1 {
var logBuf bytes.Buffer
newTestBuilder := func(v *viper.Viper) *sitesBuilder {
newTestBuilder := func(v config.Provider) *sitesBuilder {
v.Set("workingDir", workDir)
v.Set("disableKinds", []string{"taxonomy", "term", "page"})
logger := loggers.NewBasicLoggerForWriter(jww.LevelInfo, &logBuf)
@ -997,7 +997,7 @@ Styles Content: Len: {{ len $styles.Content }}|
return b
}
b := newTestBuilder(viper.New())
b := newTestBuilder(config.New())
cssDir := filepath.Join(workDir, "assets", "css", "components")
b.Assert(os.MkdirAll(cssDir, 0777), qt.IsNil)
@ -1049,7 +1049,7 @@ Styles Content: Len: 770878|
build := func(s string, shouldFail bool) error {
b.Assert(os.RemoveAll(filepath.Join(workDir, "public")), qt.IsNil)
v := viper.New()
v := config.New()
v.Set("build", map[string]interface{}{
"useResourceCacheWhen": s,
})

View file

@ -16,7 +16,7 @@ package hugolib
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
)
const robotTxtTemplate = `User-agent: Googlebot
@ -28,7 +28,7 @@ const robotTxtTemplate = `User-agent: Googlebot
func TestRobotsTXTOutput(t *testing.T) {
t.Parallel()
cfg := viper.New()
cfg := config.New()
cfg.Set("baseURL", "http://auth/bub/")
cfg.Set("enableRobotsTXT", true)

View file

@ -20,11 +20,10 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/asciidocext"
"github.com/gohugoio/hugo/markup/rst"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/gohugoio/hugo/resources/page"
@ -1214,7 +1213,7 @@ title: "Hugo Rocks!"
func TestShortcodeEmoji(t *testing.T) {
t.Parallel()
v := viper.New()
v := config.New()
v.Set("enableEmoji", true)
builder := newTestSitesBuilder(t).WithViper(v)
@ -1279,7 +1278,7 @@ func TestShortcodeRef(t *testing.T) {
t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) {
t.Parallel()
v := viper.New()
v := config.New()
v.Set("baseURL", "https://example.org")
v.Set("blackfriday", map[string]interface{}{
"plainIDAnchors": plainIDAnchors,

View file

@ -77,7 +77,6 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
// Site contains all the information relevant for constructing a static
@ -501,9 +500,9 @@ But this also means that your site configuration may not do what you expect. If
var relatedContentConfig related.Config
if cfg.Language.IsSet("related") {
relatedContentConfig, err = related.DecodeConfig(cfg.Language.Get("related"))
relatedContentConfig, err = related.DecodeConfig(cfg.Language.GetParams("related"))
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to decode related config")
}
} else {
relatedContentConfig = related.DefaultConfig
@ -574,7 +573,8 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
return nil, err
}
if err = applyDeps(cfg, s); err != nil {
var l configLoader
if err = l.applyDeps(cfg, s); err != nil {
return nil, err
}
@ -586,11 +586,11 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
// Note: This is mainly used in single site tests.
// TODO(bep) test refactor -- remove
func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
v := viper.New()
if err := loadDefaultSettingsFor(v); err != nil {
l := configLoader{cfg: config.New()}
if err := l.applyConfigDefaults(); err != nil {
return nil, err
}
return newSiteForLang(langs.NewDefaultLanguage(v), withTemplate...)
return newSiteForLang(langs.NewDefaultLanguage(l.cfg), withTemplate...)
}
// NewEnglishSite creates a new site in English language.
@ -598,11 +598,11 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (
// Note: This is mainly used in single site tests.
// TODO(bep) test refactor -- remove
func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
v := viper.New()
if err := loadDefaultSettingsFor(v); err != nil {
l := configLoader{cfg: config.New()}
if err := l.applyConfigDefaults(); err != nil {
return nil, err
}
return newSiteForLang(langs.NewLanguage("en", v), withTemplate...)
return newSiteForLang(langs.NewLanguage("en", l.cfg), withTemplate...)
}
// newSiteForLang creates a new site in the given language.
@ -1314,7 +1314,7 @@ func (s *Site) initializeSiteInfo() error {
return vvv
}
default:
m := cast.ToStringMapBool(v)
m := maps.ToStringMapBool(v)
uglyURLs = func(p page.Page) bool {
return m[p.Section()]
}

View file

@ -19,13 +19,13 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/resources/page"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/output"
"github.com/spf13/viper"
)
func TestSiteWithPageOutputs(t *testing.T) {
@ -333,7 +333,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
page.KindSection: []string{"JSON"},
}
cfg := viper.New()
cfg := config.New()
cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -358,7 +358,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
// Issue #4528
t.Run("Mixed case", func(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
outputsConfig := map[string]interface{}{
// Note that we in Hugo 0.53.0 renamed this Kind to "taxonomy",
@ -380,7 +380,7 @@ func TestCreateSiteOutputFormatsInvalidConfig(t *testing.T) {
page.KindHome: []string{"FOO", "JSON"},
}
cfg := viper.New()
cfg := config.New()
cfg.Set("outputs", outputsConfig)
_, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -394,7 +394,7 @@ func TestCreateSiteOutputFormatsEmptyConfig(t *testing.T) {
page.KindHome: []string{},
}
cfg := viper.New()
cfg := config.New()
cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@ -409,7 +409,7 @@ func TestCreateSiteOutputFormatsCustomFormats(t *testing.T) {
page.KindHome: []string{},
}
cfg := viper.New()
cfg := config.New()
cfg.Set("outputs", outputsConfig)
var (

View file

@ -23,10 +23,9 @@ import (
"testing"
"github.com/gobuffalo/flect"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/publisher"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
@ -363,7 +362,7 @@ func TestMainSections(t *testing.T) {
c := qt.New(t)
for _, paramSet := range []bool{false, true} {
c.Run(fmt.Sprintf("param-%t", paramSet), func(c *qt.C) {
v := viper.New()
v := config.New()
if paramSet {
v.Set("params", map[string]interface{}{
"mainSections": []string{"a1", "a2"},

View file

@ -19,20 +19,19 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/identity"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/tpl"
"github.com/spf13/viper"
)
func TestTemplateLookupOrder(t *testing.T) {
var (
fs *hugofs.Fs
cfg *viper.Viper
cfg config.Provider
th testHelper
)

View file

@ -30,6 +30,7 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
@ -39,7 +40,6 @@ import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/tpl"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/resources/resource"
@ -83,7 +83,7 @@ type sitesBuilder struct {
// Default toml
configFormat string
configFileSet bool
viperSet bool
configSet bool
// Default is empty.
// TODO(bep) revisit this and consider always setting it to something.
@ -111,7 +111,7 @@ type filenameContent struct {
}
func newTestSitesBuilder(t testing.TB) *sitesBuilder {
v := viper.New()
v := config.New()
fs := hugofs.NewMem(v)
litterOptions := litter.Options{
@ -140,7 +140,7 @@ func newTestSitesBuilderFromDepsCfg(t testing.TB, d deps.DepsCfg) *sitesBuilder
b.WithWorkingDir(workingDir)
return b.WithViper(d.Cfg.(*viper.Viper))
return b.WithViper(d.Cfg.(config.Provider))
}
func (s *sitesBuilder) Running() *sitesBuilder {
@ -186,26 +186,26 @@ func (s *sitesBuilder) WithConfigTemplate(data interface{}, format, configTempla
return s.WithConfigFile(format, b.String())
}
func (s *sitesBuilder) WithViper(v *viper.Viper) *sitesBuilder {
func (s *sitesBuilder) WithViper(v config.Provider) *sitesBuilder {
s.T.Helper()
if s.configFileSet {
s.T.Fatal("WithViper: use Viper or config.toml, not both")
}
defer func() {
s.viperSet = true
s.configSet = true
}()
// Write to a config file to make sure the tests follow the same code path.
var buff bytes.Buffer
m := v.AllSettings()
m := v.Get("").(maps.Params)
s.Assert(parser.InterfaceToConfig(m, metadecoders.TOML, &buff), qt.IsNil)
return s.WithConfigFile("toml", buff.String())
}
func (s *sitesBuilder) WithConfigFile(format, conf string) *sitesBuilder {
s.T.Helper()
if s.viperSet {
s.T.Fatal("WithConfigFile: use Viper or config.toml, not both")
if s.configSet {
s.T.Fatal("WithConfigFile: use config.Config or config.toml, not both")
}
s.configFileSet = true
filename := s.absFilename("config." + format)
@ -845,14 +845,14 @@ func (th testHelper) replaceDefaultContentLanguageValue(value string) string {
return value
}
func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error) (*viper.Viper, error) {
func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error) (config.Provider, error) {
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs}, withConfig...)
return v, err
}
func newTestCfgBasic() (*viper.Viper, *hugofs.Fs) {
func newTestCfgBasic() (config.Provider, *hugofs.Fs) {
mm := afero.NewMemMapFs()
v := viper.New()
v := config.New()
v.Set("defaultContentLanguageInSubdir", true)
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v)
@ -860,7 +860,7 @@ func newTestCfgBasic() (*viper.Viper, *hugofs.Fs) {
return v, fs
}
func newTestCfg(withConfig ...func(cfg config.Provider) error) (*viper.Viper, *hugofs.Fs) {
func newTestCfg(withConfig ...func(cfg config.Provider) error) (config.Provider, *hugofs.Fs) {
mm := afero.NewMemMapFs()
v, err := loadTestConfig(mm, func(cfg config.Provider) error {

View file

@ -43,13 +43,13 @@ func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesC
var languages map[string]interface{}
languagesFromConfig := cfg.GetStringMap("languages")
languagesFromConfig := cfg.GetParams("languages")
disableLanguages := cfg.GetStringSlice("disableLanguages")
if len(disableLanguages) == 0 {
languages = languagesFromConfig
} else {
languages = make(map[string]interface{})
languages = make(maps.Params)
for k, v := range languagesFromConfig {
for _, disabled := range disableLanguages {
if disabled == defaultLang {
@ -57,7 +57,7 @@ func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesC
}
if strings.EqualFold(k, disabled) {
v.(map[string]interface{})["disabled"] = true
v.(maps.Params)["disabled"] = true
break
}
}
@ -193,7 +193,7 @@ func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (Languages
case "params":
m := maps.ToStringMap(v)
// Needed for case insensitive fetching of params values
maps.ToLower(m)
maps.PrepareParams(m)
for k, vv := range m {
language.SetParam(k, vv)
}

View file

@ -28,7 +28,6 @@ import (
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/resources/page"
"github.com/spf13/afero"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/deps"
@ -500,9 +499,9 @@ func newDepsConfig(tp *TranslationProvider, cfg config.Provider, fs *hugofs.Fs)
}
}
func getConfig() *viper.Viper {
v := viper.New()
v.SetDefault("defaultContentLanguage", "en")
func getConfig() config.Provider {
v := config.New()
v.Set("defaultContentLanguage", "en")
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")

View file

@ -20,7 +20,6 @@ import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/spf13/cast"
)
// These are the settings that should only be looked up in the global Viper
@ -55,18 +54,20 @@ type Language struct {
// absolute directory reference. It is what we get.
ContentDir string
// Global config.
Cfg config.Provider
// Language specific config.
LocalCfg config.Provider
// Composite config.
config.Provider
// These are params declared in the [params] section of the language merged with the
// site's params, the most specific (language) wins on duplicate keys.
params map[string]interface{}
paramsMu sync.Mutex
paramsSet bool
// These are config values, i.e. the settings declared outside of the [params] section of the language.
// This is the map Hugo looks in when looking for configuration values (baseURL etc.).
// Values in this map can also be fetched from the params map above.
settings map[string]interface{}
}
func (l *Language) String() string {
@ -81,9 +82,12 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
for k, v := range cfg.GetStringMap("params") {
params[k] = v
}
maps.ToLower(params)
maps.PrepareParams(params)
l := &Language{Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, params: params, settings: make(map[string]interface{})}
localCfg := config.New()
compositeConfig := config.NewCompositeConfig(cfg, localCfg)
l := &Language{Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, LocalCfg: localCfg, Provider: compositeConfig, params: params}
return l
}
@ -133,7 +137,7 @@ func (l *Language) Params() maps.Params {
l.paramsMu.Lock()
defer l.paramsMu.Unlock()
if !l.paramsSet {
maps.ToLower(l.params)
maps.PrepareParams(l.params)
l.paramsSet = true
}
return l.params
@ -183,42 +187,6 @@ func (l *Language) SetParam(k string, v interface{}) {
l.params[k] = v
}
// GetBool returns the value associated with the key as a boolean.
func (l *Language) GetBool(key string) bool { return cast.ToBool(l.Get(key)) }
// GetString returns the value associated with the key as a string.
func (l *Language) GetString(key string) string { return cast.ToString(l.Get(key)) }
// GetInt returns the value associated with the key as an int.
func (l *Language) GetInt(key string) int { return cast.ToInt(l.Get(key)) }
// GetStringMap returns the value associated with the key as a map of interfaces.
func (l *Language) GetStringMap(key string) map[string]interface{} {
return maps.ToStringMap(l.Get(key))
}
// GetStringMapString returns the value associated with the key as a map of strings.
func (l *Language) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(l.Get(key))
}
// GetStringSlice returns the value associated with the key as a slice of strings.
func (l *Language) GetStringSlice(key string) []string {
return cast.ToStringSlice(l.Get(key))
}
// Get returns a value associated with the key relying on specified language.
// Get is case-insensitive for a key.
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func (l *Language) Get(key string) interface{} {
local := l.GetLocal(key)
if local != nil {
return local
}
return l.Cfg.Get(key)
}
// GetLocal gets a configuration value set on language level. It will
// not fall back to any global value.
// It will return nil if a value with the given key cannot be found.
@ -228,31 +196,29 @@ func (l *Language) GetLocal(key string) interface{} {
}
key = strings.ToLower(key)
if !globalOnlySettings[key] {
if v, ok := l.settings[key]; ok {
return v
}
return l.LocalCfg.Get(key)
}
return nil
}
// Set sets the value for the key in the language's params.
func (l *Language) Set(key string, value interface{}) {
if l == nil {
panic("language not set")
func (l *Language) Set(k string, v interface{}) {
k = strings.ToLower(k)
if globalOnlySettings[k] {
return
}
key = strings.ToLower(key)
l.settings[key] = value
l.Provider.Set(k, v)
}
// Merge is currently not supported for Language.
func (l *Language) Merge(key string, value interface{}) {
panic("Not supported")
}
// IsSet checks whether the key is set in the language or the related config store.
func (l *Language) IsSet(key string) bool {
key = strings.ToLower(key)
key = strings.ToLower(key)
if !globalOnlySettings[key] {
if _, ok := l.settings[key]; ok {
return true
}
return l.Provider.IsSet(key)
}
return l.Cfg.IsSet(key)
}

View file

@ -16,13 +16,14 @@ package langs
import (
"testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestGetGlobalOnlySetting(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("defaultContentLanguageInSubdir", true)
v.Set("contentDir", "content")
v.Set("paginatePath", "page")
@ -37,7 +38,7 @@ func TestGetGlobalOnlySetting(t *testing.T) {
func TestLanguageParams(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("p1", "p1cfg")
v.Set("contentDir", "content")

View file

@ -22,17 +22,17 @@ import (
"testing"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
func TestAsciidoctorDefaultArgs(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
p, err := Provider.New(
@ -57,7 +57,7 @@ func TestAsciidoctorDefaultArgs(t *testing.T) {
func TestAsciidoctorNonDefaultArgs(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Backend = "manpage"
mconf.AsciidocExt.NoHeaderOrFooter = false
@ -88,7 +88,7 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
func TestAsciidoctorDisallowedArgs(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Backend = "disallowed-backend"
mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
@ -117,7 +117,7 @@ func TestAsciidoctorDisallowedArgs(t *testing.T) {
func TestAsciidoctorArbitraryExtension(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
p, err := Provider.New(
@ -142,7 +142,7 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
func TestAsciidoctorDisallowedExtension(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
for _, disallowedExtension := range []string{
`foo-bar//`,
`foo-bar\\ `,
@ -177,7 +177,7 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.WorkingFolderCurrent = true
mconf.AsciidocExt.Trace = false
@ -208,7 +208,7 @@ func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.NoHeaderOrFooter = true
mconf.AsciidocExt.Extensions = []string{"asciidoctor-html5s", "asciidoctor-diagram"}
@ -247,7 +247,7 @@ func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
func TestAsciidoctorAttributes(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
mconf := markup_config.Default
mconf.AsciidocExt.Attributes = map[string]string{"my-base-url": "https://gohugo.io/", "my-attribute-name": "my value"}
mconf.AsciidocExt.Trace = false

View file

@ -16,7 +16,7 @@ package blackfriday
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/converter"
@ -140,7 +140,7 @@ func TestGetAllFlags(t *testing.T) {
func TestConvert(t *testing.T) {
c := qt.New(t)
p, err := Provider.New(converter.ProviderConfig{
Cfg: viper.New(),
Cfg: config.New(),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
@ -153,7 +153,7 @@ func TestConvert(t *testing.T) {
func TestGetHTMLRendererAnchors(t *testing.T) {
c := qt.New(t)
p, err := Provider.New(converter.ProviderConfig{
Cfg: viper.New(),
Cfg: config.New(),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{

View file

@ -17,16 +17,15 @@ package highlight
import (
"testing"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
)
func TestConfig(t *testing.T) {
c := qt.New(t)
c.Run("applyLegacyConfig", func(c *qt.C) {
v := viper.New()
v := config.New()
v.Set("pygmentsStyle", "hugo")
v.Set("pygmentsUseClasses", false)
v.Set("pygmentsCodeFences", false)

View file

@ -24,7 +24,6 @@ import (
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/gohugoio/hugo/parser"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
)
type Config struct {
@ -73,7 +72,7 @@ func normalizeConfig(m map[string]interface{}) {
if err != nil {
return
}
vm := cast.ToStringMap(v)
vm := maps.ToStringMap(v)
// Changed from a bool in 0.81.0
if vv, found := vm["attribute"]; found {
if vvb, ok := vv.(bool); ok {

View file

@ -16,7 +16,7 @@ package markup_config
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
)
@ -26,7 +26,7 @@ func TestConfig(t *testing.T) {
c.Run("Decode", func(c *qt.C) {
c.Parallel()
v := viper.New()
v := config.New()
v.Set("markup", map[string]interface{}{
"goldmark": map[string]interface{}{
@ -55,7 +55,7 @@ func TestConfig(t *testing.T) {
c.Run("legacy", func(c *qt.C) {
c.Parallel()
v := viper.New()
v := config.New()
v.Set("blackfriday", map[string]interface{}{
"angledQuotes": true,

View file

@ -16,17 +16,15 @@ package markup
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/markup/converter"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/converter"
)
func TestConverterRegistry(t *testing.T) {
c := qt.New(t)
r, err := NewConverterProvider(converter.ProviderConfig{Cfg: viper.New()})
r, err := NewConverterProvider(converter.ProviderConfig{Cfg: config.New()})
c.Assert(err, qt.IsNil)
c.Assert("goldmark", qt.Equals, r.GetMarkupConfig().DefaultMarkdownHandler)

View file

@ -16,7 +16,7 @@ package mmark
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers"
@ -62,7 +62,7 @@ func TestGetMmarkExtensions(t *testing.T) {
func TestConvert(t *testing.T) {
c := qt.New(t)
p, err := Provider.New(converter.ProviderConfig{Cfg: viper.New(), Logger: loggers.NewErrorLogger()})
p, err := Provider.New(converter.ProviderConfig{Cfg: config.New(), Logger: loggers.NewErrorLogger()})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})
c.Assert(err, qt.IsNil)

View file

@ -16,8 +16,9 @@ package org
import (
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/markup/converter"
@ -28,7 +29,7 @@ func TestConvert(t *testing.T) {
c := qt.New(t)
p, err := Provider.New(converter.ProviderConfig{
Logger: loggers.NewErrorLogger(),
Cfg: viper.New(),
Cfg: config.New(),
})
c.Assert(err, qt.IsNil)
conv, err := p.New(converter.DocumentContext{})

View file

@ -385,8 +385,8 @@ func DecodeTypes(mms ...map[string]interface{}) (Types, error) {
return m, err
}
vm := v.(map[string]interface{})
maps.ToLower(vm)
vm := maps.ToStringMap(v)
maps.PrepareParams(vm)
_, delimiterSet := vm["delimiter"]
_, suffixSet := vm["suffix"]

View file

@ -99,10 +99,10 @@ func decodeConfig(cfg config.Provider) (conf minifyConfig, err error) {
// Handle upstream renames.
if td, found := m["tdewolff"]; found {
tdm := cast.ToStringMap(td)
tdm := maps.ToStringMap(td)
for _, key := range []string{"css", "svg"} {
if v, found := tdm[key]; found {
vm := cast.ToStringMap(v)
vm := maps.ToStringMap(v)
if vv, found := vm["decimal"]; found {
vvi := cast.ToInt(vv)
if vvi > 0 {

View file

@ -16,14 +16,14 @@ package minifiers
import (
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
)
func TestConfig(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("minify", map[string]interface{}{
"disablexml": true,
@ -53,7 +53,7 @@ func TestConfig(t *testing.T) {
func TestConfigLegacy(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
// This was a bool < Hugo v0.58.
v.Set("minify", true)

View file

@ -19,16 +19,15 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/media"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/output"
"github.com/spf13/viper"
)
func TestNew(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
var rawJS string
@ -76,7 +75,7 @@ func TestNew(t *testing.T) {
func TestConfigureMinify(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("minify", map[string]interface{}{
"disablexml": true,
"tdewolff": map[string]interface{}{
@ -110,7 +109,7 @@ func TestConfigureMinify(t *testing.T) {
func TestJSONRoundTrip(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []string{`{
@ -148,7 +147,7 @@ func TestJSONRoundTrip(t *testing.T) {
func TestBugs(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []struct {
@ -171,7 +170,7 @@ func TestBugs(t *testing.T) {
// Renamed to Precision in v2.7.0. Check that we support both.
func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("minify", map[string]interface{}{
"disablexml": true,
"tdewolff": map[string]interface{}{

View file

@ -424,7 +424,7 @@ func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
if err != nil {
c.logger.Warnf("Failed to read module config for %q in %q: %s", tc.Path(), themeTOML, err)
} else {
maps.ToLower(themeCfg)
maps.PrepareParams(themeCfg)
}
}

View file

@ -29,7 +29,7 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
)
@ -122,7 +122,7 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error {
var commentsm map[string]interface{}
comments, found := b.originalPackageJSON["comments"]
if found {
commentsm = cast.ToStringMap(comments)
commentsm = maps.ToStringMap(comments)
} else {
commentsm = make(map[string]interface{})
}
@ -205,7 +205,7 @@ func (b *packageBuilder) addm(source string, m map[string]interface{}) {
// These packages will be added by order of import (project, module1, module2...),
// so that should at least give the project control over the situation.
if devDeps, found := m[devDependenciesKey]; found {
mm := cast.ToStringMapString(devDeps)
mm := maps.ToStringMapString(devDeps)
for k, v := range mm {
if _, added := b.devDependencies[k]; !added {
b.devDependencies[k] = v
@ -215,7 +215,7 @@ func (b *packageBuilder) addm(source string, m map[string]interface{}) {
}
if deps, found := m[dependenciesKey]; found {
mm := cast.ToStringMapString(deps)
mm := maps.ToStringMapString(deps)
for k, v := range mm {
if _, added := b.dependencies[k]; !added {
b.dependencies[k] = v

View file

@ -368,7 +368,11 @@ func decode(mediaTypes media.Types, input interface{}, output *Format) error {
return err
}
return decoder.Decode(input)
if err = decoder.Decode(input); err != nil {
return errors.Wrap(err, "failed to decode output format configuration")
}
return nil
}

View file

@ -22,12 +22,13 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
"github.com/gohugoio/hugo/output"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestClassCollector(t *testing.T) {
@ -138,7 +139,7 @@ func TestClassCollector(t *testing.T) {
if skipMinifyTest[test.name] {
c.Skip("skip minify test")
}
v := viper.New()
v := config.New()
m, _ := minifiers.New(media.DefaultTypes, output.DefaultFormats, v)
m.Minify(media.HTMLType, w, strings.NewReader(test.html))

View file

@ -22,6 +22,8 @@ import (
"strings"
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/mitchellh/mapstructure"
)
@ -404,16 +406,11 @@ func norm(num, min, max int) int {
}
// DecodeConfig decodes a slice of map into Config.
func DecodeConfig(in interface{}) (Config, error) {
if in == nil {
func DecodeConfig(m maps.Params) (Config, error) {
if m == nil {
return Config{}, errors.New("no related config provided")
}
m, ok := in.(map[string]interface{})
if !ok {
return Config{}, fmt.Errorf("expected map[string]interface {} got %T", in)
}
if len(m) == 0 {
return Config{}, errors.New("empty related config provided")
}

View file

@ -18,8 +18,9 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
@ -72,7 +73,7 @@ func newTestFd() *FrontMatterDescriptor {
func TestFrontMatterNewConfig(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{"publishDate", "LastMod"},
@ -89,7 +90,7 @@ func TestFrontMatterNewConfig(t *testing.T) {
c.Assert(fc.publishDate, qt.DeepEquals, []string{"date"})
// Default
cfg = viper.New()
cfg = config.New()
fc, err = newFrontmatterConfig(cfg)
c.Assert(err, qt.IsNil)
c.Assert(fc.date, qt.DeepEquals, []string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"})
@ -117,7 +118,7 @@ func TestFrontMatterDatesHandlers(t *testing.T) {
for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
cfg := viper.New()
cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{handlerID, "date"},
@ -157,7 +158,7 @@ func TestFrontMatterDatesCustomConfig(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{"mydate"},
"lastmod": []string{"publishdate"},
@ -204,7 +205,7 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{"mydate", ":default"},

View file

@ -18,7 +18,7 @@ import (
"html/template"
"testing"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/output"
@ -196,7 +196,7 @@ func doTestPagerNoPages(t *testing.T, paginator *Paginator) {
func TestPaginationURLFactory(t *testing.T) {
t.Parallel()
c := qt.New(t)
cfg := viper.New()
cfg := config.New()
cfg.Set("paginatePath", "zoo")
for _, uglyURLs := range []bool{false, true} {

View file

@ -29,7 +29,7 @@ import (
"github.com/bep/gitmap"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/viper"
"github.com/gohugoio/hugo/navigation"
@ -69,7 +69,7 @@ func newTestPageWithFile(filename string) *testPage {
}
func newTestPathSpec() *helpers.PathSpec {
return newTestPathSpecFor(viper.New())
return newTestPathSpecFor(config.New())
}
func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {

View file

@ -130,7 +130,7 @@ func AssignMetadata(metadata []map[string]interface{}, resources ...resource.Res
if found {
m := maps.ToStringMap(params)
// Needed for case insensitive fetching of params values
maps.ToLower(m)
maps.PrepareParams(m)
ma.updateParams(m)
}
}

View file

@ -17,17 +17,17 @@ import (
"path/filepath"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func NewTestResourceSpec() (*resources.Spec, error) {
cfg := viper.New()
cfg := config.New()
cfg.Set("baseURL", "https://example.org")
cfg.Set("publishDir", "public")

View file

@ -20,9 +20,9 @@ import (
"path/filepath"
"strings"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/common/maps"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/evanw/esbuild/pkg/api"
@ -30,7 +30,6 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
)
const (
@ -348,7 +347,7 @@ func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) {
var defines map[string]string
if opts.Defines != nil {
defines = cast.ToStringMapString(opts.Defines)
defines = maps.ToStringMapString(opts.Defines)
}
// By default we only need to specify outDir and no outFile

View file

@ -10,6 +10,7 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules"
@ -22,7 +23,6 @@ import (
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
type specDescriptor struct {
@ -31,8 +31,8 @@ type specDescriptor struct {
fs afero.Fs
}
func createTestCfg() *viper.Viper {
cfg := viper.New()
func createTestCfg() config.Provider {
cfg := config.New()
cfg.Set("resourceDir", "resources")
cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data")

View file

@ -19,6 +19,8 @@ import (
"runtime"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/langs"
@ -28,8 +30,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
)
func TestEmptySourceFilesystem(t *testing.T) {
@ -76,8 +76,8 @@ func TestUnicodeNorm(t *testing.T) {
}
}
func newTestConfig() *viper.Viper {
v := viper.New()
func newTestConfig() config.Provider {
v := config.New()
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")

View file

@ -15,18 +15,18 @@ package cast
import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/docshelper"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/spf13/viper"
)
// This file provides documentation support and is randomly put into this package.
func init() {
docsProvider := func() docshelper.DocProvider {
d := &deps.Deps{
Cfg: viper.New(),
Cfg: config.New(),
Log: loggers.NewErrorLogger(),
BuildStartListeners: &deps.Listeners{},
Site: page.NewDummyHugoSite(newTestConfig()),
@ -46,8 +46,8 @@ func init() {
docshelper.AddDocProviderFunc(docsProvider)
}
func newTestConfig() *viper.Viper {
v := viper.New()
func newTestConfig() config.Provider {
v := config.New()
v.Set("contentDir", "content")
return v
}

View file

@ -32,7 +32,7 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
type tstNoStringer struct{}
@ -986,7 +986,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
}
func newTestNs() *Namespace {
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
return New(newDeps(v))
}

View file

@ -17,10 +17,10 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/spf13/viper"
)
func TestInit(t *testing.T) {
@ -28,7 +28,7 @@ func TestInit(t *testing.T) {
var found bool
var ns *internal.TemplateFuncsNamespace
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
langs.LoadLanguageSettings(v, nil)

View file

@ -34,12 +34,12 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestScpGetLocal(t *testing.T) {
t.Parallel()
v := viper.New()
v := config.New()
fs := hugofs.NewMem(v)
ps := helpers.FilePathSeparator
@ -144,7 +144,7 @@ func TestScpGetRemoteParallel(t *testing.T) {
c.Assert(err, qt.IsNil)
for _, ignoreCache := range []bool{false} {
cfg := viper.New()
cfg := config.New()
cfg.Set("ignoreCache", ignoreCache)
cfg.Set("contentDir", "content")
@ -223,7 +223,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
}
func newTestNs() *Namespace {
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
return New(newDeps(v))
}

View file

@ -20,6 +20,7 @@ import (
"errors"
"html/template"
"github.com/gohugoio/hugo/common/maps"
"github.com/spf13/cast"
)
@ -71,7 +72,7 @@ func (ns *Namespace) Jsonify(args ...interface{}) (template.HTML, error) {
case 2:
var opts map[string]string
opts, err = cast.ToStringMapStringE(args[0])
opts, err = maps.ToStringMapStringE(args[0])
if err != nil {
break
}

View file

@ -16,20 +16,21 @@ package hugo
import (
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting/hqt"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/spf13/viper"
)
func TestInit(t *testing.T) {
c := qt.New(t)
var found bool
var ns *internal.TemplateFuncsNamespace
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
s := page.NewDummyHugoSite(v)

View file

@ -22,11 +22,11 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
type tstNoStringer struct{}
@ -82,7 +82,7 @@ func TestNSConfig(t *testing.T) {
t.Parallel()
c := qt.New(t)
v := viper.New()
v := config.New()
v.Set("workingDir", "/a/b")
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})

View file

@ -17,11 +17,12 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func TestReadFile(t *testing.T) {
@ -30,7 +31,7 @@ func TestReadFile(t *testing.T) {
workingDir := "/home/hugo"
v := viper.New()
v := config.New()
v.Set("workingDir", workingDir)
// f := newTestFuncsterWithViper(v)
@ -68,7 +69,7 @@ func TestFileExists(t *testing.T) {
workingDir := "/home/hugo"
v := viper.New()
v := config.New()
v.Set("workingDir", workingDir)
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
@ -103,7 +104,7 @@ func TestStat(t *testing.T) {
c := qt.New(t)
workingDir := "/home/hugo"
v := viper.New()
v := config.New()
v.Set("workingDir", workingDir)
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})

View file

@ -18,11 +18,11 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/spf13/viper"
)
var ns = New(&deps.Deps{Cfg: viper.New()})
var ns = New(&deps.Deps{Cfg: config.New()})
type tstNoStringer struct{}

View file

@ -282,7 +282,7 @@ func (ns *Namespace) ToCSS(args ...interface{}) (resource.Resource, error) {
}
if m != nil {
maps.ToLower(m)
maps.PrepareParams(m)
if t, found := m["transpiler"]; found {
switch t {
case transpilerDart, transpilerLibSass:

View file

@ -16,12 +16,13 @@ package site
import (
"testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/spf13/viper"
)
func TestInit(t *testing.T) {
@ -29,7 +30,7 @@ func TestInit(t *testing.T) {
var found bool
var ns *internal.TemplateFuncsNamespace
v := viper.New()
v := config.New()
v.Set("contentDir", "content")
s := page.NewDummyHugoSite(v)

View file

@ -16,12 +16,13 @@ package strings
import (
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting/hqt"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/spf13/viper"
)
func TestInit(t *testing.T) {
@ -30,7 +31,7 @@ func TestInit(t *testing.T) {
var ns *internal.TemplateFuncsNamespace
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns = nsf(&deps.Deps{Cfg: viper.New()})
ns = nsf(&deps.Deps{Cfg: config.New()})
if ns.Name == name {
found = true
break

Some files were not shown because too many files have changed in this diff Show more