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
"strings"
"time"
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
+ if _, ok := v.(maps.Params); !ok {
+ continue
+ }
cc := defaultCacheConfig
dc := &mapstructure.DecoderConfig{
}
if err := decoder.Decode(v); err != nil {
- return nil, err
+ return nil, errors.Wrap(err, "failed to decode filecache config")
}
if cc.Dir == "" {
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
- "github.com/spf13/viper"
)
func TestDecodeConfig(t *testing.T) {
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")
}
config.Set("cacheDir", cacheDir)
- cfg.Logger.Infoln("Using config file:", config.ConfigFileUsed())
-
return nil
}
"path/filepath"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cobra"
- "github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
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)
},
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/**")
},
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")
"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)
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.
"path/filepath"
"strings"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/parser/metadecoders"
_errors "github.com/pkg/errors"
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
var _ cmder = (*newSiteCmd)(nil)
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) {
"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) {
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
"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
+// ToStringMapE converts in to map[string]interface{}.
+func ToStringMapE(in interface{}) (map[string]interface{}, error) {
+ switch vv := in.(type) {
+ case Params:
+ return vv, nil
+ case map[string]string:
+ var m = map[string]interface{}{}
+ for k, v := range vv {
+ m[k] = v
}
+ return m, nil
- lKey := strings.ToLower(k)
- if retyped || k != lKey {
- delete(m, k)
- m[lKey] = v
- }
+ default:
+ return cast.ToStringMapE(in)
}
}
-func ToStringMapE(in interface{}) (map[string]interface{}, error) {
- switch in.(type) {
- case Params:
- return in.(Params), 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{}:
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{}) {
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)
}
package maps
import (
+ "fmt"
"strings"
"github.com/spf13/cast"
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
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
+ }
+ }
+}
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",
+ })
+
+}
"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",
})
--- /dev/null
+// 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")
+}
--- /dev/null
+// 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)
+ })
+}
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
- "github.com/spf13/viper"
)
var (
// 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.
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
func RenameKeys(m map[string]interface{}) {
keyAliases.Rename(m)
}
-
-func newViper() *viper.Viper {
- v := viper.New()
-
- return v
-}
package config
import (
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
)
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
}
"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"}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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)
+ }
+ })
+}
--- /dev/null
+// 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)
+}
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/viper"
)
func TestDecodeConfigFromTOML(t *testing.T) {
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)
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/viper"
+
)
func TestDecodeConfigFromTOML(t *testing.T) {
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")
"strings"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
- "github.com/spf13/viper"
)
func TestNewContent(t *testing.T) {
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"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/viper"
+
)
func TestDecodeConfigFromTOML(t *testing.T) {
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)
languageCode = "en-us"
title = "Hugo"
- ignoreErrors = ["error-remote-getjson"]
+ ignoreErrors = ["error-remote-getjson", "err-missing-instagram-accesstoken"]
googleAnalytics = "UA-7131036-4"
{{% 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
"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,
{{ $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>
- {{ 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>
+
+ {{ 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>
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
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=
"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"
)
}
func TestNewContentSpec(t *testing.T) {
- cfg := viper.New()
+ cfg := config.New()
c := qt.New(t)
cfg.Set("summaryLength", 32)
"strings"
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers"
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)
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
- "github.com/spf13/viper"
)
func TestMakePath(t *testing.T) {
}
func TestAbsPathify(t *testing.T) {
- defer viper.Reset()
-
type test struct {
inPath, workingDir, expected string
}
}
for i, d := range data {
- viper.Reset()
// todo see comment in AbsPathify
ps := newTestDefaultPathSpec("workingDir", d.workingDir)
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])
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")
}
func newTestContentSpec() *ContentSpec {
- v := viper.New()
+ v := config.New()
spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs())
if err != nil {
panic(err)
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))
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))
func TestWorkingDir(t *testing.T) {
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("workingDir", "/a/b/")
"sort"
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting"
func TestLanguageRootMapping(t *testing.T) {
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
fs := NewBaseFileDecorator(afero.NewMemMapFs())
"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) {
-func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
- privacyConfig, err := privacy.DecodeConfig(cfg)
+ 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
+ return l.cfg, configFiles, err
}
- servicesConfig, err := services.DecodeConfig(cfg)
+ // 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
+ return l.cfg, configFiles, err
}
- scfg.Privacy = privacyConfig
- scfg.Services = servicesConfig
+ configFiles = append(configFiles, modulesConfigFiles...)
- return
+ 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
+}
+
+// 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.).
Environ []string
}
+func (d ConfigSourceDescriptor) configFileDir() string {
+ if d.Path != "" {
+ return d.Path
+ }
+ return d.WorkingDir
+}
+
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
- }
- return d.WorkingDir
+// 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
+
+ // Services contains config for services such as Google Analytics etc.
+ Services services.Config
}
-// 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
+type configLoader struct {
+ cfg config.Provider
+ ConfigSourceDescriptor
}
-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")
+// Handle some legacy values.
+func (l configLoader) applyConfigAliases() error {
+ aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
-// 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
+ for _, alias := range aliases {
+ if l.cfg.IsSet(alias.Key) {
+ vv := l.cfg.Get(alias.Key)
+ l.cfg.Set(alias.Value, vv)
+ }
}
- if len(d.Environ) == 0 {
- d.Environ = os.Environ()
- }
+ return nil
+}
- var configFiles []string
+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)
- v := viper.New()
- l := configLoader{ConfigSourceDescriptor: d}
+ return nil
+}
- 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
- }
+func (l configLoader) applyOsEnvOverrides(environ []string) error {
+ if len(environ) == 0 {
+ return nil
}
- if d.AbsConfigDir != "" {
- dirnames, err := l.loadConfigFromConfigDir(v)
- if err == nil {
- configFiles = append(configFiles, dirnames...)
- } else if err != ErrNoConfigFile {
- return nil, nil, err
- }
- }
+ const delim = "__env__delim"
- if err := loadDefaultSettingsFor(v); err != nil {
- return v, configFiles, err
- }
+ // 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,
+ })
- // 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, l.cfg.Get)
+ if err != nil {
+ return err
}
- for _, env := range hugoEnv {
- existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, v.Get)
+ if existing != nil {
+ val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
if err != nil {
- return err
+ continue
}
- 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
+ if owner != nil {
+ owner[nestedKey] = val
} else {
- v.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
+ 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
+ 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")
}
- if err := applyOsEnvOverrides(); err != nil {
- return v, configFiles, err
- }
+ themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
- // 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", "**")
+ var ignoreVendor glob.Glob
+ if s := v1.GetString("ignoreVendorPaths"); s != "" {
+ ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
- modulesConfig, err := l.loadModulesConfig(v)
+ filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
if err != nil {
- return v, configFiles, err
+ return nil, nil, 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
- }
+ v1.Set("filecacheConfigs", filecacheConfigs)
- mods := m.ActiveModules
+ var configFilenames []string
- // Apply default project mounts.
- if err := modules.ApplyProjectConfigDefaults(v, mods[0]); err != nil {
- return err
+ 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
}
- _, modulesConfigFiles, err := l.collectModules(modulesConfig, v, collectHook)
+ modulesClient := modules.NewClient(modules.ClientConfig{
+ Fs: l.Fs,
+ Logger: l.Logger,
+ HookBeforeFinalize: hook,
+ WorkingDir: workingDir,
+ ThemesDir: themesDir,
+ CacheDir: filecacheConfigs.CacheDirModules(),
+ ModuleConfig: modConfig,
+ IgnoreVendor: ignoreVendor,
+ })
- if err == nil && len(modulesConfigFiles) > 0 {
- configFiles = append(configFiles, modulesConfigFiles...)
- }
+ v1.Set("modulesClient", modulesClient)
- if err := applyOsEnvOverrides(); err != nil {
- return v, configFiles, err
- }
+ moduleConfig, err := modulesClient.Collect()
- return v, configFiles, err
-}
+ // Avoid recreating these later.
+ v1.Set("allModules", moduleConfig.ActiveModules)
-func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
- _, err := langs.LoadLanguageSettings(cfg, oldLangs)
- return err
-}
+ if moduleConfig.GoModulesFilename != "" {
+ // We want to watch this for changes and trigger rebuild on version
+ // changes etc.
+ configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
+ }
-type configLoader struct {
- ConfigSourceDescriptor
+ return moduleConfig.ActiveModules, configFilenames, err
}
-func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, error) {
+func (l configLoader) loadConfig(configName string) (string, error) {
baseDir := l.configFileDir()
var baseFilename string
if filepath.IsAbs(configName) {
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
// 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
})
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
}
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
}
"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) {
c := qt.New(t)
- mainConfigBasic := `
-theme = "test-theme"
-baseURL = "https://example.com/"
-
-`
- mainConfig := `
+ mainConfigTemplate := `
theme = "test-theme"
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"]
[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"]
`
- 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"])
+ 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{})
+ }
- c.Assert(got["baseurl"], qt.Equals, "https://example.com/")
+ c.Run("Merge default", func(c *qt.C) {
+ b := buildForStrategy(c, "")
+
+ got := b.Cfg.Get("").(maps.Params)
+
+ 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",
+ })
+ })
- if true {
- return
- }
- // 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()
-
- 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"])
-
- 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.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) {
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"
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"
"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)
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)
+
}
"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 {
func TestNewBaseFs(t *testing.T) {
c := qt.New(t)
- v := viper.New()
+ v := config.New()
fs := hugofs.NewMem(v)
}
}
-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")
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)
"testing"
"time"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/modules/npm"
"github.com/gohugoio/hugo/common/loggers"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/testmodBuilder/mods"
- "github.com/spf13/viper"
)
func TestHugoModulesVariants(t *testing.T) {
t.Skip("skip (relative) long running modules test when running locally")
}
- config := `
+ tomlConfig := `
baseURL="https://example.org"
workingDir = %q
`
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()) {
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", `
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)
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")
`
- 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 }}
%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,
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)
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)
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")
}
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
}
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")
}
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 {
s.h = h
}
- if err := applyDeps(depsCfg, sites...); err != nil {
+ var cl configLoader
+ if err := cl.applyDeps(depsCfg, sites...); err != nil {
return err
}
"path/filepath"
"strings"
"testing"
- "time"
- "github.com/fortytw2/leaktest"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
// 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)
"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,
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)
"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"
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())
c.Assert(err, qt.IsNil)
defer clean()
- config := fmt.Sprintf(`
+ tomlConfig := fmt.Sprintf(`
baseURL = "https://example.org"
workingDir = %q
`, 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
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/")
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 {
"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"
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{}
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)
"strings"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/deps"
- "github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
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")
}
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")
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)
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"
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)
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 {
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{}{
"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"
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)
"math/rand"
"os"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"path/filepath"
"github.com/gohugoio/hugo/htesting"
- "github.com/spf13/viper"
-
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
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.
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.
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())
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())
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)
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)
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,
})
import (
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
)
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)
"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"
func TestShortcodeEmoji(t *testing.T) {
t.Parallel()
- v := viper.New()
+ v := config.New()
v.Set("enableEmoji", true)
builder := newTestSitesBuilder(t).WithViper(v)
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,
"github.com/spf13/afero"
"github.com/spf13/cast"
- "github.com/spf13/viper"
)
// Site contains all the information relevant for constructing a static
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
return nil, err
}
- if err = applyDeps(cfg, s); err != nil {
+ var l configLoader
+ if err = l.applyDeps(cfg, s); err != nil {
return nil, err
}
// 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.
// 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.
return vvv
}
default:
- m := cast.ToStringMapBool(v)
+ m := maps.ToStringMapBool(v)
uglyURLs = func(p page.Page) bool {
return m[p.Section()]
}
"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) {
page.KindSection: []string{"JSON"},
}
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
// 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",
page.KindHome: []string{"FOO", "JSON"},
}
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("outputs", outputsConfig)
_, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
page.KindHome: []string{},
}
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("outputs", outputsConfig)
outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
page.KindHome: []string{},
}
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("outputs", outputsConfig)
var (
"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"
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"},
"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
)
"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"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/viper"
"github.com/gohugoio/hugo/resources/resource"
// Default toml
configFormat string
configFileSet bool
- viperSet bool
+ configSet bool
// Default is empty.
// TODO(bep) revisit this and consider always setting it to something.
}
func newTestSitesBuilder(t testing.TB) *sitesBuilder {
- v := viper.New()
+ v := config.New()
fs := hugofs.NewMem(v)
litterOptions := litter.Options{
b.WithWorkingDir(workingDir)
- return b.WithViper(d.Cfg.(*viper.Viper))
+ return b.WithViper(d.Cfg.(config.Provider))
}
func (s *sitesBuilder) Running() *sitesBuilder {
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)
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)
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 {
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 {
}
if strings.EqualFold(k, disabled) {
- v.(map[string]interface{})["disabled"] = true
+ v.(maps.Params)["disabled"] = true
break
}
}
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)
}
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/resources/page"
"github.com/spf13/afero"
- "github.com/spf13/viper"
"github.com/gohugoio/hugo/deps"
}
}
-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")
"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
// 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 {
for k, v := range cfg.GetStringMap("params") {
params[k] = v
}
- maps.ToLower(params)
+ maps.PrepareParams(params)
+
+ localCfg := config.New()
+ compositeConfig := config.NewCompositeConfig(cfg, localCfg)
- l := &Language{Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, params: params, settings: make(map[string]interface{})}
+ l := &Language{Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, LocalCfg: localCfg, Provider: compositeConfig, params: params}
return l
}
l.paramsMu.Lock()
defer l.paramsMu.Unlock()
if !l.paramsSet {
- maps.ToLower(l.params)
+ maps.PrepareParams(l.params)
l.paramsSet = true
}
return l.params
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.
}
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)
}
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")
func TestLanguageParams(t *testing.T) {
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("p1", "p1cfg")
v.Set("contentDir", "content")
"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(
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
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"}
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(
func TestAsciidoctorDisallowedExtension(t *testing.T) {
c := qt.New(t)
- cfg := viper.New()
+ cfg := config.New()
for _, disallowedExtension := range []string{
`foo-bar//`,
`foo-bar\\ `,
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
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"}
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
import (
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/converter"
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{})
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{
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)
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/gohugoio/hugo/parser"
"github.com/mitchellh/mapstructure"
- "github.com/spf13/cast"
)
type Config struct {
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 {
import (
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
)
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{}{
c.Run("legacy", func(c *qt.C) {
c.Parallel()
- v := viper.New()
+ v := config.New()
v.Set("blackfriday", map[string]interface{}{
"angledQuotes": true,
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)
import (
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/loggers"
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)
import (
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/common/loggers"
- "github.com/spf13/viper"
"github.com/gohugoio/hugo/markup/converter"
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{})
return m, err
}
- vm := v.(map[string]interface{})
- maps.ToLower(vm)
+ vm := maps.ToStringMap(v)
+ maps.PrepareParams(vm)
_, delimiterSet := vm["delimiter"]
_, suffixSet := vm["suffix"]
// 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 {
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,
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)
"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
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{}{
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{`{
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 {
// 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{}{
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)
}
}
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
- "github.com/spf13/cast"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
)
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{})
}
// 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
}
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
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
}
"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) {
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))
"strings"
"time"
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/gohugoio/hugo/common/types"
"github.com/mitchellh/mapstructure"
)
}
// 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")
}
"testing"
"time"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/resources/resource"
- "github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
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"},
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"})
for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{handlerID, "date"},
c := qt.New(t)
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{"mydate"},
"lastmod": []string{"publishdate"},
c := qt.New(t)
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("frontmatter", map[string]interface{}{
"date": []string{"mydate", ":default"},
"html/template"
"testing"
- "github.com/spf13/viper"
+ "github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/output"
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} {
"github.com/bep/gitmap"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/resource"
- "github.com/spf13/viper"
+
"github.com/gohugoio/hugo/navigation"
}
func newTestPathSpec() *helpers.PathSpec {
- return newTestPathSpecFor(viper.New())
+ return newTestPathSpecFor(config.New())
}
func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {
if found {
m := maps.ToStringMap(params)
// Needed for case insensitive fetching of params values
- maps.ToLower(m)
+ maps.PrepareParams(m)
ma.updateParams(m)
}
}
"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")
"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"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
- "github.com/spf13/cast"
)
const (
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
"strings"
"testing"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
- "github.com/spf13/viper"
)
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")
"runtime"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/langs"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
-
- "github.com/spf13/viper"
)
func TestEmptySourceFilesystem(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")
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()),
docshelper.AddDocProviderFunc(docsProvider)
}
-func newTestConfig() *viper.Viper {
- v := viper.New()
+func newTestConfig() config.Provider {
+ v := config.New()
v.Set("contentDir", "content")
return v
}
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
"github.com/spf13/afero"
- "github.com/spf13/viper"
+
)
type tstNoStringer struct{}
}
func newTestNs() *Namespace {
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
return New(newDeps(v))
}
"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) {
var found bool
var ns *internal.TemplateFuncsNamespace
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
langs.LoadLanguageSettings(v, nil)
"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
c.Assert(err, qt.IsNil)
for _, ignoreCache := range []bool{false} {
- cfg := viper.New()
+ cfg := config.New()
cfg.Set("ignoreCache", ignoreCache)
cfg.Set("contentDir", "content")
}
func newTestNs() *Namespace {
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
return New(newDeps(v))
}
"errors"
"html/template"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/spf13/cast"
)
case 2:
var opts map[string]string
- opts, err = cast.ToStringMapStringE(args[0])
+ opts, err = maps.ToStringMapStringE(args[0])
if err != nil {
break
}
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)
"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{}
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)})
"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) {
workingDir := "/home/hugo"
- v := viper.New()
+ v := config.New()
v.Set("workingDir", workingDir)
// f := newTestFuncsterWithViper(v)
workingDir := "/home/hugo"
- v := viper.New()
+ v := config.New()
v.Set("workingDir", workingDir)
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
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)})
"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{}
}
if m != nil {
- maps.ToLower(m)
+ maps.PrepareParams(m)
if t, found := m["transpiler"]; found {
switch t {
case transpilerDart, transpilerLibSass:
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) {
var found bool
var ns *internal.TemplateFuncsNamespace
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
s := page.NewDummyHugoSite(v)
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) {
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
"html/template"
"testing"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
qt "github.com/frankban/quicktest"
"github.com/spf13/cast"
- "github.com/spf13/viper"
)
-var ns = New(&deps.Deps{Cfg: viper.New()})
+var ns = New(&deps.Deps{Cfg: config.New()})
type tstNoStringer struct{}
"github.com/gohugoio/hugo/tpl/internal"
"github.com/gohugoio/hugo/tpl/partials"
"github.com/spf13/afero"
- "github.com/spf13/viper"
+
)
var logger = loggers.NewErrorLogger()
func newTestConfig() config.Provider {
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
v.Set("dataDir", "data")
v.Set("i18nDir", "i18n")
func doBenchmarkPartial(b *testing.B, f func(ns *partials.Namespace) error) {
c := qt.New(b)
- config := newDepsConfig(viper.New())
+ config := newDepsConfig(config.New())
config.WithTemplate = func(templ tpl.TemplateManager) error {
err := templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
if err != nil {
import (
"testing"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
- "github.com/spf13/viper"
)
func TestRemarshal(t *testing.T) {
t.Parallel()
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
c := qt.New(t)
func TestRemarshalComments(t *testing.T) {
t.Parallel()
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
func TestTestRemarshalMapInput(t *testing.T) {
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
- "github.com/spf13/viper"
+
)
type tstNoStringer struct{}
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
ns := New(newDeps(v))
for _, test := range []struct {
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
func TestMarkdownifyBlocksOfText(t *testing.T) {
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
v.Set("contentDir", "content")
ns := New(newDeps(v))
t.Parallel()
c := qt.New(t)
- v := viper.New()
+ v := config.New()
ns := New(newDeps(v))
for _, test := range []struct {
"strings"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/media"
qt "github.com/frankban/quicktest"
- "github.com/spf13/viper"
)
const (
}
func TestUnmarshal(t *testing.T) {
- v := viper.New()
+ v := config.New()
ns := New(newDeps(v))
c := qt.New(t)
}
func BenchmarkUnmarshalString(b *testing.B) {
- v := viper.New()
+ v := config.New()
ns := New(newDeps(v))
const numJsons = 100
}
func BenchmarkUnmarshalResource(b *testing.B) {
- v := viper.New()
+ v := config.New()
ns := New(newDeps(v))
const numJsons = 100
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/tpl/internal"
- "github.com/spf13/viper"
)
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
"net/url"
"testing"
+ "github.com/gohugoio/hugo/config"
+
"github.com/gohugoio/hugo/htesting/hqt"
qt "github.com/frankban/quicktest"
"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{}