Misc config loading fixes
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 9 Jun 2021 08:58:18 +0000 (10:58 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 14 Jun 2021 15:00:32 +0000 (17:00 +0200)
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

107 files changed:
cache/filecache/filecache_config.go
cache/filecache/filecache_config_test.go
commands/commandeer.go
commands/commands_test.go
commands/config.go
commands/new_site.go
commands/server_test.go
common/maps/maps.go
common/maps/maps_test.go
common/maps/params.go
common/maps/params_test.go
config/commonConfig_test.go
config/compositeConfig.go [new file with mode: 0644]
config/compositeConfig_test.go [new file with mode: 0644]
config/configLoader.go
config/configProvider.go
config/configProvider_test.go
config/defaultConfigProvider.go [new file with mode: 0644]
config/defaultConfigProvider_test.go [new file with mode: 0644]
config/docshelper.go [new file with mode: 0644]
config/privacy/privacyConfig_test.go
config/services/servicesConfig_test.go
create/content_test.go
deploy/deployConfig_test.go
docs/config.toml
docs/content/en/getting-started/configuration.md
docs/data/docs.json
docs/layouts/shortcodes/code-toggle.html
go.mod
go.sum
helpers/content_test.go
helpers/general_test.go
helpers/path_test.go
helpers/testhelpers_test.go
hugofs/fs_test.go
hugofs/rootmapping_fs_test.go
hugolib/config.go
hugolib/config_test.go
hugolib/filesystems/basefs_test.go
hugolib/hugo_modules_test.go
hugolib/hugo_sites.go
hugolib/hugo_sites_build_errors_test.go
hugolib/image_test.go
hugolib/js_test.go
hugolib/minify_publisher_test.go
hugolib/page__meta.go
hugolib/page_test.go
hugolib/pagebundler_test.go
hugolib/pages_capture.go
hugolib/paths/paths_test.go
hugolib/resource_chain_babel_test.go
hugolib/resource_chain_test.go
hugolib/robotstxt_test.go
hugolib/shortcode_test.go
hugolib/site.go
hugolib/site_output_test.go
hugolib/site_test.go
hugolib/template_test.go
hugolib/testhelpers_test.go
langs/config.go
langs/i18n/i18n_test.go
langs/language.go
langs/language_test.go
markup/asciidocext/convert_test.go
markup/blackfriday/convert_test.go
markup/highlight/config_test.go
markup/markup_config/config.go
markup/markup_config/config_test.go
markup/markup_test.go
markup/mmark/convert_test.go
markup/org/convert_test.go
media/mediaType.go
minifiers/config.go
minifiers/config_test.go
minifiers/minifiers_test.go
modules/collect.go
modules/npm/package_builder.go
output/outputFormat.go
publisher/htmlElementsCollector_test.go
related/inverted_index.go
resources/page/pagemeta/page_frontmatter_test.go
resources/page/pagination_test.go
resources/page/testhelpers_test.go
resources/resource_metadata.go
resources/resource_transformers/htesting/testhelpers.go
resources/resource_transformers/js/options.go
resources/testhelpers_test.go
source/filesystem_test.go
tpl/cast/docshelper.go
tpl/collections/collections_test.go
tpl/data/init_test.go
tpl/data/resources_test.go
tpl/encoding/encoding.go
tpl/hugo/init_test.go
tpl/images/images_test.go
tpl/os/os_test.go
tpl/path/path_test.go
tpl/resources/resources.go
tpl/site/init_test.go
tpl/strings/init_test.go
tpl/strings/strings_test.go
tpl/tplimpl/template_funcs_test.go
tpl/transform/remarshal_test.go
tpl/transform/transform_test.go
tpl/transform/unmarshal_test.go
tpl/urls/init_test.go
tpl/urls/urls_test.go

index 0c6b569c1e3f2e4f4211e16bd86699bf6aedabb4..801799e36d2f8e72eec96ba23f510c59badfcdcf 100644 (file)
@@ -19,6 +19,8 @@ import (
        "strings"
        "time"
 
+       "github.com/gohugoio/hugo/common/maps"
+
        "github.com/gohugoio/hugo/config"
 
        "github.com/gohugoio/hugo/helpers"
@@ -123,6 +125,9 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
        _, isOsFs := fs.(*afero.OsFs)
 
        for k, v := range m {
+               if _, ok := v.(maps.Params); !ok {
+                       continue
+               }
                cc := defaultCacheConfig
 
                dc := &mapstructure.DecoderConfig{
@@ -137,7 +142,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
                }
 
                if err := decoder.Decode(v); err != nil {
-                       return nil, err
+                       return nil, errors.Wrap(err, "failed to decode filecache config")
                }
 
                if cc.Dir == "" {
index cd1d2c82ac6010e460bcae48df781b625c03398c..acc127e67e89d03d552ccff6ef7568a1bce47152 100644 (file)
@@ -25,7 +25,6 @@ import (
        "github.com/gohugoio/hugo/config"
 
        qt "github.com/frankban/quicktest"
-       "github.com/spf13/viper"
 )
 
 func TestDecodeConfig(t *testing.T) {
@@ -178,8 +177,8 @@ dir = "/"
        c.Assert(err, qt.Not(qt.IsNil))
 }
 
-func newTestConfig() *viper.Viper {
-       cfg := viper.New()
+func newTestConfig() config.Provider {
+       cfg := config.New()
        cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
        cfg.Set("contentDir", "content")
        cfg.Set("dataDir", "data")
index 024651aaddf66c7a895a610eb02366c8c338ef92..06e27cf18c593255d16e9372e5a5899cf500f820 100644 (file)
@@ -410,7 +410,5 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
        }
        config.Set("cacheDir", cacheDir)
 
-       cfg.Logger.Infoln("Using config file:", config.ConfigFileUsed())
-
        return nil
 }
index a0eeb157bd8e9174f076991aa2fd6b827fc724d3..99ffea48cfb936fa3a9b0e258c608789ed0a2c42 100644 (file)
@@ -20,6 +20,8 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/htesting"
 
        "github.com/spf13/afero"
@@ -29,7 +31,6 @@ import (
        "github.com/gohugoio/hugo/common/types"
 
        "github.com/spf13/cobra"
-       "github.com/spf13/viper"
 
        qt "github.com/frankban/quicktest"
 )
@@ -166,7 +167,7 @@ func TestFlags(t *testing.T) {
                        name: "ignoreVendor as bool",
                        args: []string{"server", "--ignoreVendor"},
                        check: func(c *qt.C, cmd *serverCmd) {
-                               cfg := viper.New()
+                               cfg := config.New()
                                cmd.flagsToConfig(cfg)
                                c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true)
                        },
@@ -176,7 +177,7 @@ func TestFlags(t *testing.T) {
                        name: "ignoreVendorPaths",
                        args: []string{"server", "--ignoreVendorPaths=github.com/**"},
                        check: func(c *qt.C, cmd *serverCmd) {
-                               cfg := viper.New()
+                               cfg := config.New()
                                cmd.flagsToConfig(cfg)
                                c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
                        },
@@ -216,7 +217,7 @@ func TestFlags(t *testing.T) {
                                c.Assert(sc.serverPort, qt.Equals, 1366)
                                c.Assert(sc.environment, qt.Equals, "testing")
 
-                               cfg := viper.New()
+                               cfg := config.New()
                                sc.flagsToConfig(cfg)
                                c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
                                c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
index 37bf45e3cd0aa19539378ad730ffd98ee4d2e46f..7ab429308b3c0202ea1ebd568ee70df04d1840e8 100644 (file)
@@ -22,13 +22,14 @@ import (
        "sort"
        "strings"
 
+       "github.com/gohugoio/hugo/common/maps"
+
        "github.com/gohugoio/hugo/parser"
        "github.com/gohugoio/hugo/parser/metadecoders"
 
        "github.com/gohugoio/hugo/modules"
 
        "github.com/spf13/cobra"
-       "github.com/spf13/viper"
 )
 
 var _ cmder = (*configCmd)(nil)
@@ -81,7 +82,7 @@ func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
                return err
        }
 
-       allSettings := cfg.Cfg.(*viper.Viper).AllSettings()
+       allSettings := cfg.Cfg.Get("").(maps.Params)
 
        // We need to clean up this, but we store objects in the config that
        // isn't really interesting to the end user, so filter these.
index 6fac2c22c0f80c2f2ede395bc559267a8778c5a9..71097b8ff0a1f6741c206a171407de25cd569db8 100644 (file)
@@ -19,6 +19,7 @@ import (
        "path/filepath"
        "strings"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/parser/metadecoders"
 
        _errors "github.com/pkg/errors"
@@ -29,7 +30,6 @@ import (
        "github.com/gohugoio/hugo/parser"
        "github.com/spf13/cobra"
        jww "github.com/spf13/jwalterweatherman"
-       "github.com/spf13/viper"
 )
 
 var _ cmder = (*newSiteCmd)(nil)
@@ -123,7 +123,7 @@ func (n *newSiteCmd) newSite(cmd *cobra.Command, args []string) error {
 
        forceNew, _ := cmd.Flags().GetBool("force")
 
-       return n.doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew)
+       return n.doNewSite(hugofs.NewDefault(config.New()), createpath, forceNew)
 }
 
 func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
index cc2a6d92ca30edff20a26e2eaf383fbb71b2ed4d..05d21a5165b37d192279dcb526a7922071ec2e5c 100644 (file)
@@ -22,10 +22,10 @@ import (
        "testing"
        "time"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/helpers"
 
        qt "github.com/frankban/quicktest"
-       "github.com/spf13/viper"
 )
 
 func TestServer(t *testing.T) {
@@ -101,7 +101,7 @@ func TestFixURL(t *testing.T) {
                t.Run(test.TestName, func(t *testing.T) {
                        b := newCommandsBuilder()
                        s := b.newServerCmd()
-                       v := viper.New()
+                       v := config.New()
                        baseURL := test.CLIBaseURL
                        v.Set("baseURL", test.CfgBaseURL)
                        s.serverAppend = test.AppendPort
index 41d9b6e15b88449c52785c1e0b8a8777721fda8c..5fb0790092f7bef5b141fb4e5881c996e67307c5 100644 (file)
@@ -18,53 +18,65 @@ import (
        "strings"
 
        "github.com/gobwas/glob"
-
        "github.com/spf13/cast"
 )
 
-// ToLower makes all the keys in the given map lower cased and will do so
-// recursively.
-// Notes:
-// * This will modify the map given.
-// * Any nested map[interface{}]interface{} will be converted to Params.
-func ToLower(m Params) {
-       for k, v := range m {
-               var retyped bool
-               switch v.(type) {
-               case map[interface{}]interface{}:
-                       var p Params = cast.ToStringMap(v)
-                       v = p
-                       ToLower(p)
-                       retyped = true
-               case map[string]interface{}:
-                       var p Params = v.(map[string]interface{})
-                       v = p
-                       ToLower(p)
-                       retyped = true
+// 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{}:
@@ -127,9 +139,8 @@ func (KeyRenamer) keyPath(k1, k2 string) string {
        k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
        if k1 == "" {
                return k2
-       } else {
-               return k1 + "/" + k2
        }
+       return k1 + "/" + k2
 }
 
 func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {
index 7e527aac5b199e82534d1828eefd0a6af6811220..dbe97a15aef54de72ed5e3928add34d5a2645eff 100644 (file)
@@ -67,7 +67,7 @@ func TestToLower(t *testing.T) {
        for i, test := range tests {
                t.Run(fmt.Sprint(i), func(t *testing.T) {
                        // ToLower modifies input.
-                       ToLower(test.input)
+                       PrepareParams(test.input)
                        if !reflect.DeepEqual(test.expected, test.input) {
                                t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
                        }
index 4c881093c38a0794df81145d0d6367a6f371004b..7e94d593b596c77f998034b74199752eec539135 100644 (file)
@@ -14,6 +14,7 @@
 package maps
 
 import (
+       "fmt"
        "strings"
 
        "github.com/spf13/cast"
@@ -29,6 +30,95 @@ func (p Params) Get(indices ...string) interface{} {
        return v
 }
 
+// Set overwrites values in p with values in pp for common or new keys.
+// This is done recursively.
+func (p Params) Set(pp Params) {
+       for k, v := range pp {
+               vv, found := p[k]
+               if !found {
+                       p[k] = v
+               } else {
+                       switch vvv := vv.(type) {
+                       case Params:
+                               if pv, ok := v.(Params); ok {
+                                       vvv.Set(pv)
+                               } else {
+                                       p[k] = v
+                               }
+                       default:
+                               p[k] = v
+                       }
+               }
+       }
+}
+
+// Merge transfers values from pp to p for new keys.
+// This is done recursively.
+func (p Params) Merge(pp Params) {
+       p.merge("", pp)
+}
+
+func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
+       ns, found := p.GetMergeStrategy()
+
+       var ms = ns
+       if !found && ps != "" {
+               ms = ps
+       }
+
+       noUpdate := ms == ParamsMergeStrategyNone
+       noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
+
+       for k, v := range pp {
+
+               if k == mergeStrategyKey {
+                       continue
+               }
+               vv, found := p[k]
+
+               if found {
+                       // Key matches, if both sides are Params, we try to merge.
+                       if vvv, ok := vv.(Params); ok {
+                               if pv, ok := v.(Params); ok {
+                                       vvv.merge(ms, pv)
+                               }
+
+                       }
+
+               } else if !noUpdate {
+                       p[k] = v
+
+               }
+
+       }
+}
+
+func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
+       if v, found := p[mergeStrategyKey]; found {
+               if s, ok := v.(ParamsMergeStrategy); ok {
+                       return s, true
+               }
+       }
+       return ParamsMergeStrategyShallow, false
+}
+
+func (p Params) DeleteMergeStrategy() bool {
+       if _, found := p[mergeStrategyKey]; found {
+               delete(p, mergeStrategyKey)
+               return true
+       }
+       return false
+}
+
+func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
+       switch s {
+       case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+       default:
+               panic(fmt.Sprintf("invalid merge strategy %q", s))
+       }
+       p[mergeStrategyKey] = s
+}
+
 func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) {
        if len(indices) == 0 {
                return nil, "", nil
@@ -108,3 +198,61 @@ func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interf
 
        return nil, "", nil, nil
 }
+
+// ParamsMergeStrategy tells what strategy to use in Params.Merge.
+type ParamsMergeStrategy string
+
+const (
+       // Do not merge.
+       ParamsMergeStrategyNone ParamsMergeStrategy = "none"
+       // Only add new keys.
+       ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
+       // Add new keys, merge existing.
+       ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
+
+       mergeStrategyKey = "_merge"
+)
+
+func toMergeStrategy(v interface{}) ParamsMergeStrategy {
+       s := ParamsMergeStrategy(cast.ToString(v))
+       switch s {
+       case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+               return s
+       default:
+               return ParamsMergeStrategyDeep
+       }
+}
+
+// PrepareParams
+// * makes all the keys in the given map lower cased and will do so
+// * This will modify the map given.
+// * Any nested map[interface{}]interface{} will be converted to Params.
+// * Any _merge value will be converted to proper type and value.
+func PrepareParams(m Params) {
+       for k, v := range m {
+               var retyped bool
+               lKey := strings.ToLower(k)
+               if lKey == mergeStrategyKey {
+                       v = toMergeStrategy(v)
+                       retyped = true
+               } else {
+                       switch v.(type) {
+                       case map[interface{}]interface{}:
+                               var p Params = cast.ToStringMap(v)
+                               v = p
+                               PrepareParams(p)
+                               retyped = true
+                       case map[string]interface{}:
+                               var p Params = v.(map[string]interface{})
+                               v = p
+                               PrepareParams(p)
+                               retyped = true
+                       }
+               }
+
+               if retyped || k != lKey {
+                       delete(m, k)
+                       m[lKey] = v
+               }
+       }
+}
index df8cbf8d69ea9b5fd1408a87e6efe7373d775b30..8859bb86bc6f767eeeaa3fc495c4e5b8931fd25e 100644 (file)
@@ -69,3 +69,90 @@ func TestGetNestedParamFnNestedNewKey(t *testing.T) {
        c.Assert(nestedKey, qt.Equals, "new")
        c.Assert(owner, qt.DeepEquals, nested)
 }
+
+func TestParamsSetAndMerge(t *testing.T) {
+       c := qt.New(t)
+
+       createParamsPair := func() (Params, Params) {
+               p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
+               p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, mergeStrategyKey: ParamsMergeStrategyDeep}
+               return p1, p2
+       }
+
+       p1, p2 := createParamsPair()
+
+       p1.Set(p2)
+
+       c.Assert(p1, qt.DeepEquals, Params{
+               "a": "abv",
+               "c": "cv",
+               "nested": Params{
+                       "al2": "al2bv",
+                       "cl2": "cl2v",
+                       "bl2": "bl2v",
+               },
+               "b":              "bv",
+               mergeStrategyKey: ParamsMergeStrategyDeep,
+       })
+
+       p1, p2 = createParamsPair()
+
+       p1.Merge(p2)
+
+       // Default is to do a shallow merge.
+       c.Assert(p1, qt.DeepEquals, Params{
+               "c": "cv",
+               "nested": Params{
+                       "al2": "al2v",
+                       "cl2": "cl2v",
+               },
+               "b": "bv",
+               "a": "av",
+       })
+
+       p1, p2 = createParamsPair()
+       p1.SetDefaultMergeStrategy(ParamsMergeStrategyNone)
+       p1.Merge(p2)
+       p1.DeleteMergeStrategy()
+
+       c.Assert(p1, qt.DeepEquals, Params{
+               "a": "av",
+               "c": "cv",
+               "nested": Params{
+                       "al2": "al2v",
+                       "cl2": "cl2v",
+               },
+       })
+
+       p1, p2 = createParamsPair()
+       p1.SetDefaultMergeStrategy(ParamsMergeStrategyShallow)
+       p1.Merge(p2)
+       p1.DeleteMergeStrategy()
+
+       c.Assert(p1, qt.DeepEquals, Params{
+               "a": "av",
+               "c": "cv",
+               "nested": Params{
+                       "al2": "al2v",
+                       "cl2": "cl2v",
+               },
+               "b": "bv",
+       })
+
+       p1, p2 = createParamsPair()
+       p1.SetDefaultMergeStrategy(ParamsMergeStrategyDeep)
+       p1.Merge(p2)
+       p1.DeleteMergeStrategy()
+
+       c.Assert(p1, qt.DeepEquals, Params{
+               "nested": Params{
+                       "al2": "al2v",
+                       "cl2": "cl2v",
+                       "bl2": "bl2v",
+               },
+               "b": "bv",
+               "a": "av",
+               "c": "cv",
+       })
+
+}
index d4273277a29e21ff857c226af6ea1e4900200a7f..55767913f35c4b6099698e0a51bb477aa052440e 100644 (file)
@@ -21,14 +21,12 @@ import (
        "github.com/gohugoio/hugo/common/types"
 
        qt "github.com/frankban/quicktest"
-
-       "github.com/spf13/viper"
 )
 
 func TestBuild(t *testing.T) {
        c := qt.New(t)
 
-       v := viper.New()
+       v := New()
        v.Set("build", map[string]interface{}{
                "useResourceCacheWhen": "always",
        })
diff --git a/config/compositeConfig.go b/config/compositeConfig.go
new file mode 100644 (file)
index 0000000..c684195
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+       "github.com/gohugoio/hugo/common/maps"
+)
+
+// NewCompositeConfig creates a new composite Provider with a read-only base
+// and a writeable layer.
+func NewCompositeConfig(base, layer Provider) Provider {
+       return &compositeConfig{
+               base:  base,
+               layer: layer,
+       }
+}
+
+// compositeConfig contains a read only config base with
+// a possibly writeable config layer on top.
+type compositeConfig struct {
+       base  Provider
+       layer Provider
+}
+
+func (c *compositeConfig) GetBool(key string) bool {
+       if c.layer.IsSet(key) {
+               return c.layer.GetBool(key)
+       }
+       return c.base.GetBool(key)
+}
+
+func (c *compositeConfig) GetInt(key string) int {
+       if c.layer.IsSet(key) {
+               return c.layer.GetInt(key)
+       }
+       return c.base.GetInt(key)
+}
+
+func (c *compositeConfig) Merge(key string, value interface{}) {
+       c.layer.Merge(key, value)
+}
+
+func (c *compositeConfig) GetParams(key string) maps.Params {
+       if c.layer.IsSet(key) {
+               return c.layer.GetParams(key)
+       }
+       return c.base.GetParams(key)
+}
+
+func (c *compositeConfig) GetStringMap(key string) map[string]interface{} {
+       if c.layer.IsSet(key) {
+               return c.layer.GetStringMap(key)
+       }
+       return c.base.GetStringMap(key)
+}
+
+func (c *compositeConfig) GetStringMapString(key string) map[string]string {
+       if c.layer.IsSet(key) {
+               return c.layer.GetStringMapString(key)
+       }
+       return c.base.GetStringMapString(key)
+}
+
+func (c *compositeConfig) GetStringSlice(key string) []string {
+       if c.layer.IsSet(key) {
+               return c.layer.GetStringSlice(key)
+       }
+       return c.base.GetStringSlice(key)
+}
+
+func (c *compositeConfig) Get(key string) interface{} {
+       if c.layer.IsSet(key) {
+               return c.layer.Get(key)
+       }
+       return c.base.Get(key)
+}
+
+func (c *compositeConfig) IsSet(key string) bool {
+       if c.layer.IsSet(key) {
+               return true
+       }
+       return c.base.IsSet(key)
+}
+
+func (c *compositeConfig) GetString(key string) string {
+       if c.layer.IsSet(key) {
+               return c.layer.GetString(key)
+       }
+       return c.base.GetString(key)
+}
+
+func (c *compositeConfig) Set(key string, value interface{}) {
+       c.layer.Set(key, value)
+}
+
+func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
+       panic("not supported")
+}
+
+func (c *compositeConfig) SetDefaultMergeStrategy() {
+       panic("not supported")
+}
diff --git a/config/compositeConfig_test.go b/config/compositeConfig_test.go
new file mode 100644 (file)
index 0000000..6064410
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+       "testing"
+
+       qt "github.com/frankban/quicktest"
+)
+
+func TestCompositeConfig(t *testing.T) {
+       c := qt.New(t)
+
+       c.Run("Set and get", func(c *qt.C) {
+               base, layer := New(), New()
+               cfg := NewCompositeConfig(base, layer)
+
+               layer.Set("a1", "av")
+               base.Set("b1", "bv")
+               cfg.Set("c1", "cv")
+
+               c.Assert(cfg.Get("a1"), qt.Equals, "av")
+               c.Assert(cfg.Get("b1"), qt.Equals, "bv")
+               c.Assert(cfg.Get("c1"), qt.Equals, "cv")
+               c.Assert(cfg.IsSet("c1"), qt.IsTrue)
+               c.Assert(layer.IsSet("c1"), qt.IsTrue)
+               c.Assert(base.IsSet("c1"), qt.IsFalse)
+       })
+}
index 6d94f0b7947b665cfb2bfbde52c846bd2c8f0ac5..0998b1befab133665994e2194c914b43faddc432 100644 (file)
@@ -20,7 +20,6 @@ import (
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/parser/metadecoders"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 var (
@@ -43,15 +42,11 @@ func IsValidConfigFilename(filename string) bool {
 
 // FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
 func FromConfigString(config, configType string) (Provider, error) {
-       v := newViper()
        m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
        if err != nil {
                return nil, err
        }
-
-       v.MergeConfigMap(m)
-
-       return v, nil
+       return NewFrom(m), nil
 }
 
 // FromFile loads the configuration from the given filename.
@@ -60,15 +55,7 @@ func FromFile(fs afero.Fs, filename string) (Provider, error) {
        if err != nil {
                return nil, err
        }
-
-       v := newViper()
-
-       err = v.MergeConfigMap(m)
-       if err != nil {
-               return nil, err
-       }
-
-       return v, nil
+       return NewFrom(m), nil
 }
 
 // FromFileToMap is the same as FromFile, but it returns the config values
@@ -116,9 +103,3 @@ func init() {
 func RenameKeys(m map[string]interface{}) {
        keyAliases.Rename(m)
 }
-
-func newViper() *viper.Viper {
-       v := viper.New()
-
-       return v
-}
index 928bf948a2d03976d04e9c1ffed4ac4fa20ff6c5..92206ca9e3e3df01dc185f5fe6b304760354250f 100644 (file)
@@ -14,6 +14,7 @@
 package config
 
 import (
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/common/types"
 )
 
@@ -22,11 +23,15 @@ type Provider interface {
        GetString(key string) string
        GetInt(key string) int
        GetBool(key string) bool
+       GetParams(key string) maps.Params
        GetStringMap(key string) map[string]interface{}
        GetStringMapString(key string) map[string]string
        GetStringSlice(key string) []string
        Get(key string) interface{}
        Set(key string, value interface{})
+       Merge(key string, value interface{})
+       SetDefaultMergeStrategy()
+       WalkParams(walkFn func(params ...KeyParams) bool)
        IsSet(key string) bool
 }
 
index d9fff56b6f7a080d6caaf83bd4bf247f00b07905..0afba1e58a66daa4cb94bbe319e95eb1e3d76c3d 100644 (file)
@@ -17,12 +17,11 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
-       "github.com/spf13/viper"
 )
 
 func TestGetStringSlicePreserveString(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := New()
 
        s := "This is a string"
        sSlice := []string{"This", "is", "a", "slice"}
diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go
new file mode 100644 (file)
index 0000000..d9c9db7
--- /dev/null
@@ -0,0 +1,372 @@
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+       "fmt"
+       "sort"
+       "strings"
+       "sync"
+
+       "github.com/spf13/cast"
+
+       "github.com/gohugoio/hugo/common/maps"
+)
+
+var (
+
+       // ConfigRootKeysSet contains all of the config map root keys.
+       // TODO(bep) use this for something (docs etc.)
+       ConfigRootKeysSet = map[string]bool{
+               "build":         true,
+               "caches":        true,
+               "frontmatter":   true,
+               "languages":     true,
+               "imaging":       true,
+               "markup":        true,
+               "mediatypes":    true,
+               "menus":         true,
+               "minify":        true,
+               "module":        true,
+               "outputformats": true,
+               "params":        true,
+               "permalinks":    true,
+               "related":       true,
+               "sitemap":       true,
+               "taxonomies":    true,
+       }
+
+       // ConfigRootKeys is a sorted version of ConfigRootKeysSet.
+       ConfigRootKeys []string
+)
+
+func init() {
+       for k := range ConfigRootKeysSet {
+               ConfigRootKeys = append(ConfigRootKeys, k)
+       }
+       sort.Strings(ConfigRootKeys)
+}
+
+// New creates a Provider backed by an empty maps.Params.
+func New() Provider {
+       return &defaultConfigProvider{
+               root: make(maps.Params),
+       }
+}
+
+// NewFrom creates a Provider backed by params.
+func NewFrom(params maps.Params) Provider {
+       maps.PrepareParams(params)
+       return &defaultConfigProvider{
+               root: params,
+       }
+}
+
+// defaultConfigProvider is a Provider backed by a map where all keys are lower case.
+// All methods are thread safe.
+type defaultConfigProvider struct {
+       mu   sync.RWMutex
+       root maps.Params
+
+       keyCache sync.Map
+}
+
+func (c *defaultConfigProvider) Get(k string) interface{} {
+       if k == "" {
+               return c.root
+       }
+       c.mu.RLock()
+       key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
+       if m == nil {
+               return nil
+       }
+       v := m[key]
+       c.mu.RUnlock()
+       return v
+}
+
+func (c *defaultConfigProvider) GetBool(k string) bool {
+       v := c.Get(k)
+       return cast.ToBool(v)
+}
+
+func (c *defaultConfigProvider) GetInt(k string) int {
+       v := c.Get(k)
+       return cast.ToInt(v)
+}
+
+func (c *defaultConfigProvider) IsSet(k string) bool {
+       var found bool
+       c.mu.RLock()
+       key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
+       if m != nil {
+               _, found = m[key]
+       }
+       c.mu.RUnlock()
+       return found
+}
+
+func (c *defaultConfigProvider) GetString(k string) string {
+       v := c.Get(k)
+       return cast.ToString(v)
+}
+
+func (c *defaultConfigProvider) GetParams(k string) maps.Params {
+       v := c.Get(k)
+       if v == nil {
+               return nil
+       }
+       return v.(maps.Params)
+}
+
+func (c *defaultConfigProvider) GetStringMap(k string) map[string]interface{} {
+       v := c.Get(k)
+       return maps.ToStringMap(v)
+}
+
+func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
+       v := c.Get(k)
+       return maps.ToStringMapString(v)
+}
+
+func (c *defaultConfigProvider) GetStringSlice(k string) []string {
+       v := c.Get(k)
+       return cast.ToStringSlice(v)
+}
+
+func (c *defaultConfigProvider) Set(k string, v interface{}) {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+
+       k = strings.ToLower(k)
+
+       if k == "" {
+               if p, ok := maps.ToParamsAndPrepare(v); ok {
+                       // Set the values directly in root.
+                       c.root.Set(p)
+               } else {
+                       c.root[k] = v
+               }
+
+               return
+       }
+
+       switch vv := v.(type) {
+       case map[string]interface{}:
+               var p maps.Params = vv
+               v = p
+               maps.PrepareParams(p)
+       }
+
+       key, m := c.getNestedKeyAndMap(k, true)
+
+       if existing, found := m[key]; found {
+               if p1, ok := existing.(maps.Params); ok {
+                       if p2, ok := v.(maps.Params); ok {
+                               p1.Set(p2)
+                               return
+                       }
+               }
+       }
+
+       m[key] = v
+}
+
+func (c *defaultConfigProvider) Merge(k string, v interface{}) {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       k = strings.ToLower(k)
+
+       if k == "" {
+               rs, f := c.root.GetMergeStrategy()
+               if f && rs == maps.ParamsMergeStrategyNone {
+                       // The user has set a "no merge" strategy on this,
+                       // nothing more to do.
+                       return
+               }
+
+               if p, ok := maps.ToParamsAndPrepare(v); ok {
+                       // As there may be keys in p not in root, we need to handle
+                       // those as a special case.
+                       for kk, vv := range p {
+                               if pp, ok := vv.(maps.Params); ok {
+                                       if ppp, ok := c.root[kk]; ok {
+                                               ppp.(maps.Params).Merge(pp)
+                                       } else {
+                                               // We need to use the default merge strategy for
+                                               // this key.
+                                               np := make(maps.Params)
+                                               strategy := c.determineMergeStrategy(KeyParams{Key: "", Params: c.root}, KeyParams{Key: kk, Params: np})
+                                               np.SetDefaultMergeStrategy(strategy)
+                                               np.Merge(pp)
+                                               if len(np) > 0 {
+                                                       c.root[kk] = np
+                                               }
+                                       }
+                               }
+                       }
+                       // Merge the rest.
+                       c.root.Merge(p)
+               } else {
+                       panic(fmt.Sprintf("unsupported type %T received in Merge", v))
+               }
+
+               return
+       }
+
+       switch vv := v.(type) {
+       case map[string]interface{}:
+               var p maps.Params = vv
+               v = p
+               maps.PrepareParams(p)
+       }
+
+       key, m := c.getNestedKeyAndMap(k, true)
+
+       if existing, found := m[key]; found {
+               if p1, ok := existing.(maps.Params); ok {
+                       if p2, ok := v.(maps.Params); ok {
+                               p1.Merge(p2)
+                       }
+               }
+       } else {
+               m[key] = v
+       }
+}
+
+func (c *defaultConfigProvider) WalkParams(walkFn func(params ...KeyParams) bool) {
+       var walk func(params ...KeyParams)
+       walk = func(params ...KeyParams) {
+               if walkFn(params...) {
+                       return
+               }
+               p1 := params[len(params)-1]
+               i := len(params)
+               for k, v := range p1.Params {
+                       if p2, ok := v.(maps.Params); ok {
+                               paramsplus1 := make([]KeyParams, i+1)
+                               copy(paramsplus1, params)
+                               paramsplus1[i] = KeyParams{Key: k, Params: p2}
+                               walk(paramsplus1...)
+                       }
+               }
+       }
+       walk(KeyParams{Key: "", Params: c.root})
+}
+
+func (c *defaultConfigProvider) determineMergeStrategy(params ...KeyParams) maps.ParamsMergeStrategy {
+       if len(params) == 0 {
+               return maps.ParamsMergeStrategyNone
+       }
+
+       var (
+               strategy   maps.ParamsMergeStrategy
+               prevIsRoot bool
+               curr       = params[len(params)-1]
+       )
+
+       if len(params) > 1 {
+               prev := params[len(params)-2]
+               prevIsRoot = prev.Key == ""
+
+               // Inherit from parent (but not from the root unless it's set by user).
+               s, found := prev.Params.GetMergeStrategy()
+               if !prevIsRoot && !found {
+                       panic("invalid state, merge strategy not set on parent")
+               }
+               if found || !prevIsRoot {
+                       strategy = s
+               }
+       }
+
+       switch curr.Key {
+       case "":
+       // Don't set a merge strategy on the root unless set by user.
+       // This will be handled as a special case.
+       case "params":
+               strategy = maps.ParamsMergeStrategyDeep
+       case "outputformats", "mediatypes":
+               if prevIsRoot {
+                       strategy = maps.ParamsMergeStrategyShallow
+               }
+       case "menus":
+               isMenuKey := prevIsRoot
+               if !isMenuKey {
+                       // Can also be set below languages.
+                       // root > languages > en > menus
+                       if len(params) == 4 && params[1].Key == "languages" {
+                               isMenuKey = true
+                       }
+               }
+               if isMenuKey {
+                       strategy = maps.ParamsMergeStrategyShallow
+               }
+       default:
+               if strategy == "" {
+                       strategy = maps.ParamsMergeStrategyNone
+               }
+       }
+
+       return strategy
+}
+
+type KeyParams struct {
+       Key    string
+       Params maps.Params
+}
+
+func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
+       c.WalkParams(func(params ...KeyParams) bool {
+               if len(params) == 0 {
+                       return false
+               }
+               p := params[len(params)-1].Params
+               var found bool
+               if _, found = p.GetMergeStrategy(); found {
+                       // Set by user.
+                       return false
+               }
+               strategy := c.determineMergeStrategy(params...)
+               if strategy != "" {
+                       p.SetDefaultMergeStrategy(strategy)
+               }
+               return false
+       })
+
+}
+
+func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
+       var parts []string
+       v, ok := c.keyCache.Load(key)
+       if ok {
+               parts = v.([]string)
+       } else {
+               parts = strings.Split(key, ".")
+               c.keyCache.Store(key, parts)
+       }
+       current := c.root
+       for i := 0; i < len(parts)-1; i++ {
+               next, found := current[parts[i]]
+               if !found {
+                       if create {
+                               next = make(maps.Params)
+                               current[parts[i]] = next
+                       } else {
+                               return "", nil
+                       }
+               }
+               current = next.(maps.Params)
+       }
+       return parts[len(parts)-1], current
+}
diff --git a/config/defaultConfigProvider_test.go b/config/defaultConfigProvider_test.go
new file mode 100644 (file)
index 0000000..834165d
--- /dev/null
@@ -0,0 +1,315 @@
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+       "context"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+       "testing"
+
+       "github.com/spf13/viper"
+
+       "github.com/gohugoio/hugo/common/para"
+
+       "github.com/gohugoio/hugo/common/maps"
+
+       qt "github.com/frankban/quicktest"
+)
+
+func TestDefaultConfigProvider(t *testing.T) {
+       c := qt.New(t)
+
+       c.Run("Set and get", func(c *qt.C) {
+               cfg := New()
+               var k string
+               var v interface{}
+
+               k, v = "foo", "bar"
+               cfg.Set(k, v)
+               c.Assert(cfg.Get(k), qt.Equals, v)
+               c.Assert(cfg.Get(strings.ToUpper(k)), qt.Equals, v)
+               c.Assert(cfg.GetString(k), qt.Equals, v)
+
+               k, v = "foo", 42
+               cfg.Set(k, v)
+               c.Assert(cfg.Get(k), qt.Equals, v)
+               c.Assert(cfg.GetInt(k), qt.Equals, v)
+
+               c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                       "foo": 42,
+               })
+       })
+
+       c.Run("Set and get map", func(c *qt.C) {
+               cfg := New()
+
+               cfg.Set("foo", map[string]interface{}{
+                       "bar": "baz",
+               })
+
+               c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+                       "bar": "baz",
+               })
+
+               c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]interface{}{"bar": string("baz")})
+               c.Assert(cfg.GetStringMapString("foo"), qt.DeepEquals, map[string]string{"bar": string("baz")})
+       })
+
+       c.Run("Set and get nested", func(c *qt.C) {
+               cfg := New()
+
+               cfg.Set("a", map[string]interface{}{
+                       "B": "bv",
+               })
+               cfg.Set("a.c", "cv")
+
+               c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+                       "b": "bv",
+                       "c": "cv",
+               })
+               c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
+
+               cfg.Set("b.a", "av")
+               c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+                       "a": "av",
+               })
+
+               cfg.Set("b", map[string]interface{}{
+                       "b": "bv",
+               })
+
+               c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+                       "a": "av",
+                       "b": "bv",
+               })
+
+               cfg = New()
+
+               cfg.Set("a", "av")
+
+               cfg.Set("", map[string]interface{}{
+                       "a": "av2",
+                       "b": "bv2",
+               })
+
+               c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                       "a": "av2",
+                       "b": "bv2",
+               })
+
+               cfg = New()
+
+               cfg.Set("a", "av")
+
+               cfg.Set("", map[string]interface{}{
+                       "b": "bv2",
+               })
+
+               c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                       "a": "av",
+                       "b": "bv2",
+               })
+
+               cfg = New()
+
+               cfg.Set("", map[string]interface{}{
+                       "foo": map[string]interface{}{
+                               "a": "av",
+                       },
+               })
+
+               cfg.Set("", map[string]interface{}{
+                       "foo": map[string]interface{}{
+                               "b": "bv2",
+                       },
+               })
+
+               c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+                       "a": "av",
+                       "b": "bv2",
+               })
+       })
+
+       c.Run("Merge default strategy", func(c *qt.C) {
+               cfg := New()
+
+               cfg.Set("a", map[string]interface{}{
+                       "B": "bv",
+               })
+
+               cfg.Merge("a", map[string]interface{}{
+                       "B": "bv2",
+                       "c": "cv2",
+               })
+
+               c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+                       "b": "bv",
+                       "c": "cv2",
+               })
+
+               cfg = New()
+
+               cfg.Set("a", "av")
+
+               cfg.Merge("", map[string]interface{}{
+                       "a": "av2",
+                       "b": "bv2",
+               })
+
+               c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                       "a": "av",
+                       "b": "bv2",
+               })
+       })
+
+       c.Run("Merge shallow", func(c *qt.C) {
+               cfg := New()
+
+               cfg.Set("a", map[string]interface{}{
+                       "_merge": "shallow",
+                       "B":      "bv",
+                       "c": map[string]interface{}{
+                               "b": "bv",
+                       },
+               })
+
+               cfg.Merge("a", map[string]interface{}{
+                       "c": map[string]interface{}{
+                               "d": "dv2",
+                       },
+                       "e": "ev2",
+               })
+
+               c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+                       "e":      "ev2",
+                       "_merge": maps.ParamsMergeStrategyShallow,
+                       "b":      "bv",
+                       "c": maps.Params{
+                               "b": "bv",
+                       },
+               })
+       })
+
+       c.Run("IsSet", func(c *qt.C) {
+               cfg := New()
+
+               cfg.Set("a", map[string]interface{}{
+                       "B": "bv",
+               })
+
+               c.Assert(cfg.IsSet("A"), qt.IsTrue)
+               c.Assert(cfg.IsSet("a.b"), qt.IsTrue)
+               c.Assert(cfg.IsSet("z"), qt.IsFalse)
+       })
+
+       c.Run("Para", func(c *qt.C) {
+               cfg := New()
+               p := para.New(4)
+               r, _ := p.Start(context.Background())
+
+               setAndGet := func(k string, v int) error {
+                       vs := strconv.Itoa(v)
+                       cfg.Set(k, v)
+                       err := errors.New("get failed")
+                       if cfg.Get(k) != v {
+                               return err
+                       }
+                       if cfg.GetInt(k) != v {
+                               return err
+                       }
+                       if cfg.GetString(k) != vs {
+                               return err
+                       }
+                       if !cfg.IsSet(k) {
+                               return err
+                       }
+                       return nil
+               }
+
+               for i := 0; i < 20; i++ {
+                       i := i
+                       r.Run(func() error {
+                               const v = 42
+                               k := fmt.Sprintf("k%d", i)
+                               if err := setAndGet(k, v); err != nil {
+                                       return err
+                               }
+
+                               m := maps.Params{
+                                       "new": 42,
+                               }
+
+                               cfg.Merge("", m)
+
+                               return nil
+                       })
+               }
+
+               c.Assert(r.Wait(), qt.IsNil)
+       })
+}
+
+func BenchmarkDefaultConfigProvider(b *testing.B) {
+       type cfger interface {
+               Get(key string) interface{}
+               Set(key string, value interface{})
+               IsSet(key string) bool
+       }
+
+       newMap := func() map[string]interface{} {
+               return map[string]interface{}{
+                       "a": map[string]interface{}{
+                               "b": map[string]interface{}{
+                                       "c": 32,
+                                       "d": 43,
+                               },
+                       },
+                       "b": 62,
+               }
+       }
+
+       runMethods := func(b *testing.B, cfg cfger) {
+               m := newMap()
+               cfg.Set("mymap", m)
+               cfg.Set("num", 32)
+               if !(cfg.IsSet("mymap") && cfg.IsSet("mymap.a") && cfg.IsSet("mymap.a.b") && cfg.IsSet("mymap.a.b.c")) {
+                       b.Fatal("IsSet failed")
+               }
+
+               if cfg.Get("num") != 32 {
+                       b.Fatal("Get failed")
+               }
+
+               if cfg.Get("mymap.a.b.c") != 32 {
+                       b.Fatal("Get failed")
+               }
+       }
+
+       b.Run("Viper", func(b *testing.B) {
+               v := viper.New()
+               for i := 0; i < b.N; i++ {
+                       runMethods(b, v)
+               }
+       })
+
+       b.Run("Custom", func(b *testing.B) {
+               cfg := New()
+               for i := 0; i < b.N; i++ {
+                       runMethods(b, cfg)
+               }
+       })
+}
diff --git a/config/docshelper.go b/config/docshelper.go
new file mode 100644 (file)
index 0000000..336a0dc
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2021 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+       "github.com/gohugoio/hugo/common/maps"
+       "github.com/gohugoio/hugo/docshelper"
+)
+
+// This is is just some helpers used to create some JSON used in the Hugo docs.
+func init() {
+       docsProvider := func() docshelper.DocProvider {
+
+               cfg := New()
+               for _, configRoot := range ConfigRootKeys {
+                       cfg.Set(configRoot, make(maps.Params))
+               }
+               lang := maps.Params{
+                       "en": maps.Params{
+                               "menus":  maps.Params{},
+                               "params": maps.Params{},
+                       },
+               }
+               cfg.Set("languages", lang)
+               cfg.SetDefaultMergeStrategy()
+
+               configHelpers := map[string]interface{}{
+                       "mergeStrategy": cfg.Get(""),
+               }
+               return docshelper.DocProvider{"config": configHelpers}
+       }
+
+       docshelper.AddDocProviderFunc(docsProvider)
+}
index 0fb599c0ad556ec09f4ee7047a7790fd61ffb630..c17ce713deae16abd550a5efb1ae5938889ac7ca 100644 (file)
@@ -18,7 +18,6 @@ import (
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/config"
-       "github.com/spf13/viper"
 )
 
 func TestDecodeConfigFromTOML(t *testing.T) {
@@ -94,7 +93,7 @@ PrivacyENhanced = true
 func TestDecodeConfigDefault(t *testing.T) {
        c := qt.New(t)
 
-       pc, err := DecodeConfig(viper.New())
+       pc, err := DecodeConfig(config.New())
        c.Assert(err, qt.IsNil)
        c.Assert(pc, qt.Not(qt.IsNil))
        c.Assert(pc.YouTube.PrivacyEnhanced, qt.Equals, false)
index 6e979b999fb660e7c363e291dccadf3874ee418d..d7a52ba4f90c425d84dafc0d540bf7353133823b 100644 (file)
@@ -18,7 +18,7 @@ import (
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/config"
-       "github.com/spf13/viper"
+       
 )
 
 func TestDecodeConfigFromTOML(t *testing.T) {
@@ -55,7 +55,7 @@ disableInlineCSS = true
 func TestUseSettingsFromRootIfSet(t *testing.T) {
        c := qt.New(t)
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("disqusShortname", "root_short")
        cfg.Set("googleAnalytics", "ga_root")
 
index 37dbf98bd4e1edb7db9a8b859738ff2e2bb4de90..38ff7de8dd4658514f4a239b110361585947964f 100644 (file)
@@ -20,6 +20,8 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/deps"
 
        "github.com/gohugoio/hugo/hugolib"
@@ -30,7 +32,6 @@ import (
        "github.com/gohugoio/hugo/create"
        "github.com/gohugoio/hugo/helpers"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func TestNewContent(t *testing.T) {
@@ -245,7 +246,7 @@ func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
        return string(b)
 }
 
-func newTestCfg(c *qt.C, mm afero.Fs) (*viper.Viper, *hugofs.Fs) {
+func newTestCfg(c *qt.C, mm afero.Fs) (config.Provider, *hugofs.Fs) {
        cfg := `
 
 theme = "mytheme"
index 413b1211b1d0600e251008228b2488623ab76357..e30f31c321912726fef3766a71a6ba4cc4b6879b 100644 (file)
@@ -21,7 +21,7 @@ import (
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/config"
-       "github.com/spf13/viper"
+       
 )
 
 func TestDecodeConfigFromTOML(t *testing.T) {
@@ -164,7 +164,7 @@ Pattern = "["  # invalid regular expression
 func TestDecodeConfigDefault(t *testing.T) {
        c := qt.New(t)
 
-       dcfg, err := decodeConfig(viper.New())
+       dcfg, err := decodeConfig(config.New())
        c.Assert(err, qt.IsNil)
        c.Assert(len(dcfg.Targets), qt.Equals, 0)
        c.Assert(len(dcfg.Matchers), qt.Equals, 0)
index efa1b0573560a20cf96142d389fb1fbc4b4485c0..d21f6f172a85fca400d02324417726deff9f0d4f 100644 (file)
@@ -7,7 +7,7 @@ footnotereturnlinkcontents = "↩"
 languageCode = "en-us"
 title = "Hugo"
 
- ignoreErrors = ["error-remote-getjson"]
+ ignoreErrors = ["error-remote-getjson", "err-missing-instagram-accesstoken"]
 
 
 googleAnalytics = "UA-7131036-4"
index 48a4af0c90b39b55519aad7acb19090e0294d710..fd8c65d047044e6df8bbab323000a1dcc66be371 100644 (file)
@@ -80,6 +80,26 @@ Considering the structure above, when running `hugo --environment staging`, Hugo
 {{% note %}}
 Default environments are __development__ with `hugo server` and __production__ with `hugo`.
 {{%/ note %}}
+
+## Merge Configuration from Themes
+
+{{< new-in "0.84.0" >}} The configuration merge described below was improved in Hugo 0.84.0 and made fully configurable. The big change/improvement was that we now, by default, do deep merging of `params` maps from themes.
+
+The configuration value for `_merge` can be one of:
+
+none
+: No merge.
+
+shallow
+: Only add values for new keys.
+
+shallow
+: Add values for new keys, merge existing.
+
+Note that you don't need to be so verbose as in the default setup below; a `_merge` value higher up will be inherited if not set.
+
+{{< code-toggle config="mergeStrategy" skipHeader=true />}}
+
 ## All Configuration Settings
 
 The following is the full list of Hugo-defined variables with their default
index 2b17ad8af2437fe8bb72649dac5e9bceeafed1b0..b927f8d0a6414986a7d7a586daf331a898eb0cab 100644 (file)
         "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,
index da4b00719f2db385f3301a95614d23287a313723..0b92d520d924c921df98e21b50440e5f9e72ad76 100644 (file)
@@ -1,34 +1,41 @@
 {{ $file := .Get "file" }}
 {{ $code := "" }}
 {{ with .Get "config" }}
-{{ $file = $file | default "config" }}
-{{ $sections := (split . ".") }}
-{{ $configSection := index $.Site.Data.docs.config $sections }}
-{{ $code = dict $sections $configSection  }}
+  {{ $file = $file | default "config" }}
+  {{ $sections := (split . ".") }}
+  {{ $configSection := index $.Site.Data.docs.config $sections }}
+  {{ $code = dict $sections $configSection  }}
+  {{ if $.Get "skipHeader"}}
+    {{ $code = $configSection  }}
+  {{ end }}
 {{ else }}
-{{ $code = $.Inner }}
+  {{ $code = $.Inner }}
 {{ end  }}
 {{ $langs := (slice "yaml" "toml" "json") }}
 <div class="code relative" {{ with $file }}id="{{ . | urlize}}"{{ end }}>
-       <div class="code-nav flex flex-nowrap items-stretch">
-               {{- with $file -}}
-                       <div class="san-serif f6 dib lh-solid pl2 pv2 mr2">{{ . }}.</div>
-               {{- end -}}
-               {{ range $langs }}
-                       <button data-toggle-tab="{{ . }}" class="tab-button {{ cond (eq . "yaml") "active" ""}} ba san-serif f6 dib lh-solid ph2 pv2">{{ . }}</button>&nbsp;
-               {{ end }}
-       </div>
-       <div class="tab-content">
-               {{ range $langs }}
-                       <div data-pane="{{ . }}" class="code-copy-content nt3 tab-pane {{ cond (eq . "yaml") "active" ""}}">
-                               {{ highlight ($code | transform.Remarshal . | safeHTML) . ""}}
-                       </div>
-                       {{ if ne ($.Get "copy") "false" }}
-                               <button class="needs-js copy copy-toggle bg-accent-color-dark f6 absolute top-0 right-0 lh-solid hover-bg-primary-color-dark bn white ph3 pv2" title="Copy this code to your clipboard." data-clipboard-action="copy" aria-label="copy button">
-                               </button>
-                               {{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
-                       {{end}}
-               {{ end }}
-       </div>
-
+  <div class="code-nav flex flex-nowrap items-stretch">
+    {{- with $file -}}
+      <div class="san-serif f6 dib lh-solid pl2 pv2 mr2">
+        {{ . }}.
+      </div>
+    {{- end -}}
+    {{ range $langs }}
+      <button data-toggle-tab="{{ . }}" class="tab-button {{ cond (eq . "yaml") "active" ""}} ba san-serif f6 dib lh-solid ph2 pv2">
+        {{ . }}
+      </button>
+      &nbsp;
+    {{ end }}
+  </div>
+  <div class="tab-content">
+    {{ range $langs }}
+      <div data-pane="{{ . }}" class="code-copy-content nt3 tab-pane {{ cond (eq . "yaml") "active" ""}}">
+        {{ highlight ($code | transform.Remarshal . | safeHTML) . ""}}
+      </div>
+      {{ if ne ($.Get "copy") "false" }}
+        <button class="needs-js copy copy-toggle bg-accent-color-dark f6 absolute top-0 right-0 lh-solid hover-bg-primary-color-dark bn white ph3 pv2" title="Copy this code to your clipboard." data-clipboard-action="copy" aria-label="copy button"></button>
+        {{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
+      {{end}}
+    {{ end }}
+  </div>
+  
 </div>
diff --git a/go.mod b/go.mod
index ca89a0ca6d79cc93bd87a0fe5ba100efae0ecd3a..f0c9f1fa754565355acfab66a30164f034317a78 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -55,7 +55,7 @@ require (
        github.com/spf13/fsync v0.9.0
        github.com/spf13/jwalterweatherman v1.1.0
        github.com/spf13/pflag v1.0.5
-       github.com/spf13/viper v1.7.1
+       github.com/spf13/viper v1.7.0
        github.com/tdewolff/minify/v2 v2.9.16
        github.com/yuin/goldmark v1.3.5
        github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
diff --git a/go.sum b/go.sum
index ccdc49b69843129a9b394de1b9b76d259bdf0c22..d9cd59c3473e5f62fa3578630e4d83f7ad48f712 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -642,6 +642,7 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
index 573c1c90dc051a199df418edc44b232c5250769f..515b788f1cee1a7bda40faf32b1b1622165713fe 100644 (file)
@@ -19,12 +19,11 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/spf13/afero"
 
        "github.com/gohugoio/hugo/common/loggers"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
 )
 
@@ -103,7 +102,7 @@ func TestBytesToHTML(t *testing.T) {
 }
 
 func TestNewContentSpec(t *testing.T) {
-       cfg := viper.New()
+       cfg := config.New()
        c := qt.New(t)
 
        cfg.Set("summaryLength", 32)
index 518188c1737788d26576362d793cc1f0b186c748..bfabcbef4c15d1a64c1fdf857ac7c55ef2e5d358 100644 (file)
@@ -19,7 +19,7 @@ import (
        "strings"
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        "github.com/gohugoio/hugo/common/loggers"
 
@@ -29,7 +29,7 @@ import (
 
 func TestResolveMarkup(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs())
        c.Assert(err, qt.IsNil)
 
index e1ac90b2f927999b49bde2d02fd05b7e8027ebf4..c9595183253dc6cf40c9d8e373397167fe111ee9 100644 (file)
@@ -31,7 +31,6 @@ import (
 
        "github.com/gohugoio/hugo/hugofs"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func TestMakePath(t *testing.T) {
@@ -490,8 +489,6 @@ func TestExists(t *testing.T) {
 }
 
 func TestAbsPathify(t *testing.T) {
-       defer viper.Reset()
-
        type test struct {
                inPath, workingDir, expected string
        }
@@ -511,7 +508,6 @@ func TestAbsPathify(t *testing.T) {
        }
 
        for i, d := range data {
-               viper.Reset()
                // todo see comment in AbsPathify
                ps := newTestDefaultPathSpec("workingDir", d.workingDir)
 
index 4a6ebd0b16d84fa5599f9da73dfe52535ece47a6..7d63e4d88b41065998cdc35b973283ba710a1503 100644 (file)
@@ -2,24 +2,24 @@ package helpers
 
 import (
        "github.com/gohugoio/hugo/common/loggers"
+       "github.com/gohugoio/hugo/config"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/modules"
 )
 
-func newTestPathSpec(fs *hugofs.Fs, v *viper.Viper) *PathSpec {
+func newTestPathSpec(fs *hugofs.Fs, v config.Provider) *PathSpec {
        l := langs.NewDefaultLanguage(v)
        ps, _ := NewPathSpec(fs, l, nil)
        return ps
 }
 
 func newTestDefaultPathSpec(configKeyValues ...interface{}) *PathSpec {
-       v := viper.New()
+       v := config.New()
        fs := hugofs.NewMem(v)
-       cfg := newTestCfgFor(fs)
+       cfg := newTestCfg()
 
        for i := 0; i < len(configKeyValues); i += 2 {
                cfg.Set(configKeyValues[i].(string), configKeyValues[i+1])
@@ -27,15 +27,8 @@ func newTestDefaultPathSpec(configKeyValues ...interface{}) *PathSpec {
        return newTestPathSpec(fs, cfg)
 }
 
-func newTestCfgFor(fs *hugofs.Fs) *viper.Viper {
-       v := newTestCfg()
-       v.SetFs(fs.Source)
-
-       return v
-}
-
-func newTestCfg() *viper.Viper {
-       v := viper.New()
+func newTestCfg() config.Provider {
+       v := config.New()
        v.Set("contentDir", "content")
        v.Set("dataDir", "data")
        v.Set("i18nDir", "i18n")
@@ -56,7 +49,7 @@ func newTestCfg() *viper.Viper {
 }
 
 func newTestContentSpec() *ContentSpec {
-       v := viper.New()
+       v := config.New()
        spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs())
        if err != nil {
                panic(err)
index a343bbd1f8ff7ddba54b9ce7383d776b4b6cc4b6..8d52267af41c7a849588c11b865c70f81fa6b5f1 100644 (file)
@@ -16,15 +16,16 @@ package hugofs
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/htesting/hqt"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func TestNewDefault(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        f := NewDefault(v)
 
        c.Assert(f.Source, qt.Not(qt.IsNil))
@@ -35,7 +36,7 @@ func TestNewDefault(t *testing.T) {
 
 func TestNewMem(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        f := NewMem(v)
 
        c.Assert(f.Source, qt.Not(qt.IsNil))
@@ -48,7 +49,7 @@ func TestNewMem(t *testing.T) {
 
 func TestWorkingDir(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
 
        v.Set("workingDir", "/a/b/")
 
index fc2ddeb621e2e518008893999faa0c65193b417d..db9ed25cd5f551a9c131349568362518ad2ef2ef 100644 (file)
@@ -20,7 +20,7 @@ import (
        "sort"
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/htesting"
@@ -29,7 +29,7 @@ import (
 
 func TestLanguageRootMapping(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
 
        fs := NewBaseFileDecorator(afero.NewMemMapFs())
index eaa6710ae7da74910464867cd9f43a0f5d5f2cc0..f559d7fd30b619cd96eb3498b03eafa9de95d800 100644 (file)
@@ -43,34 +43,136 @@ import (
        "github.com/gohugoio/hugo/config/services"
        "github.com/gohugoio/hugo/helpers"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
-// SiteConfig represents the config in .Site.Config.
-type SiteConfig struct {
-       // This contains all privacy related settings that can be used to
-       // make the YouTube template etc. GDPR compliant.
-       Privacy privacy.Config
+var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n       Run `hugo help new` for details.\n")
 
-       // Services contains config for services such as Google Analytics etc.
-       Services services.Config
-}
+// LoadConfig loads Hugo configuration into a new Viper and then adds
+// a set of defaults.
+func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (config.Provider, []string, error) {
 
-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.).
@@ -98,6 +200,13 @@ type ConfigSourceDescriptor struct {
        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"}
@@ -105,185 +214,219 @@ func (d ConfigSourceDescriptor) configFilenames() []string {
        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) {
@@ -318,24 +461,13 @@ func (l configLoader) loadConfig(configName string, v *viper.Viper) (string, err
                return "", l.wrapFileError(err, filename)
        }
 
-       if err = v.MergeConfigMap(m); err != nil {
-               return "", l.wrapFileError(err, filename)
-       }
+       // Set overwrites keys of the same name, recursively.
+       l.cfg.Set("", m)
 
        return filename, nil
 }
 
-func (l configLoader) wrapFileError(err error, filename string) error {
-       err, _ = herrors.WithFileContextForFile(
-               err,
-               filename,
-               filename,
-               l.Fs,
-               herrors.SimpleLineMatcher)
-       return err
-}
-
-func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error) {
+func (l configLoader) loadConfigFromConfigDir() ([]string, error) {
        sourceFs := l.Fs
        configDir := l.AbsConfigDir
 
@@ -421,9 +553,8 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
                        // Migrate menu => menus etc.
                        config.RenameKeys(root)
 
-                       if err := v.MergeConfigMap(root); err != nil {
-                               return l.wrapFileError(err, path)
-                       }
+                       // Set will overwrite keys with the same name, recursively.
+                       l.cfg.Set("", root)
 
                        return nil
                })
@@ -436,8 +567,13 @@ func (l configLoader) loadConfigFromConfigDir(v *viper.Viper) ([]string, error)
        return dirnames, nil
 }
 
-func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error) {
-       modConfig, err := modules.DecodeConfig(v1)
+func (l configLoader) loadLanguageSettings(oldLangs langs.Languages) error {
+       _, err := langs.LoadLanguageSettings(l.cfg, oldLangs)
+       return err
+}
+
+func (l configLoader) loadModulesConfig() (modules.Config, error) {
+       modConfig, err := modules.DecodeConfig(l.cfg)
        if err != nil {
                return modules.Config{}, err
        }
@@ -445,211 +581,29 @@ func (l configLoader) loadModulesConfig(v1 *viper.Viper) (modules.Config, error)
        return modConfig, nil
 }
 
-func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
-       workingDir := l.WorkingDir
-       if workingDir == "" {
-               workingDir = v1.GetString("workingDir")
-       }
-
-       themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir"))
-
-       var ignoreVendor glob.Glob
-       if s := v1.GetString("ignoreVendorPaths"); s != "" {
-               ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
-       }
-
-       filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1)
+func (configLoader) loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
+       privacyConfig, err := privacy.DecodeConfig(cfg)
        if err != nil {
-               return nil, nil, err
-       }
-
-       v1.Set("filecacheConfigs", filecacheConfigs)
-
-       var configFilenames []string
-
-       hook := func(m *modules.ModulesConfig) error {
-               for _, tc := range m.ActiveModules {
-                       if tc.ConfigFilename() != "" {
-                               if tc.Watch() {
-                                       configFilenames = append(configFilenames, tc.ConfigFilename())
-                               }
-                               if err := l.applyThemeConfig(v1, tc); err != nil {
-                                       return err
-                               }
-                       }
-               }
-
-               if hookBeforeFinalize != nil {
-                       return hookBeforeFinalize(m)
-               }
-
-               return nil
-       }
-
-       modulesClient := modules.NewClient(modules.ClientConfig{
-               Fs:                 l.Fs,
-               Logger:             l.Logger,
-               HookBeforeFinalize: hook,
-               WorkingDir:         workingDir,
-               ThemesDir:          themesDir,
-               CacheDir:           filecacheConfigs.CacheDirModules(),
-               ModuleConfig:       modConfig,
-               IgnoreVendor:       ignoreVendor,
-       })
-
-       v1.Set("modulesClient", modulesClient)
-
-       moduleConfig, err := modulesClient.Collect()
-
-       // Avoid recreating these later.
-       v1.Set("allModules", moduleConfig.ActiveModules)
-
-       if moduleConfig.GoModulesFilename != "" {
-               // We want to watch this for changes and trigger rebuild on version
-               // changes etc.
-               configFilenames = append(configFilenames, moduleConfig.GoModulesFilename)
-       }
-
-       return moduleConfig.ActiveModules, configFilenames, err
-}
-
-func (l configLoader) applyThemeConfig(v1 *viper.Viper, theme modules.Module) error {
-       const (
-               paramsKey    = "params"
-               languagesKey = "languages"
-               menuKey      = "menus"
-       )
-
-       v2 := theme.Cfg()
-
-       for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
-               l.mergeStringMapKeepLeft("", key, v1, v2)
-       }
-
-       // Only add params and new menu entries, we do not add language definitions.
-       if v1.IsSet(languagesKey) && v2.IsSet(languagesKey) {
-               v1Langs := v1.GetStringMap(languagesKey)
-               for k := range v1Langs {
-                       langParamsKey := languagesKey + "." + k + "." + paramsKey
-                       l.mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
-               }
-               v2Langs := v2.GetStringMap(languagesKey)
-               for k := range v2Langs {
-                       if k == "" {
-                               continue
-                       }
-
-                       langMenuKey := languagesKey + "." + k + "." + menuKey
-                       if v2.IsSet(langMenuKey) {
-                               // Only add if not in the main config.
-                               v2menus := v2.GetStringMap(langMenuKey)
-                               for k, v := range v2menus {
-                                       menuEntry := menuKey + "." + k
-                                       menuLangEntry := langMenuKey + "." + k
-                                       if !v1.IsSet(menuEntry) && !v1.IsSet(menuLangEntry) {
-                                               v1.Set(menuLangEntry, v)
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // Add menu definitions from theme not found in project
-       if v2.IsSet(menuKey) {
-               v2menus := v2.GetStringMap(menuKey)
-               for k, v := range v2menus {
-                       menuEntry := menuKey + "." + k
-                       if !v1.IsSet(menuEntry) {
-                               v1.SetDefault(menuEntry, v)
-                       }
-               }
-       }
-
-       return nil
-}
-
-func (configLoader) mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
-       if !v2.IsSet(key) {
                return
        }
 
-       if !v1.IsSet(key) && !(rootKey != "" && rootKey != key && v1.IsSet(rootKey)) {
-               v1.Set(key, v2.Get(key))
+       servicesConfig, err := services.DecodeConfig(cfg)
+       if err != nil {
                return
        }
 
-       m1 := v1.GetStringMap(key)
-       m2 := v2.GetStringMap(key)
+       scfg.Privacy = privacyConfig
+       scfg.Services = servicesConfig
 
-       for k, v := range m2 {
-               if _, found := m1[k]; !found {
-                       if rootKey != "" && v1.IsSet(rootKey+"."+k) {
-                               continue
-                       }
-                       m1[k] = v
-               }
-       }
+       return
 }
 
-func loadDefaultSettingsFor(v *viper.Viper) error {
-       v.RegisterAlias("indexes", "taxonomies")
-
-       /*
-
-               TODO(bep) from 0.56 these are configured as module mounts.
-                       v.SetDefault("contentDir", "content")
-                       v.SetDefault("layoutDir", "layouts")
-                       v.SetDefault("assetDir", "assets")
-                       v.SetDefault("staticDir", "static")
-                       v.SetDefault("dataDir", "data")
-                       v.SetDefault("i18nDir", "i18n")
-                       v.SetDefault("archetypeDir", "archetypes")
-       */
-
-       v.SetDefault("cleanDestinationDir", false)
-       v.SetDefault("watch", false)
-       v.SetDefault("resourceDir", "resources")
-       v.SetDefault("publishDir", "public")
-       v.SetDefault("themesDir", "themes")
-       v.SetDefault("buildDrafts", false)
-       v.SetDefault("buildFuture", false)
-       v.SetDefault("buildExpired", false)
-       v.SetDefault("environment", hugo.EnvironmentProduction)
-       v.SetDefault("uglyURLs", false)
-       v.SetDefault("verbose", false)
-       v.SetDefault("ignoreCache", false)
-       v.SetDefault("canonifyURLs", false)
-       v.SetDefault("relativeURLs", false)
-       v.SetDefault("removePathAccents", false)
-       v.SetDefault("titleCaseStyle", "AP")
-       v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
-       v.SetDefault("permalinks", make(map[string]string))
-       v.SetDefault("sitemap", config.Sitemap{Priority: -1, Filename: "sitemap.xml"})
-       v.SetDefault("disableLiveReload", false)
-       v.SetDefault("pluralizeListTitles", true)
-       v.SetDefault("forceSyncStatic", false)
-       v.SetDefault("footnoteAnchorPrefix", "")
-       v.SetDefault("footnoteReturnLinkContents", "")
-       v.SetDefault("newContentEditor", "")
-       v.SetDefault("paginate", 10)
-       v.SetDefault("paginatePath", "page")
-       v.SetDefault("summaryLength", 70)
-       v.SetDefault("rssLimit", -1)
-       v.SetDefault("sectionPagesMenu", "")
-       v.SetDefault("disablePathToLower", false)
-       v.SetDefault("hasCJKLanguage", false)
-       v.SetDefault("enableEmoji", false)
-       v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
-       v.SetDefault("defaultContentLanguage", "en")
-       v.SetDefault("defaultContentLanguageInSubdir", false)
-       v.SetDefault("enableMissingTranslationPlaceholders", false)
-       v.SetDefault("enableGitInfo", false)
-       v.SetDefault("ignoreFiles", make([]string, 0))
-       v.SetDefault("disableAliases", false)
-       v.SetDefault("debug", false)
-       v.SetDefault("disableFastRender", false)
-       v.SetDefault("timeout", "30s")
-       v.SetDefault("enableInlineShortcodes", false)
-
-       return nil
+func (l configLoader) wrapFileError(err error, filename string) error {
+       err, _ = herrors.WithFileContextForFile(
+               err,
+               filename,
+               filename,
+               l.Fs,
+               herrors.SimpleLineMatcher)
+       return err
 }
index fb81c927eb964054fa04f40a3eeea8311342673a..77ac9b92f162efeecb3a1e0a55b09d20f151340c 100644 (file)
@@ -17,11 +17,15 @@ import (
        "bytes"
        "fmt"
        "path/filepath"
+       "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/media"
+       "github.com/google/go-cmp/cmp"
+
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func TestLoadConfig(t *testing.T) {
@@ -77,12 +81,7 @@ func TestLoadConfigFromTheme(t *testing.T) {
 
        c := qt.New(t)
 
-       mainConfigBasic := `
-theme = "test-theme"
-baseURL = "https://example.com/"
-
-`
-       mainConfig := `
+       mainConfigTemplate := `
 theme = "test-theme"
 baseURL = "https://example.com/"
 
@@ -90,9 +89,12 @@ baseURL = "https://example.com/"
 date = ["date","publishDate"]
 
 [params]
+MERGE_PARAMS
 p1 = "p1 main"
-p2 = "p2 main"
-top = "top"
+[params.b]
+b1 = "b1 main"
+[params.b.c]
+bc1 = "bc1 main"
 
 [mediaTypes]
 [mediaTypes."text/m1"]
@@ -130,7 +132,14 @@ expiryDate = ["date"]
 [params]
 p1 = "p1 theme"
 p2 = "p2 theme"
-p3 = "p3 theme"
+[params.b]
+b1 = "b1 theme"
+b2 = "b2 theme"
+[params.b.c]
+bc1 = "bc1 theme"
+bc2 = "bc2 theme"
+[params.b.c.d]
+bcd1 = "bcd1 theme"
 
 [mediaTypes]
 [mediaTypes."text/m1"]
@@ -176,190 +185,137 @@ name = "menu-theme"
 
 `
 
-       b := newTestSitesBuilder(t)
-       b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig)
-       b.CreateSites().Build(BuildCfg{})
-
-       got := b.Cfg.(*viper.Viper).AllSettings()
-
-       b.AssertObject(`
-map[string]interface {}{
-  "p1": "p1 main",
-  "p2": "p2 main",
-  "p3": "p3 theme",
-  "top": "top",
-}`, got["params"])
-
-       b.AssertObject(`
-map[string]interface {}{
-  "date": []interface {}{
-    "date",
-    "publishDate",
-  },
-}`, got["frontmatter"])
-
-       b.AssertObject(`
-map[string]interface {}{
-  "text/m1": map[string]interface {}{
-    "suffixes": []interface {}{
-      "m1main",
-    },
-  },
-  "text/m2": map[string]interface {}{
-    "suffixes": []interface {}{
-      "m2theme",
-    },
-  },
-}`, got["mediatypes"])
-
-       b.AssertObject(`
-map[string]interface {}{
-  "o1": map[string]interface {}{
-    "basename": "o1main",
-    "mediatype": Type{
-      MainType: "text",
-      SubType: "m1",
-      Delimiter: ".",
-      FirstSuffix: SuffixInfo{
-        Suffix: "m1main",
-        FullSuffix: ".m1main",
-      },
-    },
-  },
-  "o2": map[string]interface {}{
-    "basename": "o2theme",
-    "mediatype": Type{
-      MainType: "text",
-      SubType: "m2",
-      Delimiter: ".",
-      FirstSuffix: SuffixInfo{
-        Suffix: "m2theme",
-        FullSuffix: ".m2theme",
-      },
-    },
-  },
-}`, got["outputformats"])
-
-       b.AssertObject(`map[string]interface {}{
-  "en": map[string]interface {}{
-    "languagename": "English",
-    "menus": map[string]interface {}{
-      "theme": []map[string]interface {}{
-        map[string]interface {}{
-          "name": "menu-lang-en-theme",
-        },
-      },
-    },
-    "params": map[string]interface {}{
-      "pl1": "p1-en-main",
-      "pl2": "p2-en-theme",
-    },
-  },
-  "nb": map[string]interface {}{
-    "languagename": "Norsk",
-    "menus": map[string]interface {}{
-      "theme": []map[string]interface {}{
-        map[string]interface {}{
-          "name": "menu-lang-nb-theme",
-        },
-      },
-    },
-    "params": map[string]interface {}{
-      "pl1": "p1-nb-main",
-      "pl2": "p2-nb-theme",
-    },
-  },
-}
-`, got["languages"])
-
-       b.AssertObject(`
-map[string]interface {}{
-  "main": []map[string]interface {}{
-    map[string]interface {}{
-      "name": "menu-main-main",
-    },
-  },
-  "thememenu": []map[string]interface {}{
-    map[string]interface {}{
-      "name": "menu-theme",
-    },
-  },
-  "top": []map[string]interface {}{
-    map[string]interface {}{
-      "name": "menu-top-main",
-    },
-  },
-}
-`, got["menus"])
+       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) {
@@ -490,7 +446,12 @@ intSlice = [5,7,9]
 floatSlice = [3.14, 5.19]
 stringSlice = ["a", "b"]
 
+[outputFormats]
+[outputFormats.ofbase]
+mediaType = "text/plain"
+
 [params]
+paramWithNoEnvOverride="nooverride"
 [params.api_config]
 api_key="default_key"
 another_key="default another_key"
@@ -504,9 +465,16 @@ quality = 75
 
        b.WithSourceFile("themes/mytheme/config.toml", `
 
+[outputFormats]
+[outputFormats.oftheme]
+mediaType = "text/plain"
+[outputFormats.ofbase]
+mediaType = "application/xml"
+
 [params]
 [params.mytheme_section]
 theme_param="themevalue"
+theme_param_nooverride="nooverride"
 [params.mytheme_section2]
 theme_param="themevalue2"
 
@@ -530,14 +498,16 @@ theme_param="themevalue2"
                "HUGOxPARAMSxMYTHEME_SECTION2xTHEME_PARAM", "themevalue2_changed",
                "HUGO_PARAMS_EMPTY", ``,
                "HUGO_PARAMS_HTML", `<a target="_blank" />`,
-               //
+               // Issue #8618
                "HUGO_SERVICES_GOOGLEANALYTICS_ID", `gaid`,
+               "HUGO_PARAMS_A_B_C", "abc",
        )
 
        b.Build(BuildCfg{})
 
        cfg := b.H.Cfg
-       scfg := b.H.Sites[0].siteConfigConfig.Services
+       s := b.H.Sites[0]
+       scfg := s.siteConfigConfig.Services
 
        c.Assert(cfg.Get("environment"), qt.Equals, "test")
        c.Assert(cfg.GetBool("enablegitinfo"), qt.Equals, false)
@@ -551,9 +521,23 @@ theme_param="themevalue2"
        c.Assert(cfg.Get("params.api_config.api_key"), qt.Equals, "new_key")
        c.Assert(cfg.Get("params.api_config.another_key"), qt.Equals, "default another_key")
        c.Assert(cfg.Get("params.mytheme_section.theme_param"), qt.Equals, "themevalue_changed")
+       c.Assert(cfg.Get("params.mytheme_section.theme_param_nooverride"), qt.Equals, "nooverride")
        c.Assert(cfg.Get("params.mytheme_section2.theme_param"), qt.Equals, "themevalue2_changed")
        c.Assert(cfg.Get("params.empty"), qt.Equals, ``)
        c.Assert(cfg.Get("params.html"), qt.Equals, `<a target="_blank" />`)
 
+       params := cfg.Get("params").(maps.Params)
+       c.Assert(params["paramwithnoenvoverride"], qt.Equals, "nooverride")
+       c.Assert(cfg.Get("params.paramwithnoenvoverride"), qt.Equals, "nooverride")
        c.Assert(scfg.GoogleAnalytics.ID, qt.Equals, "gaid")
+       c.Assert(cfg.Get("params.a.b"), qt.DeepEquals, maps.Params{
+               "c": "abc",
+       })
+
+       ofBase, _ := s.outputFormatsConfig.GetByName("ofbase")
+       ofTheme, _ := s.outputFormatsConfig.GetByName("oftheme")
+
+       c.Assert(ofBase.MediaType, qt.Equals, media.TextType)
+       c.Assert(ofTheme.MediaType, qt.Equals, media.TextType)
+
 }
index 139d0c20efb615444a4a202de3ca21da8a931849..a119d4c179024a5a02cd588fc8086bc0c731b295 100644 (file)
@@ -33,7 +33,7 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/hugolib/paths"
        "github.com/gohugoio/hugo/modules"
-       "github.com/spf13/viper"
+       
 )
 
 func initConfig(fs afero.Fs, cfg config.Provider) error {
@@ -76,7 +76,7 @@ func initConfig(fs afero.Fs, cfg config.Provider) error {
 
 func TestNewBaseFs(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
 
        fs := hugofs.NewMem(v)
 
@@ -181,8 +181,8 @@ theme = ["atheme"]
        }
 }
 
-func createConfig() *viper.Viper {
-       v := viper.New()
+func createConfig() config.Provider {
+       v := config.New()
        v.Set("contentDir", "mycontent")
        v.Set("i18nDir", "myi18n")
        v.Set("staticDir", "mystatic")
@@ -453,7 +453,7 @@ func countFilesAndGetFilenames(fs afero.Fs, dirname string) (int, []string, erro
        return counter, filenames, nil
 }
 
-func setConfigAndWriteSomeFilesTo(fs afero.Fs, v *viper.Viper, key, val string, num int) {
+func setConfigAndWriteSomeFilesTo(fs afero.Fs, v config.Provider, key, val string, num int) {
        workingDir := v.GetString("workingDir")
        v.Set(key, val)
        fs.Mkdir(val, 0755)
index 5ebf8bd0b0d2301d454289a75292566bf61a7da2..96355f08bc686d4445922ffcf128170a1d73c770 100644 (file)
@@ -22,6 +22,7 @@ import (
        "testing"
        "time"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/modules/npm"
 
        "github.com/gohugoio/hugo/common/loggers"
@@ -37,7 +38,6 @@ import (
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/testmodBuilder/mods"
-       "github.com/spf13/viper"
 )
 
 func TestHugoModulesVariants(t *testing.T) {
@@ -45,7 +45,7 @@ func TestHugoModulesVariants(t *testing.T) {
                t.Skip("skip (relative) long running modules test when running locally")
        }
 
-       config := `
+       tomlConfig := `
 baseURL="https://example.org"
 workingDir = %q
 
@@ -56,7 +56,7 @@ path="github.com/gohugoio/hugoTestModule2"
 `
 
        createConfig := func(workingDir, moduleOpts string) string {
-               return fmt.Sprintf(config, workingDir, moduleOpts)
+               return fmt.Sprintf(tomlConfig, workingDir, moduleOpts)
        }
 
        newTestBuilder := func(t testing.TB, moduleOpts string) (*sitesBuilder, func()) {
@@ -65,7 +65,7 @@ path="github.com/gohugoio/hugoTestModule2"
                b.Assert(err, qt.IsNil)
                workingDir := filepath.Join(tempDir, "myhugosite")
                b.Assert(os.MkdirAll(workingDir, 0777), qt.IsNil)
-               b.Fs = hugofs.NewDefault(viper.New())
+               b.Fs = hugofs.NewDefault(config.New())
                b.WithWorkingDir(workingDir).WithConfigFile("toml", createConfig(workingDir, moduleOpts))
                b.WithTemplates(
                        "index.html", `
@@ -333,7 +333,7 @@ func TestHugoModulesMatrix(t *testing.T) {
        for _, m := range testmods[:2] {
                c := qt.New(t)
 
-               v := viper.New()
+               v := config.New()
 
                workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-test")
                c.Assert(err, qt.IsNil)
@@ -671,7 +671,7 @@ func TestModulesSymlinks(t *testing.T) {
 
        c := qt.New(t)
        // We need to use the OS fs for this.
-       cfg := viper.New()
+       cfg := config.New()
        fs := hugofs.NewFrom(hugofs.Os, cfg)
 
        workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mod-sym")
@@ -839,13 +839,13 @@ workingDir = %q
 
 `
 
-       config := fmt.Sprintf(configTemplate, workingDir)
+       tomlConfig := fmt.Sprintf(configTemplate, workingDir)
 
        b := newTestSitesBuilder(t).Running()
 
-       b.Fs = hugofs.NewDefault(viper.New())
+       b.Fs = hugofs.NewDefault(config.New())
 
-       b.WithWorkingDir(workingDir).WithConfigFile("toml", config)
+       b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig)
        b.WithTemplatesAdded("index.html", `
 {{ .Title }}
 {{ .Content }}
@@ -960,16 +960,16 @@ workingDir = %q
 %s
 
 `
-               config := fmt.Sprintf(configTemplate, workingDir, mounts)
-               config = strings.Replace(config, "WORKING_DIR", workingDir, -1)
+               tomlConfig := fmt.Sprintf(configTemplate, workingDir, mounts)
+               tomlConfig = strings.Replace(tomlConfig, "WORKING_DIR", workingDir, -1)
 
                b := newTestSitesBuilder(c).Running()
 
-               b.Fs = hugofs.NewDefault(viper.New())
+               b.Fs = hugofs.NewDefault(config.New())
 
                os.MkdirAll(filepath.Join(workingDir, "content", "blog"), 0777)
 
-               b.WithWorkingDir(workingDir).WithConfigFile("toml", config)
+               b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig)
 
                return test{
                        b:          b,
@@ -1064,7 +1064,7 @@ func TestSiteWithGoModButNoModules(t *testing.T) {
        workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod")
        c.Assert(err, qt.IsNil)
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("workingDir", workDir)
        fs := hugofs.NewFrom(hugofs.Os, cfg)
 
@@ -1090,7 +1090,7 @@ func TestModuleAbsMount(t *testing.T) {
        absContentDir, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-content")
        c.Assert(err, qt.IsNil)
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("workingDir", workDir)
        fs := hugofs.NewFrom(hugofs.Os, cfg)
 
index 0607bde1cbf426a475dd554e7c0eea483e5d7f96..d380cf737db6ca90c6776fec02e7c37a6a35f2e3 100644 (file)
@@ -374,7 +374,8 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
                s.h = h
        }
 
-       if err := applyDeps(cfg, sites...); err != nil {
+       var l configLoader
+       if err := l.applyDeps(cfg, sites...); err != nil {
                return nil, errors.Wrap(err, "add site dependencies")
        }
 
@@ -407,7 +408,7 @@ func (h *HugoSites) loadGitInfo() error {
        return nil
 }
 
-func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
+func (l configLoader) applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
        if cfg.TemplateProvider == nil {
                cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
        }
@@ -446,7 +447,7 @@ func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
 
                        d.Site = s.Info
 
-                       siteConfig, err := loadSiteConfig(s.language)
+                       siteConfig, err := l.loadSiteConfig(s.language)
                        if err != nil {
                                return errors.Wrap(err, "load site config")
                        }
@@ -607,11 +608,12 @@ func (h *HugoSites) withSite(fn func(s *Site) error) error {
 func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error {
        oldLangs, _ := h.Cfg.Get("languagesSorted").(langs.Languages)
 
-       if err := loadLanguageSettings(h.Cfg, oldLangs); err != nil {
+       l := configLoader{cfg: h.Cfg}
+       if err := l.loadLanguageSettings(oldLangs); err != nil {
                return err
        }
 
-       depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: cfg}
+       depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: l.cfg}
 
        sites, err := createSitesFromConfig(depsCfg)
        if err != nil {
@@ -629,7 +631,8 @@ func (h *HugoSites) createSitesFromConfig(cfg config.Provider) error {
                s.h = h
        }
 
-       if err := applyDeps(depsCfg, sites...); err != nil {
+       var cl configLoader
+       if err := cl.applyDeps(depsCfg, sites...); err != nil {
                return err
        }
 
index efe013c3977c64484d0fc26fdccdec816389983f..8b23e7ac734a989802464e7a33a40a4b02805dee 100644 (file)
@@ -5,9 +5,7 @@ import (
        "path/filepath"
        "strings"
        "testing"
-       "time"
 
-       "github.com/fortytw2/leaktest"
        "github.com/gohugoio/hugo/htesting"
 
        qt "github.com/frankban/quicktest"
@@ -318,7 +316,7 @@ Some content.
 // https://github.com/gohugoio/hugo/issues/5375
 func TestSiteBuildTimeout(t *testing.T) {
        if !htesting.IsCI() {
-               defer leaktest.CheckTimeout(t, 10*time.Second)()
+               //defer leaktest.CheckTimeout(t, 10*time.Second)()
        }
 
        b := newTestSitesBuilder(t)
index 0dacf2a33e099706c240c6315eddb04e5bf4a429..4726f5b4951cda9bc216e43fe97a70c5853156f3 100644 (file)
@@ -21,11 +21,11 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/htesting"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/hugofs"
-       "github.com/spf13/viper"
 )
 
 // We have many tests for the different resize operations etc. in the resource package,
@@ -38,7 +38,7 @@ func TestImageOps(t *testing.T) {
        defer clean()
 
        newBuilder := func(timeout interface{}) *sitesBuilder {
-               v := viper.New()
+               v := config.New()
                v.Set("workingDir", workDir)
                v.Set("baseURL", "https://example.org")
                v.Set("timeout", timeout)
index cd0883fd5e6213476ad0669dc2ba799f8fcbb39f..75dc0e7de9b20b56f071f02972609223c2b8e0ff 100644 (file)
@@ -21,11 +21,10 @@ import (
        "testing"
 
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/config"
 
        "github.com/gohugoio/hugo/htesting"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -88,7 +87,7 @@ document.body.textContent = greeter(user);`
        c.Assert(err, qt.IsNil)
        defer clean()
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workDir)
        v.Set("disableKinds", []string{"taxonomy", "term", "page"})
        b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
@@ -162,7 +161,7 @@ func TestJSBuild(t *testing.T) {
        c.Assert(err, qt.IsNil)
        defer clean()
 
-       config := fmt.Sprintf(`
+       tomlConfig := fmt.Sprintf(`
 baseURL = "https://example.org"
 workingDir = %q
 
@@ -177,8 +176,8 @@ path="github.com/gohugoio/hugoTestProjectJSModImports"
 `, workDir)
 
        b := newTestSitesBuilder(t)
-       b.Fs = hugofs.NewDefault(viper.New())
-       b.WithWorkingDir(workDir).WithConfigFile("toml", config).WithLogger(loggers.NewInfoLogger())
+       b.Fs = hugofs.NewDefault(config.New())
+       b.WithWorkingDir(workDir).WithConfigFile("toml", tomlConfig).WithLogger(loggers.NewInfoLogger())
        b.WithSourceFile("go.mod", `module github.com/gohugoio/tests/testHugoModules
         
 go 1.15
index 66e674adeadddeb6bdd820d5ff2ab218df6958b6..ef460efa260c63952ce0ab0d418724d4c8b0f036 100644 (file)
@@ -16,13 +16,13 @@ package hugolib
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 )
 
 func TestMinifyPublisher(t *testing.T) {
        t.Parallel()
 
-       v := viper.New()
+       v := config.New()
        v.Set("minify", true)
        v.Set("baseURL", "https://example.org/")
 
index 3df997452cb977062421c26eb9e359725eeb5f3c..759fadd2d405b083bfe99f56ddda05cee0c5fbaf 100644 (file)
@@ -336,7 +336,7 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
 
        if frontmatter != nil {
                // Needed for case insensitive fetching of params values
-               maps.ToLower(frontmatter)
+               maps.PrepareParams(frontmatter)
                if p.bucket != nil {
                        // Check for any cascade define on itself.
                        if cv, found := frontmatter["cascade"]; found {
index 9d23aaa5c703fb87c12ca15763109be40d1b3e0e..5bc3db22fb4b9b0497bce76ebb9e9ecfc6ee6fb8 100644 (file)
@@ -37,7 +37,6 @@ import (
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/resources/resource"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
@@ -786,7 +785,7 @@ func TestPageWithLastmodFromGitInfo(t *testing.T) {
        c := qt.New(t)
 
        // We need to use the OS fs for this.
-       cfg := viper.New()
+       cfg := config.New()
        fs := hugofs.NewFrom(hugofs.Os, cfg)
        fs.Destination = &afero.MemMapFs{}
 
@@ -1066,7 +1065,7 @@ func TestChompBOM(t *testing.T) {
 
 func TestPageWithEmoji(t *testing.T) {
        for _, enableEmoji := range []bool{true, false} {
-               v := viper.New()
+               v := config.New()
                v.Set("enableEmoji", enableEmoji)
 
                b := newTestSitesBuilder(t).WithViper(v)
index 7d775871a7fde8ae52da138e94776422800aff72..b63d663e30b1381c8df1efb9fe94fd89bc09c848 100644 (file)
@@ -23,6 +23,8 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/helpers"
@@ -35,7 +37,6 @@ import (
        "github.com/gohugoio/hugo/htesting"
 
        "github.com/gohugoio/hugo/deps"
-       "github.com/spf13/viper"
 
        qt "github.com/frankban/quicktest"
 )
@@ -352,12 +353,11 @@ func TestMultilingualDisableDefaultLanguage(t *testing.T) {
 
        c := qt.New(t)
        _, cfg := newTestBundleSourcesMultilingual(t)
-
        cfg.Set("disableLanguages", []string{"en"})
-
-       err := loadDefaultSettingsFor(cfg)
+       l := configLoader{cfg: cfg}
+       err := l.applyConfigDefaults()
        c.Assert(err, qt.IsNil)
-       err = loadLanguageSettings(cfg, nil)
+       err = l.loadLanguageSettings(nil)
        c.Assert(err, qt.Not(qt.IsNil))
        c.Assert(err.Error(), qt.Contains, "cannot disable default language")
 }
@@ -397,7 +397,7 @@ func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
 
        c := qt.New(t)
        // We need to use the OS fs for this.
-       cfg := viper.New()
+       cfg := config.New()
        fs := hugofs.NewFrom(hugofs.Os, cfg)
 
        workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym")
@@ -696,7 +696,7 @@ Single content.
        b.AssertFileContent("public/section-not-bundle/single/index.html", "Section Single", "|<p>Single content.</p>")
 }
 
-func newTestBundleSources(t testing.TB) (*hugofs.Fs, *viper.Viper) {
+func newTestBundleSources(t testing.TB) (*hugofs.Fs, config.Provider) {
        cfg, fs := newTestCfgBasic()
        c := qt.New(t)
 
@@ -863,7 +863,7 @@ Content for 은행.
        return fs, cfg
 }
 
-func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, *viper.Viper) {
+func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, config.Provider) {
        cfg, fs := newTestCfgBasic()
 
        workDir := "/work"
@@ -1319,7 +1319,7 @@ func TestPageBundlerHome(t *testing.T) {
        workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home")
        c.Assert(err, qt.IsNil)
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("workingDir", workDir)
        fs := hugofs.NewFrom(hugofs.Os, cfg)
 
index c7a42acdc0b7b777db65daca2a9ea5194f097493..45221af7141ce0a450c735bd6a5e781955a8f420 100644 (file)
@@ -130,7 +130,7 @@ func (c *pagesCollector) isCascadingEdit(dir contentDirKey) (bool, string) {
 
                section = s
 
-               maps.ToLower(pf.FrontMatter)
+               maps.PrepareParams(pf.FrontMatter)
                cascade1, ok := pf.FrontMatter["cascade"]
                hasCascade := n.p.bucket.cascade != nil && len(n.p.bucket.cascade) > 0
                if !ok {
index 59dbf0e00e9e6b7218184e47fc83622979d7d77b..d3ead4d1745d72fb4f55c9e504ee1b51f5f3f39a 100644 (file)
@@ -16,17 +16,16 @@ package paths
 import (
        "testing"
 
-       "github.com/gohugoio/hugo/langs"
-
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/hugofs"
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/langs"
 )
 
 func TestNewPaths(t *testing.T) {
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        fs := hugofs.NewMem(v)
 
        v.Set("languages", map[string]interface{}{
index 61b2ef6d5531a5391b45a2164acc4d181bad46c7..9e5c9c4a53613690f67a91840532d097f4dc878f 100644 (file)
@@ -19,14 +19,14 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/common/hexec"
 
        jww "github.com/spf13/jwalterweatherman"
 
        "github.com/gohugoio/hugo/htesting"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -91,7 +91,7 @@ class Car2 {
        var logBuf bytes.Buffer
        logger := loggers.NewBasicLoggerForWriter(jww.LevelInfo, &logBuf)
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workDir)
        v.Set("disableKinds", []string{"taxonomy", "term", "page"})
        b := newTestSitesBuilder(t).WithLogger(logger)
index 9ea1d85298c16f2aeb9cda23412ad99e6b52809e..a367237abef59af4f69149d0174ec0da64d1844b 100644 (file)
@@ -20,6 +20,8 @@ import (
        "math/rand"
        "os"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
 
        "path/filepath"
@@ -35,8 +37,6 @@ import (
 
        "github.com/gohugoio/hugo/htesting"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -65,7 +65,7 @@ func TestSCSSWithIncludePaths(t *testing.T) {
                        c.Assert(err, qt.IsNil)
                        defer clean()
 
-                       v := viper.New()
+                       v := config.New()
                        v.Set("workingDir", workDir)
                        b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
                        // Need to use OS fs for this.
@@ -130,7 +130,7 @@ func TestSCSSWithRegularCSSImport(t *testing.T) {
                        c.Assert(err, qt.IsNil)
                        defer clean()
 
-                       v := viper.New()
+                       v := config.New()
                        v.Set("workingDir", workDir)
                        b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
                        // Need to use OS fs for this.
@@ -230,7 +230,7 @@ func TestSCSSWithThemeOverrides(t *testing.T) {
                        theme := "mytheme"
                        themesDir := filepath.Join(workDir, "themes")
                        themeDirs := filepath.Join(themesDir, theme)
-                       v := viper.New()
+                       v := config.New()
                        v.Set("workingDir", workDir)
                        v.Set("theme", theme)
                        b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
@@ -345,7 +345,7 @@ func TestSCSSWithIncludePathsSass(t *testing.T) {
        c.Assert(err, qt.IsNil)
        defer clean1()
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workDir)
        v.Set("theme", "mytheme")
        b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
@@ -974,7 +974,7 @@ h1 {
 
        var logBuf bytes.Buffer
 
-       newTestBuilder := func(v *viper.Viper) *sitesBuilder {
+       newTestBuilder := func(v config.Provider) *sitesBuilder {
                v.Set("workingDir", workDir)
                v.Set("disableKinds", []string{"taxonomy", "term", "page"})
                logger := loggers.NewBasicLoggerForWriter(jww.LevelInfo, &logBuf)
@@ -997,7 +997,7 @@ Styles Content: Len: {{ len $styles.Content }}|
                return b
        }
 
-       b := newTestBuilder(viper.New())
+       b := newTestBuilder(config.New())
 
        cssDir := filepath.Join(workDir, "assets", "css", "components")
        b.Assert(os.MkdirAll(cssDir, 0777), qt.IsNil)
@@ -1049,7 +1049,7 @@ Styles Content: Len: 770878|
        build := func(s string, shouldFail bool) error {
                b.Assert(os.RemoveAll(filepath.Join(workDir, "public")), qt.IsNil)
 
-               v := viper.New()
+               v := config.New()
                v.Set("build", map[string]interface{}{
                        "useResourceCacheWhen": s,
                })
index 6bc39e97c74908ca4e73abbf76560f756599110b..2035c235ff649309a4626ace100c318e5468ffc3 100644 (file)
@@ -16,7 +16,7 @@ package hugolib
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 )
 
 const robotTxtTemplate = `User-agent: Googlebot
@@ -28,7 +28,7 @@ const robotTxtTemplate = `User-agent: Googlebot
 func TestRobotsTXTOutput(t *testing.T) {
        t.Parallel()
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("baseURL", "http://auth/bub/")
        cfg.Set("enableRobotsTXT", true)
 
index 51187a00332c857bfd27c2281e038a0ce4df06cb..7eb0d01def712dfb7f40a637c9a163092fff4c67 100644 (file)
@@ -20,11 +20,10 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/markup/asciidocext"
        "github.com/gohugoio/hugo/markup/rst"
 
-       "github.com/spf13/viper"
-
        "github.com/gohugoio/hugo/parser/pageparser"
        "github.com/gohugoio/hugo/resources/page"
 
@@ -1214,7 +1213,7 @@ title: "Hugo Rocks!"
 func TestShortcodeEmoji(t *testing.T) {
        t.Parallel()
 
-       v := viper.New()
+       v := config.New()
        v.Set("enableEmoji", true)
 
        builder := newTestSitesBuilder(t).WithViper(v)
@@ -1279,7 +1278,7 @@ func TestShortcodeRef(t *testing.T) {
                t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) {
                        t.Parallel()
 
-                       v := viper.New()
+                       v := config.New()
                        v.Set("baseURL", "https://example.org")
                        v.Set("blackfriday", map[string]interface{}{
                                "plainIDAnchors": plainIDAnchors,
index 12714892da9cc9d68adb66a73f77735dbc496550..9921dcc976edd29cbdaabfcc0fd740a7c5c59143 100644 (file)
@@ -77,7 +77,6 @@ import (
 
        "github.com/spf13/afero"
        "github.com/spf13/cast"
-       "github.com/spf13/viper"
 )
 
 // Site contains all the information relevant for constructing a static
@@ -501,9 +500,9 @@ But this also means that your site configuration may not do what you expect. If
        var relatedContentConfig related.Config
 
        if cfg.Language.IsSet("related") {
-               relatedContentConfig, err = related.DecodeConfig(cfg.Language.Get("related"))
+               relatedContentConfig, err = related.DecodeConfig(cfg.Language.GetParams("related"))
                if err != nil {
-                       return nil, err
+                       return nil, errors.Wrap(err, "failed to decode related config")
                }
        } else {
                relatedContentConfig = related.DefaultConfig
@@ -574,7 +573,8 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
                return nil, err
        }
 
-       if err = applyDeps(cfg, s); err != nil {
+       var l configLoader
+       if err = l.applyDeps(cfg, s); err != nil {
                return nil, err
        }
 
@@ -586,11 +586,11 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
 // Note: This is mainly used in single site tests.
 // TODO(bep) test refactor -- remove
 func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
-       v := viper.New()
-       if err := loadDefaultSettingsFor(v); err != nil {
+       l := configLoader{cfg: config.New()}
+       if err := l.applyConfigDefaults(); err != nil {
                return nil, err
        }
-       return newSiteForLang(langs.NewDefaultLanguage(v), withTemplate...)
+       return newSiteForLang(langs.NewDefaultLanguage(l.cfg), withTemplate...)
 }
 
 // NewEnglishSite creates a new site in English language.
@@ -598,11 +598,11 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (
 // Note: This is mainly used in single site tests.
 // TODO(bep) test refactor -- remove
 func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
-       v := viper.New()
-       if err := loadDefaultSettingsFor(v); err != nil {
+       l := configLoader{cfg: config.New()}
+       if err := l.applyConfigDefaults(); err != nil {
                return nil, err
        }
-       return newSiteForLang(langs.NewLanguage("en", v), withTemplate...)
+       return newSiteForLang(langs.NewLanguage("en", l.cfg), withTemplate...)
 }
 
 // newSiteForLang creates a new site in the given language.
@@ -1314,7 +1314,7 @@ func (s *Site) initializeSiteInfo() error {
                                return vvv
                        }
                default:
-                       m := cast.ToStringMapBool(v)
+                       m := maps.ToStringMapBool(v)
                        uglyURLs = func(p page.Page) bool {
                                return m[p.Section()]
                        }
index 1961dd06f68e6ff34b7ac853c3ceea11cbc37536..f3455f3692c6de19f9777f07693b99db4f2b597e 100644 (file)
@@ -19,13 +19,13 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/resources/page"
 
        "github.com/spf13/afero"
 
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/output"
-       "github.com/spf13/viper"
 )
 
 func TestSiteWithPageOutputs(t *testing.T) {
@@ -333,7 +333,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
                        page.KindSection: []string{"JSON"},
                }
 
-               cfg := viper.New()
+               cfg := config.New()
                cfg.Set("outputs", outputsConfig)
 
                outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@@ -358,7 +358,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
        // Issue #4528
        t.Run("Mixed case", func(t *testing.T) {
                c := qt.New(t)
-               cfg := viper.New()
+               cfg := config.New()
 
                outputsConfig := map[string]interface{}{
                        // Note that we in Hugo 0.53.0 renamed this Kind to "taxonomy",
@@ -380,7 +380,7 @@ func TestCreateSiteOutputFormatsInvalidConfig(t *testing.T) {
                page.KindHome: []string{"FOO", "JSON"},
        }
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("outputs", outputsConfig)
 
        _, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@@ -394,7 +394,7 @@ func TestCreateSiteOutputFormatsEmptyConfig(t *testing.T) {
                page.KindHome: []string{},
        }
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("outputs", outputsConfig)
 
        outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false)
@@ -409,7 +409,7 @@ func TestCreateSiteOutputFormatsCustomFormats(t *testing.T) {
                page.KindHome: []string{},
        }
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("outputs", outputsConfig)
 
        var (
index 365679a328b5c5b47481775ebd4c71b023ce372e..e259911643f462b5a50c4cd56326980f7cee4847 100644 (file)
@@ -23,10 +23,9 @@ import (
        "testing"
 
        "github.com/gobuffalo/flect"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/publisher"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/resources/page"
@@ -363,7 +362,7 @@ func TestMainSections(t *testing.T) {
        c := qt.New(t)
        for _, paramSet := range []bool{false, true} {
                c.Run(fmt.Sprintf("param-%t", paramSet), func(c *qt.C) {
-                       v := viper.New()
+                       v := config.New()
                        if paramSet {
                                v.Set("params", map[string]interface{}{
                                        "mainSections": []string{"a1", "a2"},
index f487cec67ac784a01a2b036d3ed178436a56d00f..abb6d32f9248c7519bcce9612d3f6e6fcd808c81 100644 (file)
@@ -19,20 +19,19 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/identity"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/tpl"
-
-       "github.com/spf13/viper"
 )
 
 func TestTemplateLookupOrder(t *testing.T) {
        var (
                fs  *hugofs.Fs
-               cfg *viper.Viper
+               cfg config.Provider
                th  testHelper
        )
 
index 09988f9726d9bcb9727ecd983652fb725b09964e..451022e5cb35ff0cf696861035088c4e4af6c593 100644 (file)
@@ -30,6 +30,7 @@ import (
 
        "github.com/fsnotify/fsnotify"
        "github.com/gohugoio/hugo/common/herrors"
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/resources/page"
@@ -39,7 +40,6 @@ import (
 
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/tpl"
-       "github.com/spf13/viper"
 
        "github.com/gohugoio/hugo/resources/resource"
 
@@ -83,7 +83,7 @@ type sitesBuilder struct {
        // Default toml
        configFormat  string
        configFileSet bool
-       viperSet      bool
+       configSet      bool
 
        // Default is empty.
        // TODO(bep) revisit this and consider always setting it to something.
@@ -111,7 +111,7 @@ type filenameContent struct {
 }
 
 func newTestSitesBuilder(t testing.TB) *sitesBuilder {
-       v := viper.New()
+       v := config.New()
        fs := hugofs.NewMem(v)
 
        litterOptions := litter.Options{
@@ -140,7 +140,7 @@ func newTestSitesBuilderFromDepsCfg(t testing.TB, d deps.DepsCfg) *sitesBuilder
 
        b.WithWorkingDir(workingDir)
 
-       return b.WithViper(d.Cfg.(*viper.Viper))
+       return b.WithViper(d.Cfg.(config.Provider))
 }
 
 func (s *sitesBuilder) Running() *sitesBuilder {
@@ -186,26 +186,26 @@ func (s *sitesBuilder) WithConfigTemplate(data interface{}, format, configTempla
        return s.WithConfigFile(format, b.String())
 }
 
-func (s *sitesBuilder) WithViper(v *viper.Viper) *sitesBuilder {
+func (s *sitesBuilder) WithViper(v config.Provider) *sitesBuilder {
        s.T.Helper()
        if s.configFileSet {
                s.T.Fatal("WithViper: use Viper or config.toml, not both")
        }
        defer func() {
-               s.viperSet = true
+               s.configSet = true
        }()
 
        // Write to a config file to make sure the tests follow the same code path.
        var buff bytes.Buffer
-       m := v.AllSettings()
+       m := v.Get("").(maps.Params)
        s.Assert(parser.InterfaceToConfig(m, metadecoders.TOML, &buff), qt.IsNil)
        return s.WithConfigFile("toml", buff.String())
 }
 
 func (s *sitesBuilder) WithConfigFile(format, conf string) *sitesBuilder {
        s.T.Helper()
-       if s.viperSet {
-               s.T.Fatal("WithConfigFile: use Viper or config.toml, not both")
+       if s.configSet {
+               s.T.Fatal("WithConfigFile: use config.Config or config.toml, not both")
        }
        s.configFileSet = true
        filename := s.absFilename("config." + format)
@@ -845,14 +845,14 @@ func (th testHelper) replaceDefaultContentLanguageValue(value string) string {
        return value
 }
 
-func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error) (*viper.Viper, error) {
+func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error) (config.Provider, error) {
        v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs}, withConfig...)
        return v, err
 }
 
-func newTestCfgBasic() (*viper.Viper, *hugofs.Fs) {
+func newTestCfgBasic() (config.Provider, *hugofs.Fs) {
        mm := afero.NewMemMapFs()
-       v := viper.New()
+       v := config.New()
        v.Set("defaultContentLanguageInSubdir", true)
 
        fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v)
@@ -860,7 +860,7 @@ func newTestCfgBasic() (*viper.Viper, *hugofs.Fs) {
        return v, fs
 }
 
-func newTestCfg(withConfig ...func(cfg config.Provider) error) (*viper.Viper, *hugofs.Fs) {
+func newTestCfg(withConfig ...func(cfg config.Provider) error) (config.Provider, *hugofs.Fs) {
        mm := afero.NewMemMapFs()
 
        v, err := loadTestConfig(mm, func(cfg config.Provider) error {
index 3b1da89ecc2259119d2c29f34cf1ffacfe0121f4..fe4ed9d14d756a182b10265f4899d3eb1466a95f 100644 (file)
@@ -43,13 +43,13 @@ func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesC
 
        var languages map[string]interface{}
 
-       languagesFromConfig := cfg.GetStringMap("languages")
+       languagesFromConfig := cfg.GetParams("languages")
        disableLanguages := cfg.GetStringSlice("disableLanguages")
 
        if len(disableLanguages) == 0 {
                languages = languagesFromConfig
        } else {
-               languages = make(map[string]interface{})
+               languages = make(maps.Params)
                for k, v := range languagesFromConfig {
                        for _, disabled := range disableLanguages {
                                if disabled == defaultLang {
@@ -57,7 +57,7 @@ func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesC
                                }
 
                                if strings.EqualFold(k, disabled) {
-                                       v.(map[string]interface{})["disabled"] = true
+                                       v.(maps.Params)["disabled"] = true
                                        break
                                }
                        }
@@ -193,7 +193,7 @@ func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (Languages
                        case "params":
                                m := maps.ToStringMap(v)
                                // Needed for case insensitive fetching of params values
-                               maps.ToLower(m)
+                               maps.PrepareParams(m)
                                for k, vv := range m {
                                        language.SetParam(k, vv)
                                }
index 18c12201074cfaca0e1b8ee37a4e5b12d9ad4139..be20ca3c86fcd9b7c82052800963e51bee3c0b47 100644 (file)
@@ -28,7 +28,6 @@ import (
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 
        "github.com/gohugoio/hugo/deps"
 
@@ -500,9 +499,9 @@ func newDepsConfig(tp *TranslationProvider, cfg config.Provider, fs *hugofs.Fs)
        }
 }
 
-func getConfig() *viper.Viper {
-       v := viper.New()
-       v.SetDefault("defaultContentLanguage", "en")
+func getConfig() config.Provider {
+       v := config.New()
+       v.Set("defaultContentLanguage", "en")
        v.Set("contentDir", "content")
        v.Set("dataDir", "data")
        v.Set("i18nDir", "i18n")
index bdfde573cbbcd4cfff8dfcf90963642f3ff5e2f8..a86d82ba3f3aae5e8e8acf06df8b73046291369e 100644 (file)
@@ -20,7 +20,6 @@ import (
 
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/config"
-       "github.com/spf13/cast"
 )
 
 // These are the settings that should only be looked up in the global Viper
@@ -55,18 +54,20 @@ type Language struct {
        // absolute directory reference. It is what we get.
        ContentDir string
 
+       // Global config.
        Cfg config.Provider
 
+       // Language specific config.
+       LocalCfg config.Provider
+
+       // Composite config.
+       config.Provider
+
        // These are params declared in the [params] section of the language merged with the
        // site's params, the most specific (language) wins on duplicate keys.
        params    map[string]interface{}
        paramsMu  sync.Mutex
        paramsSet bool
-
-       // These are config values, i.e. the settings declared outside of the [params] section of the language.
-       // This is the map Hugo looks in when looking for configuration values (baseURL etc.).
-       // Values in this map can also be fetched from the params map above.
-       settings map[string]interface{}
 }
 
 func (l *Language) String() string {
@@ -81,9 +82,12 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
        for k, v := range cfg.GetStringMap("params") {
                params[k] = v
        }
-       maps.ToLower(params)
+       maps.PrepareParams(params)
+
+       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
 }
 
@@ -133,7 +137,7 @@ func (l *Language) Params() maps.Params {
        l.paramsMu.Lock()
        defer l.paramsMu.Unlock()
        if !l.paramsSet {
-               maps.ToLower(l.params)
+               maps.PrepareParams(l.params)
                l.paramsSet = true
        }
        return l.params
@@ -183,42 +187,6 @@ func (l *Language) SetParam(k string, v interface{}) {
        l.params[k] = v
 }
 
-// GetBool returns the value associated with the key as a boolean.
-func (l *Language) GetBool(key string) bool { return cast.ToBool(l.Get(key)) }
-
-// GetString returns the value associated with the key as a string.
-func (l *Language) GetString(key string) string { return cast.ToString(l.Get(key)) }
-
-// GetInt returns the value associated with the key as an int.
-func (l *Language) GetInt(key string) int { return cast.ToInt(l.Get(key)) }
-
-// GetStringMap returns the value associated with the key as a map of interfaces.
-func (l *Language) GetStringMap(key string) map[string]interface{} {
-       return maps.ToStringMap(l.Get(key))
-}
-
-// GetStringMapString returns the value associated with the key as a map of strings.
-func (l *Language) GetStringMapString(key string) map[string]string {
-       return cast.ToStringMapString(l.Get(key))
-}
-
-// GetStringSlice returns the value associated with the key as a slice of strings.
-func (l *Language) GetStringSlice(key string) []string {
-       return cast.ToStringSlice(l.Get(key))
-}
-
-// Get returns a value associated with the key relying on specified language.
-// Get is case-insensitive for a key.
-//
-// Get returns an interface. For a specific value use one of the Get____ methods.
-func (l *Language) Get(key string) interface{} {
-       local := l.GetLocal(key)
-       if local != nil {
-               return local
-       }
-       return l.Cfg.Get(key)
-}
-
 // GetLocal gets a configuration value set on language level. It will
 // not fall back to any global value.
 // It will return nil if a value with the given key cannot be found.
@@ -228,31 +196,29 @@ func (l *Language) GetLocal(key string) interface{} {
        }
        key = strings.ToLower(key)
        if !globalOnlySettings[key] {
-               if v, ok := l.settings[key]; ok {
-                       return v
-               }
+               return l.LocalCfg.Get(key)
        }
        return nil
 }
 
-// Set sets the value for the key in the language's params.
-func (l *Language) Set(key string, value interface{}) {
-       if l == nil {
-               panic("language not set")
+func (l *Language) Set(k string, v interface{}) {
+       k = strings.ToLower(k)
+       if globalOnlySettings[k] {
+               return
        }
-       key = strings.ToLower(key)
-       l.settings[key] = value
+       l.Provider.Set(k, v)
+}
+
+// Merge is currently not supported for Language.
+func (l *Language) Merge(key string, value interface{}) {
+       panic("Not supported")
 }
 
 // IsSet checks whether the key is set in the language or the related config store.
 func (l *Language) IsSet(key string) bool {
-       key = strings.ToLower(key)
-
        key = strings.ToLower(key)
        if !globalOnlySettings[key] {
-               if _, ok := l.settings[key]; ok {
-                       return true
-               }
+               return l.Provider.IsSet(key)
        }
        return l.Cfg.IsSet(key)
 }
index 97abe77cc1521521409295b4ea336d7408638ae2..8557d781afb9ac8a31e3452637adc9387070c243 100644 (file)
@@ -16,13 +16,14 @@ package langs
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        qt "github.com/frankban/quicktest"
-       "github.com/spf13/viper"
 )
 
 func TestGetGlobalOnlySetting(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        v.Set("defaultContentLanguageInSubdir", true)
        v.Set("contentDir", "content")
        v.Set("paginatePath", "page")
@@ -37,7 +38,7 @@ func TestGetGlobalOnlySetting(t *testing.T) {
 func TestLanguageParams(t *testing.T) {
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("p1", "p1cfg")
        v.Set("contentDir", "content")
 
index 4c183f7bb9b171d38ef3f0ece7dc2ffee7740409..14110bb045665917f9a2aaf0a919131b9d148156 100644 (file)
@@ -22,17 +22,17 @@ import (
        "testing"
 
        "github.com/gohugoio/hugo/common/loggers"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/markup/converter"
        "github.com/gohugoio/hugo/markup/markup_config"
        "github.com/gohugoio/hugo/markup/tableofcontents"
-       "github.com/spf13/viper"
 
        qt "github.com/frankban/quicktest"
 )
 
 func TestAsciidoctorDefaultArgs(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
 
        p, err := Provider.New(
@@ -57,7 +57,7 @@ func TestAsciidoctorDefaultArgs(t *testing.T) {
 
 func TestAsciidoctorNonDefaultArgs(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.Backend = "manpage"
        mconf.AsciidocExt.NoHeaderOrFooter = false
@@ -88,7 +88,7 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
 
 func TestAsciidoctorDisallowedArgs(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.Backend = "disallowed-backend"
        mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
@@ -117,7 +117,7 @@ func TestAsciidoctorDisallowedArgs(t *testing.T) {
 
 func TestAsciidoctorArbitraryExtension(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
        p, err := Provider.New(
@@ -142,7 +142,7 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
 
 func TestAsciidoctorDisallowedExtension(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        for _, disallowedExtension := range []string{
                `foo-bar//`,
                `foo-bar\\ `,
@@ -177,7 +177,7 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
 
 func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.WorkingFolderCurrent = true
        mconf.AsciidocExt.Trace = false
@@ -208,7 +208,7 @@ func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
 
 func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.NoHeaderOrFooter = true
        mconf.AsciidocExt.Extensions = []string{"asciidoctor-html5s", "asciidoctor-diagram"}
@@ -247,7 +247,7 @@ func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
 
 func TestAsciidoctorAttributes(t *testing.T) {
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        mconf := markup_config.Default
        mconf.AsciidocExt.Attributes = map[string]string{"my-base-url": "https://gohugo.io/", "my-attribute-name": "my value"}
        mconf.AsciidocExt.Trace = false
index 414905a76ef217aaf65c0bed59e844a1d9f37aec..5b1c1a71fe5b7db867f4be449e021136681f6c9e 100644 (file)
@@ -16,7 +16,7 @@ package blackfriday
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        "github.com/gohugoio/hugo/markup/converter"
 
@@ -140,7 +140,7 @@ func TestGetAllFlags(t *testing.T) {
 func TestConvert(t *testing.T) {
        c := qt.New(t)
        p, err := Provider.New(converter.ProviderConfig{
-               Cfg: viper.New(),
+               Cfg: config.New(),
        })
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{})
@@ -153,7 +153,7 @@ func TestConvert(t *testing.T) {
 func TestGetHTMLRendererAnchors(t *testing.T) {
        c := qt.New(t)
        p, err := Provider.New(converter.ProviderConfig{
-               Cfg: viper.New(),
+               Cfg: config.New(),
        })
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{
index 3c374762273080c9f6b4d42e927060402dca15be..ab92ecf36fa41aa6417b07fccb8e9b43415c944c 100644 (file)
@@ -17,16 +17,15 @@ package highlight
 import (
        "testing"
 
-       "github.com/spf13/viper"
-
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
 )
 
 func TestConfig(t *testing.T) {
        c := qt.New(t)
 
        c.Run("applyLegacyConfig", func(c *qt.C) {
-               v := viper.New()
+               v := config.New()
                v.Set("pygmentsStyle", "hugo")
                v.Set("pygmentsUseClasses", false)
                v.Set("pygmentsCodeFences", false)
index a3562cd241f83e21442988d2d1d07c9f20c37af0..d4a10170960a6c132ccdf7b36d5b881be279fe19 100644 (file)
@@ -24,7 +24,6 @@ import (
        "github.com/gohugoio/hugo/markup/tableofcontents"
        "github.com/gohugoio/hugo/parser"
        "github.com/mitchellh/mapstructure"
-       "github.com/spf13/cast"
 )
 
 type Config struct {
@@ -73,7 +72,7 @@ func normalizeConfig(m map[string]interface{}) {
        if err != nil {
                return
        }
-       vm := cast.ToStringMap(v)
+       vm := maps.ToStringMap(v)
        // Changed from a bool in 0.81.0
        if vv, found := vm["attribute"]; found {
                if vvb, ok := vv.(bool); ok {
index 4a1f1232b7d0886921cb1d046030c7d58f820398..08d7b5995732d592cbd3c43ff363abcb5d14bb8e 100644 (file)
@@ -16,7 +16,7 @@ package markup_config
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        qt "github.com/frankban/quicktest"
 )
@@ -26,7 +26,7 @@ func TestConfig(t *testing.T) {
 
        c.Run("Decode", func(c *qt.C) {
                c.Parallel()
-               v := viper.New()
+               v := config.New()
 
                v.Set("markup", map[string]interface{}{
                        "goldmark": map[string]interface{}{
@@ -55,7 +55,7 @@ func TestConfig(t *testing.T) {
 
        c.Run("legacy", func(c *qt.C) {
                c.Parallel()
-               v := viper.New()
+               v := config.New()
 
                v.Set("blackfriday", map[string]interface{}{
                        "angledQuotes": true,
index 6e7fe205925da71c31903e6ab69cf89b131cf1a6..71d39075dc72f1045b4f0ebdd51a582fb7f1fb52 100644 (file)
@@ -16,17 +16,15 @@ package markup
 import (
        "testing"
 
-       "github.com/spf13/viper"
-
-       "github.com/gohugoio/hugo/markup/converter"
-
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
+       "github.com/gohugoio/hugo/markup/converter"
 )
 
 func TestConverterRegistry(t *testing.T) {
        c := qt.New(t)
 
-       r, err := NewConverterProvider(converter.ProviderConfig{Cfg: viper.New()})
+       r, err := NewConverterProvider(converter.ProviderConfig{Cfg: config.New()})
 
        c.Assert(err, qt.IsNil)
        c.Assert("goldmark", qt.Equals, r.GetMarkupConfig().DefaultMarkdownHandler)
index 01d3e8cbb243c00eaa8fb62a51d9f5c8113244c6..414e023ab0bd131aa368ce588394dc67b5beb106 100644 (file)
@@ -16,7 +16,7 @@ package mmark
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        "github.com/gohugoio/hugo/common/loggers"
 
@@ -62,7 +62,7 @@ func TestGetMmarkExtensions(t *testing.T) {
 
 func TestConvert(t *testing.T) {
        c := qt.New(t)
-       p, err := Provider.New(converter.ProviderConfig{Cfg: viper.New(), Logger: loggers.NewErrorLogger()})
+       p, err := Provider.New(converter.ProviderConfig{Cfg: config.New(), Logger: loggers.NewErrorLogger()})
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{})
        c.Assert(err, qt.IsNil)
index 96c388457ae9188ead4b4fdfdfd5749c92708bf0..e3676fc34cc70bb85edfc8d2336c6155f18832a5 100644 (file)
@@ -16,8 +16,9 @@ package org
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/common/loggers"
-       "github.com/spf13/viper"
 
        "github.com/gohugoio/hugo/markup/converter"
 
@@ -28,7 +29,7 @@ func TestConvert(t *testing.T) {
        c := qt.New(t)
        p, err := Provider.New(converter.ProviderConfig{
                Logger: loggers.NewErrorLogger(),
-               Cfg:    viper.New(),
+               Cfg:    config.New(),
        })
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{})
index 21a91524dd1e772370e5de047e50922e8b518812..817ea1ba81d14d2f518f36e5112b65a8dbb27c1e 100644 (file)
@@ -385,8 +385,8 @@ func DecodeTypes(mms ...map[string]interface{}) (Types, error) {
                                return m, err
                        }
 
-                       vm := v.(map[string]interface{})
-                       maps.ToLower(vm)
+                       vm := maps.ToStringMap(v)
+                       maps.PrepareParams(vm)
                        _, delimiterSet := vm["delimiter"]
                        _, suffixSet := vm["suffix"]
 
index fc707ce3778e67e60800e53cfc29c175f7ee545b..675e5d2b87a901fb92f4d529ec8e23c598d8113f 100644 (file)
@@ -99,10 +99,10 @@ func decodeConfig(cfg config.Provider) (conf minifyConfig, err error) {
 
        // Handle upstream renames.
        if td, found := m["tdewolff"]; found {
-               tdm := cast.ToStringMap(td)
+               tdm := maps.ToStringMap(td)
                for _, key := range []string{"css", "svg"} {
                        if v, found := tdm[key]; found {
-                               vm := cast.ToStringMap(v)
+                               vm := maps.ToStringMap(v)
                                if vv, found := vm["decimal"]; found {
                                        vvi := cast.ToInt(vv)
                                        if vvi > 0 {
index 8518638425dece08b4a0bc168c557736a085cdff..fca56b7f103b4f0d98fef8dba45083672f334b92 100644 (file)
@@ -16,14 +16,14 @@ package minifiers
 import (
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        qt "github.com/frankban/quicktest"
 )
 
 func TestConfig(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
 
        v.Set("minify", map[string]interface{}{
                "disablexml": true,
@@ -53,7 +53,7 @@ func TestConfig(t *testing.T) {
 
 func TestConfigLegacy(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
 
        // This was a bool < Hugo v0.58.
        v.Set("minify", true)
index 97fe4e465eb07afcb933ef18d8f1b624e577d606..37e01742053f5d0a6ab78cf57b8ce846959ed1eb 100644 (file)
@@ -19,16 +19,15 @@ import (
        "strings"
        "testing"
 
-       "github.com/gohugoio/hugo/media"
-
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
+       "github.com/gohugoio/hugo/media"
        "github.com/gohugoio/hugo/output"
-       "github.com/spf13/viper"
 )
 
 func TestNew(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
 
        var rawJS string
@@ -76,7 +75,7 @@ func TestNew(t *testing.T) {
 
 func TestConfigureMinify(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        v.Set("minify", map[string]interface{}{
                "disablexml": true,
                "tdewolff": map[string]interface{}{
@@ -110,7 +109,7 @@ func TestConfigureMinify(t *testing.T) {
 
 func TestJSONRoundTrip(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
 
        for _, test := range []string{`{
@@ -148,7 +147,7 @@ func TestJSONRoundTrip(t *testing.T) {
 
 func TestBugs(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
 
        for _, test := range []struct {
@@ -171,7 +170,7 @@ func TestBugs(t *testing.T) {
 // Renamed to Precision in v2.7.0. Check that we support both.
 func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
        c := qt.New(t)
-       v := viper.New()
+       v := config.New()
        v.Set("minify", map[string]interface{}{
                "disablexml": true,
                "tdewolff": map[string]interface{}{
index db79f434e1bcb08b7100cdacbc400fcac91b8975..163eda74a57ef139bd1690d779b00ff7d9ccd34c 100644 (file)
@@ -424,7 +424,7 @@ func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
                if err != nil {
                        c.logger.Warnf("Failed to read module config for %q in %q: %s", tc.Path(), themeTOML, err)
                } else {
-                       maps.ToLower(themeCfg)
+                       maps.PrepareParams(themeCfg)
                }
        }
 
index fe5d198323a08b6fc4889e5b6ad4cc712df87aeb..a073b3d8fc14ee900e61ca22a079d6156d27e771 100644 (file)
@@ -29,7 +29,7 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/spf13/afero"
 
-       "github.com/spf13/cast"
+       "github.com/gohugoio/hugo/common/maps"
 
        "github.com/gohugoio/hugo/helpers"
 )
@@ -122,7 +122,7 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error {
        var commentsm map[string]interface{}
        comments, found := b.originalPackageJSON["comments"]
        if found {
-               commentsm = cast.ToStringMap(comments)
+               commentsm = maps.ToStringMap(comments)
        } else {
                commentsm = make(map[string]interface{})
        }
@@ -205,7 +205,7 @@ func (b *packageBuilder) addm(source string, m map[string]interface{}) {
        // These packages will be added by order of import (project, module1, module2...),
        // so that should at least give the project control over the situation.
        if devDeps, found := m[devDependenciesKey]; found {
-               mm := cast.ToStringMapString(devDeps)
+               mm := maps.ToStringMapString(devDeps)
                for k, v := range mm {
                        if _, added := b.devDependencies[k]; !added {
                                b.devDependencies[k] = v
@@ -215,7 +215,7 @@ func (b *packageBuilder) addm(source string, m map[string]interface{}) {
        }
 
        if deps, found := m[dependenciesKey]; found {
-               mm := cast.ToStringMapString(deps)
+               mm := maps.ToStringMapString(deps)
                for k, v := range mm {
                        if _, added := b.dependencies[k]; !added {
                                b.dependencies[k] = v
index 9a081121a214700984847adb0220a2fa6c177feb..a52f43c8b68528b7eecf9bcf8edffe7c72ae8487 100644 (file)
@@ -368,7 +368,11 @@ func decode(mediaTypes media.Types, input interface{}, output *Format) error {
                return err
        }
 
-       return decoder.Decode(input)
+       if err = decoder.Decode(input); err != nil {
+               return errors.Wrap(err, "failed to decode output format configuration")
+       }
+
+       return nil
 
 }
 
index 2400b161265ac36c75040ee0e519991c67d3c11a..9ede8009588572f629b3ff05510f71331dafcecd 100644 (file)
@@ -22,12 +22,13 @@ import (
        "testing"
        "time"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/media"
        "github.com/gohugoio/hugo/minifiers"
        "github.com/gohugoio/hugo/output"
 
        qt "github.com/frankban/quicktest"
-       "github.com/spf13/viper"
 )
 
 func TestClassCollector(t *testing.T) {
@@ -138,7 +139,7 @@ func TestClassCollector(t *testing.T) {
                                        if skipMinifyTest[test.name] {
                                                c.Skip("skip minify test")
                                        }
-                                       v := viper.New()
+                                       v := config.New()
                                        m, _ := minifiers.New(media.DefaultTypes, output.DefaultFormats, v)
                                        m.Minify(media.HTMLType, w, strings.NewReader(test.html))
 
index 2e0ea295dfc2378e1e1c9bfa68e590c28b9f2526..329a8c998065e512579178fc0c03b58841c47190 100644 (file)
@@ -22,6 +22,8 @@ import (
        "strings"
        "time"
 
+       "github.com/gohugoio/hugo/common/maps"
+
        "github.com/gohugoio/hugo/common/types"
        "github.com/mitchellh/mapstructure"
 )
@@ -404,16 +406,11 @@ func norm(num, min, max int) int {
 }
 
 // DecodeConfig decodes a slice of map into Config.
-func DecodeConfig(in interface{}) (Config, error) {
-       if in == nil {
+func DecodeConfig(m maps.Params) (Config, error) {
+       if m == nil {
                return Config{}, errors.New("no related config provided")
        }
 
-       m, ok := in.(map[string]interface{})
-       if !ok {
-               return Config{}, fmt.Errorf("expected map[string]interface {} got %T", in)
-       }
-
        if len(m) == 0 {
                return Config{}, errors.New("empty related config provided")
        }
index 7c4c77a918ec85f1c36c28b64e911e1d51769b47..6e3833b0f9747414b241b74df29fd9941891a57a 100644 (file)
@@ -18,8 +18,9 @@ import (
        "testing"
        "time"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/resources/resource"
-       "github.com/spf13/viper"
 
        qt "github.com/frankban/quicktest"
 )
@@ -72,7 +73,7 @@ func newTestFd() *FrontMatterDescriptor {
 func TestFrontMatterNewConfig(t *testing.T) {
        c := qt.New(t)
 
-       cfg := viper.New()
+       cfg := config.New()
 
        cfg.Set("frontmatter", map[string]interface{}{
                "date":        []string{"publishDate", "LastMod"},
@@ -89,7 +90,7 @@ func TestFrontMatterNewConfig(t *testing.T) {
        c.Assert(fc.publishDate, qt.DeepEquals, []string{"date"})
 
        // Default
-       cfg = viper.New()
+       cfg = config.New()
        fc, err = newFrontmatterConfig(cfg)
        c.Assert(err, qt.IsNil)
        c.Assert(fc.date, qt.DeepEquals, []string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"})
@@ -117,7 +118,7 @@ func TestFrontMatterDatesHandlers(t *testing.T) {
 
        for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
 
-               cfg := viper.New()
+               cfg := config.New()
 
                cfg.Set("frontmatter", map[string]interface{}{
                        "date": []string{handlerID, "date"},
@@ -157,7 +158,7 @@ func TestFrontMatterDatesCustomConfig(t *testing.T) {
 
        c := qt.New(t)
 
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("frontmatter", map[string]interface{}{
                "date":        []string{"mydate"},
                "lastmod":     []string{"publishdate"},
@@ -204,7 +205,7 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
 
        c := qt.New(t)
 
-       cfg := viper.New()
+       cfg := config.New()
 
        cfg.Set("frontmatter", map[string]interface{}{
                "date":        []string{"mydate", ":default"},
index 8d4f857d749b07bae78b24404ec9bc177954b146..07ad6233b56838d5d09a912d32c2c81c786737e3 100644 (file)
@@ -18,7 +18,7 @@ import (
        "html/template"
        "testing"
 
-       "github.com/spf13/viper"
+       "github.com/gohugoio/hugo/config"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/output"
@@ -196,7 +196,7 @@ func doTestPagerNoPages(t *testing.T, paginator *Paginator) {
 func TestPaginationURLFactory(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("paginatePath", "zoo")
 
        for _, uglyURLs := range []bool{false, true} {
index 187930461267802c06f02dbfe08618798df2c275..1a0a6586aa85c1ffd93ad1667667d66f2a72fb09 100644 (file)
@@ -29,7 +29,7 @@ import (
        "github.com/bep/gitmap"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/resources/resource"
-       "github.com/spf13/viper"
+       
 
        "github.com/gohugoio/hugo/navigation"
 
@@ -69,7 +69,7 @@ func newTestPageWithFile(filename string) *testPage {
 }
 
 func newTestPathSpec() *helpers.PathSpec {
-       return newTestPathSpecFor(viper.New())
+       return newTestPathSpecFor(config.New())
 }
 
 func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {
index a0c232c67a911c4cf436106fa322015983be1038..0dd0945e39bfbae6ff42390935421de10cbbae45 100644 (file)
@@ -130,7 +130,7 @@ func AssignMetadata(metadata []map[string]interface{}, resources ...resource.Res
                                if found {
                                        m := maps.ToStringMap(params)
                                        // Needed for case insensitive fetching of params values
-                                       maps.ToLower(m)
+                                       maps.PrepareParams(m)
                                        ma.updateParams(m)
                                }
                        }
index 8eacf7da4e627972e8573cfc67b63199bc4ea048..21333eccb935b06bd8a6226444cb2663a6ab6e18 100644 (file)
@@ -17,17 +17,17 @@ import (
        "path/filepath"
 
        "github.com/gohugoio/hugo/cache/filecache"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/media"
        "github.com/gohugoio/hugo/output"
        "github.com/gohugoio/hugo/resources"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func NewTestResourceSpec() (*resources.Spec, error) {
-       cfg := viper.New()
+       cfg := config.New()
        cfg.Set("baseURL", "https://example.org")
        cfg.Set("publishDir", "public")
 
index ceed22f34c21f204574fb28573a8edd154b2de68..84f571f179863ce2b9d779656b099dd1c9b56fc7 100644 (file)
@@ -20,9 +20,9 @@ import (
        "path/filepath"
        "strings"
 
-       "github.com/spf13/afero"
-
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/pkg/errors"
+       "github.com/spf13/afero"
 
        "github.com/evanw/esbuild/pkg/api"
 
@@ -30,7 +30,6 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/media"
        "github.com/mitchellh/mapstructure"
-       "github.com/spf13/cast"
 )
 
 const (
@@ -348,7 +347,7 @@ func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) {
 
        var defines map[string]string
        if opts.Defines != nil {
-               defines = cast.ToStringMapString(opts.Defines)
+               defines = maps.ToStringMapString(opts.Defines)
        }
 
        // By default we only need to specify outDir and no outFile
index 32e4213ea7c319869725a5a62263cb167b53ad5e..12dc8efe8b97982cbb0d37fda3489155cac7d7a7 100644 (file)
@@ -10,6 +10,7 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/modules"
 
@@ -22,7 +23,6 @@ import (
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/resources/resource"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 type specDescriptor struct {
@@ -31,8 +31,8 @@ type specDescriptor struct {
        fs      afero.Fs
 }
 
-func createTestCfg() *viper.Viper {
-       cfg := viper.New()
+func createTestCfg() config.Provider {
+       cfg := config.New()
        cfg.Set("resourceDir", "resources")
        cfg.Set("contentDir", "content")
        cfg.Set("dataDir", "data")
index 5dc1879782e069c468295967069c3dbc983be325..e6dc9ce168cda5a04e3a6df2ecb20d679781805d 100644 (file)
@@ -19,6 +19,8 @@ import (
        "runtime"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/modules"
 
        "github.com/gohugoio/hugo/langs"
@@ -28,8 +30,6 @@ import (
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugofs"
-
-       "github.com/spf13/viper"
 )
 
 func TestEmptySourceFilesystem(t *testing.T) {
@@ -76,8 +76,8 @@ func TestUnicodeNorm(t *testing.T) {
        }
 }
 
-func newTestConfig() *viper.Viper {
-       v := viper.New()
+func newTestConfig() config.Provider {
+       v := config.New()
        v.Set("contentDir", "content")
        v.Set("dataDir", "data")
        v.Set("i18nDir", "i18n")
index 035db1740f48a5aee588d85b004b649fbc3f2a9d..a3cc26de5e092ee5088c045bd978af7fecb9196b 100644 (file)
@@ -15,18 +15,18 @@ package cast
 
 import (
        "github.com/gohugoio/hugo/common/loggers"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/docshelper"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/tpl/internal"
-       "github.com/spf13/viper"
 )
 
 // This file provides documentation support and is randomly put into this package.
 func init() {
        docsProvider := func() docshelper.DocProvider {
                d := &deps.Deps{
-                       Cfg:                 viper.New(),
+                       Cfg:                 config.New(),
                        Log:                 loggers.NewErrorLogger(),
                        BuildStartListeners: &deps.Listeners{},
                        Site:                page.NewDummyHugoSite(newTestConfig()),
@@ -46,8 +46,8 @@ func init() {
        docshelper.AddDocProviderFunc(docsProvider)
 }
 
-func newTestConfig() *viper.Viper {
-       v := viper.New()
+func newTestConfig() config.Provider {
+       v := config.New()
        v.Set("contentDir", "content")
        return v
 }
index 1e0569751025c905d5b878b609f3347d6a6b5aa8..3faf46930bc3b71822e072696c7baed1c5fe4b54 100644 (file)
@@ -32,7 +32,7 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/langs"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
+       
 )
 
 type tstNoStringer struct{}
@@ -986,7 +986,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
 }
 
 func newTestNs() *Namespace {
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        return New(newDeps(v))
 }
index fedce8e5c432e3d64635b8d5e822aae3c3dfdb95..9174d42a6b29b160d554b4ab2408c07cd41d9412 100644 (file)
@@ -17,10 +17,10 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/htesting/hqt"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/tpl/internal"
-       "github.com/spf13/viper"
 )
 
 func TestInit(t *testing.T) {
@@ -28,7 +28,7 @@ func TestInit(t *testing.T) {
        var found bool
        var ns *internal.TemplateFuncsNamespace
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        langs.LoadLanguageSettings(v, nil)
 
index 18061c052889c0c9c9aa369fa17bc5704bd60b7c..1bf6d769fa4126e0bedcc73bf52b9bc55f4e0c54 100644 (file)
@@ -34,12 +34,12 @@ import (
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/langs"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
+       
 )
 
 func TestScpGetLocal(t *testing.T) {
        t.Parallel()
-       v := viper.New()
+       v := config.New()
        fs := hugofs.NewMem(v)
        ps := helpers.FilePathSeparator
 
@@ -144,7 +144,7 @@ func TestScpGetRemoteParallel(t *testing.T) {
        c.Assert(err, qt.IsNil)
 
        for _, ignoreCache := range []bool{false} {
-               cfg := viper.New()
+               cfg := config.New()
                cfg.Set("ignoreCache", ignoreCache)
                cfg.Set("contentDir", "content")
 
@@ -223,7 +223,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
 }
 
 func newTestNs() *Namespace {
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        return New(newDeps(v))
 }
index 09e2b94bcce3f9474c37b3e1e95408f4147beac0..ce25151a0d6f1872f5fbcd773c6e1c3bb4bef07d 100644 (file)
@@ -20,6 +20,7 @@ import (
        "errors"
        "html/template"
 
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/spf13/cast"
 )
 
@@ -71,7 +72,7 @@ func (ns *Namespace) Jsonify(args ...interface{}) (template.HTML, error) {
        case 2:
                var opts map[string]string
 
-               opts, err = cast.ToStringMapStringE(args[0])
+               opts, err = maps.ToStringMapStringE(args[0])
                if err != nil {
                        break
                }
index c94a883fd21dfe8d996aa49a2ef2494123298b9e..9b1b14be43e4095a7e6d17fd32feb0dd604d5797 100644 (file)
@@ -16,20 +16,21 @@ package hugo
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/htesting/hqt"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/tpl/internal"
-       "github.com/spf13/viper"
 )
 
 func TestInit(t *testing.T) {
        c := qt.New(t)
        var found bool
        var ns *internal.TemplateFuncsNamespace
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        s := page.NewDummyHugoSite(v)
 
index b1b1e1cfd850bca3a89a9804586973e7400868ac..2a2b794785368bd8d3706743f98582e657648fc2 100644 (file)
@@ -22,11 +22,11 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/spf13/afero"
        "github.com/spf13/cast"
-       "github.com/spf13/viper"
 )
 
 type tstNoStringer struct{}
@@ -82,7 +82,7 @@ func TestNSConfig(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", "/a/b")
 
        ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
index 3adb6f8c23c5aef62423eda79b6ffd7f91b3b580..bbc0d018c0f71bf9b69b9e435d21a84fe617eb63 100644 (file)
@@ -17,11 +17,12 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/spf13/afero"
-       "github.com/spf13/viper"
 )
 
 func TestReadFile(t *testing.T) {
@@ -30,7 +31,7 @@ func TestReadFile(t *testing.T) {
 
        workingDir := "/home/hugo"
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workingDir)
 
        // f := newTestFuncsterWithViper(v)
@@ -68,7 +69,7 @@ func TestFileExists(t *testing.T) {
 
        workingDir := "/home/hugo"
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workingDir)
 
        ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
@@ -103,7 +104,7 @@ func TestStat(t *testing.T) {
        c := qt.New(t)
        workingDir := "/home/hugo"
 
-       v := viper.New()
+       v := config.New()
        v.Set("workingDir", workingDir)
 
        ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
index ce453b9a1356e7044fa9a0ce8b8a992b05e434c7..dc0761f2f4eb4dbbcb222d581e735e131cebf436 100644 (file)
@@ -18,11 +18,11 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
-       "github.com/spf13/viper"
 )
 
-var ns = New(&deps.Deps{Cfg: viper.New()})
+var ns = New(&deps.Deps{Cfg: config.New()})
 
 type tstNoStringer struct{}
 
index da2b4ca3b3581f5f8bdc373fee54dede93c66c12..850def00e27984383f40b1226e6836e2c7452805 100644 (file)
@@ -282,7 +282,7 @@ func (ns *Namespace) ToCSS(args ...interface{}) (resource.Resource, error) {
        }
 
        if m != nil {
-               maps.ToLower(m)
+               maps.PrepareParams(m)
                if t, found := m["transpiler"]; found {
                        switch t {
                        case transpilerDart, transpilerLibSass:
index f4a7935adb0327355df2451c708a271a55982490..f4c2ecd5c89e140ec3c2117292d7f3a57e2e18f6 100644 (file)
@@ -16,12 +16,13 @@ package site
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/htesting/hqt"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/tpl/internal"
-       "github.com/spf13/viper"
 )
 
 func TestInit(t *testing.T) {
@@ -29,7 +30,7 @@ func TestInit(t *testing.T) {
 
        var found bool
        var ns *internal.TemplateFuncsNamespace
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        s := page.NewDummyHugoSite(v)
 
index b356896cfa6b22eb332edeb8f4d35eda0de2a5c2..dd15418c8a2d20ac312374a254ce44493a768085 100644 (file)
@@ -16,12 +16,13 @@ package strings
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+
        "github.com/gohugoio/hugo/htesting/hqt"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/tpl/internal"
-       "github.com/spf13/viper"
 )
 
 func TestInit(t *testing.T) {
@@ -30,7 +31,7 @@ func TestInit(t *testing.T) {
        var ns *internal.TemplateFuncsNamespace
 
        for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
-               ns = nsf(&deps.Deps{Cfg: viper.New()})
+               ns = nsf(&deps.Deps{Cfg: config.New()})
                if ns.Name == name {
                        found = true
                        break
index 6e14a408c5af3bfcaa695a56acee895016a40ba9..18c033793c6332a8b77fe33770d7cb8416bb172f 100644 (file)
@@ -17,14 +17,14 @@ import (
        "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{}
 
index 67e9579245887d059a482081ec56fd6b0aeec65f..6ddf13b76e32b61a16da8fcb9afdef130bb741ba 100644 (file)
@@ -37,13 +37,13 @@ import (
        "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")
@@ -206,7 +206,7 @@ func BenchmarkPartialCached(b *testing.B) {
 
 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 {
index eb5f4253c6055e9e4136ac5a53784a69421d0cf0..9f3e05e6142d68591faec1e276dab0fdf88d4b7e 100644 (file)
@@ -16,16 +16,16 @@ package transform
 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)
@@ -112,7 +112,7 @@ title: Test Metadata
 func TestRemarshalComments(t *testing.T) {
        t.Parallel()
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -158,7 +158,7 @@ func TestTestRemarshalError(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -172,7 +172,7 @@ func TestTestRemarshalError(t *testing.T) {
 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))
 
index b98d82d2782a9ce6b317aea091b5ee07ec5955fa..1dbf97f9870acbacb4c8e5e4ed9fcb7a116e2b53 100644 (file)
@@ -26,7 +26,7 @@ import (
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/langs"
-       "github.com/spf13/viper"
+       
 )
 
 type tstNoStringer struct{}
@@ -35,7 +35,7 @@ func TestEmojify(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        ns := New(newDeps(v))
 
        for _, test := range []struct {
@@ -64,7 +64,7 @@ func TestHighlight(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -96,7 +96,7 @@ func TestHTMLEscape(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -126,7 +126,7 @@ func TestHTMLUnescape(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -156,7 +156,7 @@ func TestMarkdownify(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        v.Set("contentDir", "content")
        ns := New(newDeps(v))
 
@@ -185,7 +185,7 @@ func TestMarkdownify(t *testing.T) {
 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))
 
@@ -211,7 +211,7 @@ func TestPlainify(t *testing.T) {
        t.Parallel()
        c := qt.New(t)
 
-       v := viper.New()
+       v := config.New()
        ns := New(newDeps(v))
 
        for _, test := range []struct {
index ec81c316ab3e64a4b262b9f52d7d738ca9b31e88..85e3610d155ec7c3d53a738f822c7563f0c6b6bd 100644 (file)
@@ -19,13 +19,14 @@ import (
        "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 (
@@ -79,7 +80,7 @@ func (t testContentResource) Key() string {
 }
 
 func TestUnmarshal(t *testing.T) {
-       v := viper.New()
+       v := config.New()
        ns := New(newDeps(v))
        c := qt.New(t)
 
@@ -173,7 +174,7 @@ a;b;c`, mime: media.CSVType}, map[string]interface{}{"DElimiter": ";", "Comment"
 }
 
 func BenchmarkUnmarshalString(b *testing.B) {
-       v := viper.New()
+       v := config.New()
        ns := New(newDeps(v))
 
        const numJsons = 100
@@ -196,7 +197,7 @@ func BenchmarkUnmarshalString(b *testing.B) {
 }
 
 func BenchmarkUnmarshalResource(b *testing.B) {
-       v := viper.New()
+       v := config.New()
        ns := New(newDeps(v))
 
        const numJsons = 100
index f88aaf398b13bee5b1b6d30b289936ebc417a2e5..27b21144ad9769f8f565e4f513a821beddb2551c 100644 (file)
@@ -16,11 +16,12 @@ package urls
 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) {
@@ -29,7 +30,7 @@ func TestInit(t *testing.T) {
        var ns *internal.TemplateFuncsNamespace
 
        for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
-               ns = nsf(&deps.Deps{Cfg: viper.New()})
+               ns = nsf(&deps.Deps{Cfg: config.New()})
                if ns.Name == name {
                        found = true
                        break
index 9c005d2df4a61c09615b9f22a0bc66cf1d42dd19..23068c243268f7110df6273c2c2c114e0660b433 100644 (file)
@@ -17,14 +17,15 @@ import (
        "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{}