aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2021-06-09 10:58:18 +0200
committerBjørn Erik Pedersen <[email protected]>2021-06-14 17:00:32 +0200
commitd392893cd73dc00c927f342778f6dca9628d328e (patch)
treee2ea3eec09f36b7122ecdbc498c3c130e240e85c
parenta886dd53b80322e1edf924f2ede4d4ea037c5baf (diff)
downloadhugo-d392893cd73dc00c927f342778f6dca9628d328e.tar.gz
hugo-d392893cd73dc00c927f342778f6dca9628d328e.zip
Misc config loading fixes
The main motivation behind this is simplicity and correctnes, but the new small config library is also faster: ``` BenchmarkDefaultConfigProvider/Viper-16 252418 4546 ns/op 2720 B/op 30 allocs/op BenchmarkDefaultConfigProvider/Custom-16 450756 2651 ns/op 1008 B/op 6 allocs/op ``` Fixes #8633 Fixes #8618 Fixes #8630 Updates #8591 Closes #6680 Closes #5192
-rw-r--r--cache/filecache/filecache_config.go7
-rw-r--r--cache/filecache/filecache_config_test.go5
-rw-r--r--commands/commandeer.go2
-rw-r--r--commands/commands_test.go9
-rw-r--r--commands/config.go5
-rw-r--r--commands/new_site.go4
-rw-r--r--commands/server_test.go4
-rw-r--r--common/maps/maps.go77
-rw-r--r--common/maps/maps_test.go2
-rw-r--r--common/maps/params.go148
-rw-r--r--common/maps/params_test.go87
-rw-r--r--config/commonConfig_test.go4
-rw-r--r--config/compositeConfig.go113
-rw-r--r--config/compositeConfig_test.go40
-rw-r--r--config/configLoader.go23
-rw-r--r--config/configProvider.go5
-rw-r--r--config/configProvider_test.go3
-rw-r--r--config/defaultConfigProvider.go372
-rw-r--r--config/defaultConfigProvider_test.go315
-rw-r--r--config/docshelper.go45
-rw-r--r--config/privacy/privacyConfig_test.go3
-rw-r--r--config/services/servicesConfig_test.go4
-rw-r--r--create/content_test.go5
-rw-r--r--deploy/deployConfig_test.go4
-rw-r--r--docs/config.toml2
-rw-r--r--docs/content/en/getting-started/configuration.md20
-rw-r--r--docs/data/docs.json59
-rw-r--r--docs/layouts/shortcodes/code-toggle.html59
-rw-r--r--go.mod2
-rw-r--r--go.sum1
-rw-r--r--helpers/content_test.go5
-rw-r--r--helpers/general_test.go4
-rw-r--r--helpers/path_test.go4
-rw-r--r--helpers/testhelpers_test.go21
-rw-r--r--hugofs/fs_test.go9
-rw-r--r--hugofs/rootmapping_fs_test.go4
-rw-r--r--hugolib/config.go680
-rw-r--r--hugolib/config_test.go372
-rw-r--r--hugolib/filesystems/basefs_test.go10
-rw-r--r--hugolib/hugo_modules_test.go30
-rw-r--r--hugolib/hugo_sites.go15
-rw-r--r--hugolib/hugo_sites_build_errors_test.go4
-rw-r--r--hugolib/image_test.go4
-rw-r--r--hugolib/js_test.go11
-rw-r--r--hugolib/minify_publisher_test.go4
-rw-r--r--hugolib/page__meta.go2
-rw-r--r--hugolib/page_test.go5
-rw-r--r--hugolib/pagebundler_test.go18
-rw-r--r--hugolib/pages_capture.go2
-rw-r--r--hugolib/paths/paths_test.go7
-rw-r--r--hugolib/resource_chain_babel_test.go6
-rw-r--r--hugolib/resource_chain_test.go18
-rw-r--r--hugolib/robotstxt_test.go4
-rw-r--r--hugolib/shortcode_test.go7
-rw-r--r--hugolib/site.go22
-rw-r--r--hugolib/site_output_test.go12
-rw-r--r--hugolib/site_test.go5
-rw-r--r--hugolib/template_test.go5
-rw-r--r--hugolib/testhelpers_test.go26
-rw-r--r--langs/config.go8
-rw-r--r--langs/i18n/i18n_test.go7
-rw-r--r--langs/language.go84
-rw-r--r--langs/language_test.go7
-rw-r--r--markup/asciidocext/convert_test.go18
-rw-r--r--markup/blackfriday/convert_test.go6
-rw-r--r--markup/highlight/config_test.go5
-rw-r--r--markup/markup_config/config.go3
-rw-r--r--markup/markup_config/config_test.go6
-rw-r--r--markup/markup_test.go8
-rw-r--r--markup/mmark/convert_test.go4
-rw-r--r--markup/org/convert_test.go5
-rw-r--r--media/mediaType.go4
-rw-r--r--minifiers/config.go4
-rw-r--r--minifiers/config_test.go6
-rw-r--r--minifiers/minifiers_test.go15
-rw-r--r--modules/collect.go2
-rw-r--r--modules/npm/package_builder.go8
-rw-r--r--output/outputFormat.go6
-rw-r--r--publisher/htmlElementsCollector_test.go5
-rw-r--r--related/inverted_index.go11
-rw-r--r--resources/page/pagemeta/page_frontmatter_test.go13
-rw-r--r--resources/page/pagination_test.go4
-rw-r--r--resources/page/testhelpers_test.go4
-rw-r--r--resources/resource_metadata.go2
-rw-r--r--resources/resource_transformers/htesting/testhelpers.go4
-rw-r--r--resources/resource_transformers/js/options.go7
-rw-r--r--resources/testhelpers_test.go6
-rw-r--r--source/filesystem_test.go8
-rw-r--r--tpl/cast/docshelper.go8
-rw-r--r--tpl/collections/collections_test.go4
-rw-r--r--tpl/data/init_test.go4
-rw-r--r--tpl/data/resources_test.go8
-rw-r--r--tpl/encoding/encoding.go3
-rw-r--r--tpl/hugo/init_test.go5
-rw-r--r--tpl/images/images_test.go4
-rw-r--r--tpl/os/os_test.go9
-rw-r--r--tpl/path/path_test.go4
-rw-r--r--tpl/resources/resources.go2
-rw-r--r--tpl/site/init_test.go5
-rw-r--r--tpl/strings/init_test.go5
-rw-r--r--tpl/strings/strings_test.go4
-rw-r--r--tpl/tplimpl/template_funcs_test.go6
-rw-r--r--tpl/transform/remarshal_test.go10
-rw-r--r--tpl/transform/transform_test.go16
-rw-r--r--tpl/transform/unmarshal_test.go9
-rw-r--r--tpl/urls/init_test.go5
-rw-r--r--tpl/urls/urls_test.go5
107 files changed, 2115 insertions, 1016 deletions
diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go
index 0c6b569c1..801799e36 100644
--- a/cache/filecache/filecache_config.go
+++ b/cache/filecache/filecache_config.go
@@ -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 == "" {
diff --git a/cache/filecache/filecache_config_test.go b/cache/filecache/filecache_config_test.go
index cd1d2c82a..acc127e67 100644
--- a/cache/filecache/filecache_config_test.go
+++ b/cache/filecache/filecache_config_test.go
@@ -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")
diff --git a/commands/commandeer.go b/commands/commandeer.go
index 024651aad..06e27cf18 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -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
}
diff --git a/commands/commands_test.go b/commands/commands_test.go
index a0eeb157b..99ffea48c 100644
--- a/commands/commands_test.go
+++ b/commands/commands_test.go
@@ -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")
diff --git a/commands/config.go b/commands/config.go
index 37bf45e3c..7ab429308 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -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.
diff --git a/commands/new_site.go b/commands/new_site.go
index 6fac2c22c..71097b8ff 100644
--- a/commands/new_site.go
+++ b/commands/new_site.go
@@ -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) {
diff --git a/commands/server_test.go b/commands/server_test.go
index cc2a6d92c..05d21a516 100644
--- a/commands/server_test.go
+++ b/commands/server_test.go
@@ -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
diff --git a/common/maps/maps.go b/common/maps/maps.go
index 41d9b6e15..5fb079009 100644
--- a/common/maps/maps.go
+++ b/common/maps/maps.go
@@ -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{}) {
diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go
index 7e527aac5..dbe97a15a 100644
--- a/common/maps/maps_test.go
+++ b/common/maps/maps_test.go
@@ -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)
}
diff --git a/common/maps/params.go b/common/maps/params.go
index 4c881093c..7e94d593b 100644
--- a/common/maps/params.go
+++ b/common/maps/params.go
@@ -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
+ }
+ }
+}
diff --git a/common/maps/params_test.go b/common/maps/params_test.go
index df8cbf8d6..8859bb86b 100644
--- a/common/maps/params_test.go
+++ b/common/maps/params_test.go
@@ -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",
+ })
+
+}
diff --git a/config/commonConfig_test.go b/config/commonConfig_test.go
index d4273277a..55767913f 100644
--- a/config/commonConfig_test.go
+++ b/config/commonConfig_test.go
@@ -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
index 000000000..c68419533
--- /dev/null
+++ b/config/compositeConfig.go
@@ -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
index 000000000..60644102f
--- /dev/null
+++ b/config/compositeConfig_test.go
@@ -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)
+ })
+}
diff --git a/config/configLoader.go b/config/configLoader.go
index 6d94f0b79..0998b1bef 100644
--- a/config/configLoader.go
+++ b/config/configLoader.go
@@ -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
-}
diff --git a/config/configProvider.go b/config/configProvider.go
index 928bf948a..92206ca9e 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -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
}
diff --git a/config/configProvider_test.go b/config/configProvider_test.go
index d9fff56b6..0afba1e58 100644
--- a/config/configProvider_test.go
+++ b/config/configProvider_test.go
@@ -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
index 000000000..d9c9db7f1
--- /dev/null
+++ b/config/defaultConfigProvider.go
@@ -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
index 000000000..834165d96
--- /dev/null
+++ b/config/defaultConfigProvider_test.go
@@ -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
index 000000000..336a0dc16
--- /dev/null
+++ b/config/docshelper.go
@@ -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)
+}
diff --git a/config/privacy/privacyConfig_test.go b/config/privacy/privacyConfig_test.go
index 0fb599c0a..c17ce713d 100644
--- a/config/privacy/privacyConfig_test.go
+++ b/config/privacy/privacyConfig_test.go
@@ -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)
diff --git a/config/services/servicesConfig_test.go b/config/services/servicesConfig_test.go
index 6e979b999..d7a52ba4f 100644
--- a/config/services/servicesConfig_test.go
+++ b/config/services/servicesConfig_test.go
@@ -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")
diff --git a/create/content_test.go b/create/content_test.go
index 37dbf98bd..38ff7de8d 100644
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -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"
diff --git a/deploy/deployConfig_test.go b/deploy/deployConfig_test.go
index 413b1211b..e30f31c32 100644
--- a/deploy/deployConfig_test.go
+++ b/deploy/deployConfig_test.go
@@ -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)
diff --git a/docs/config.toml b/docs/config.toml
index efa1b0573..d21f6f172 100644
--- a/docs/config.toml
+++ b/docs/config.toml
@@ -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"
diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md
index 48a4af0c9..fd8c65d04 100644
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -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
diff --git a/docs/data/docs.json b/docs/data/docs.json
index 2b17ad8af..b927f8d0a 100644
--- a/docs/data/docs.json
+++ b/docs/data/docs.json
@@ -1587,6 +1587,65 @@
"preserveTOC": false
}
},
+ "mergeStrategy": {
+ "build": {
+ "_merge": "none"
+ },
+ "caches": {
+ "_merge": "none"
+ },
+ "frontmatter": {
+ "_merge": "none"
+ },
+ "imaging": {
+ "_merge": "none"
+ },
+ "languages": {
+ "_merge": "none",
+ "en": {
+ "_merge": "none",
+ "menus": {
+ "_merge": "shallow"
+ },
+ "params": {
+ "_merge": "deep"
+ }
+ }
+ },
+ "markup": {
+ "_merge": "none"
+ },
+ "mediatypes": {
+ "_merge": "shallow"
+ },
+ "menus": {
+ "_merge": "shallow"
+ },
+ "minify": {
+ "_merge": "none"
+ },
+ "module": {
+ "_merge": "none"
+ },
+ "outputformats": {
+ "_merge": "shallow"
+ },
+ "params": {
+ "_merge": "deep"
+ },
+ "permalinks": {
+ "_merge": "none"
+ },
+ "related": {
+ "_merge": "none"
+ },
+ "sitemap": {
+ "_merge": "none"
+ },
+ "taxonomies": {
+ "_merge": "none"
+ }
+ },
"minify": {
"minifyOutput": false,
"disableHTML": false,
diff --git a/docs/layouts/shortcodes/code-toggle.html b/docs/layouts/shortcodes/code-toggle.html
index da4b00719..0b92d520d 100644
--- a/docs/layouts/shortcodes/code-toggle.html
+++ b/docs/layouts/shortcodes/code-toggle.html
@@ -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 ca89a0ca6..f0c9f1fa7 100644
--- 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 ccdc49b69..d9cd59c34 100644
--- 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=
diff --git a/helpers/content_test.go b/helpers/content_test.go
index 573c1c90d..515b788f1 100644
--- a/helpers/content_test.go
+++ b/helpers/content_test.go
@@ -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)
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 518188c17..bfabcbef4 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -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)
diff --git a/helpers/path_test.go b/helpers/path_test.go
index e1ac90b2f..c95951832 100644
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -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)
diff --git a/helpers/testhelpers_test.go b/helpers/testhelpers_test.go
index 4a6ebd0b1..7d63e4d88 100644
--- a/helpers/testhelpers_test.go
+++ b/helpers/testhelpers_test.go
@@ -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)
diff --git a/hugofs/fs_test.go b/hugofs/fs_test.go
index a343bbd1f..8d52267af 100644
--- a/hugofs/fs_test.go
+++ b/hugofs/fs_test.go
@@ -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/")
diff --git a/hugofs/rootmapping_fs_test.go b/hugofs/rootmapping_fs_test.go
index fc2ddeb62..db9ed25cd 100644
--- a/hugofs/rootmapping_fs_test.go
+++ b/hugofs/rootmapping_fs_test.go
@@ -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())
diff --git a/hugolib/config.go b/hugolib/config.go
index eaa6710ae..f559d7fd3 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -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
}
diff --git a/hugolib/config_test.go b/hugolib/config_test.go
index fb81c927e..77ac9b92f 100644
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -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)
+
}
diff --git a/hugolib/filesystems/basefs_test.go b/hugolib/filesystems/basefs_test.go
index 139d0c20e..a119d4c17 100644
--- a/hugolib/filesystems/basefs_test.go
+++ b/hugolib/filesystems/basefs_test.go
@@ -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)
diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go
index 5ebf8bd0b..96355f08b 100644
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -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)
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 0607bde1c..d380cf737 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -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
}
diff --git a/hugolib/hugo_sites_build_errors_test.go b/hugolib/hugo_sites_build_errors_test.go
index efe013c39..8b23e7ac7 100644
--- a/hugolib/hugo_sites_build_errors_test.go
+++ b/hugolib/hugo_sites_build_errors_test.go
@@ -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)
diff --git a/hugolib/image_test.go b/hugolib/image_test.go
index 0dacf2a33..4726f5b49 100644
--- a/hugolib/image_test.go
+++ b/hugolib/image_test.go
@@ -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)
diff --git a/hugolib/js_test.go b/hugolib/js_test.go
index cd0883fd5..75dc0e7de 100644
--- a/hugolib/js_test.go
+++ b/hugolib/js_test.go
@@ -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
diff --git a/hugolib/minify_publisher_test.go b/hugolib/minify_publisher_test.go
index 66e674ade..ef460efa2 100644
--- a/hugolib/minify_publisher_test.go
+++ b/hugolib/minify_publisher_test.go
@@ -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/")
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 3df997452..759fadd2d 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -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 {
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index 9d23aaa5c..5bc3db22f 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -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)
diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go
index 7d775871a..b63d663e3 100644
--- a/hugolib/pagebundler_test.go
+++ b/hugolib/pagebundler_test.go
@@ -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)
diff --git a/hugolib/pages_capture.go b/hugolib/pages_capture.go
index c7a42acdc..45221af71 100644
--- a/hugolib/pages_capture.go
+++ b/hugolib/pages_capture.go
@@ -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 {
diff --git a/hugolib/paths/paths_test.go b/hugolib/paths/paths_test.go
index 59dbf0e00..d3ead4d17 100644
--- a/hugolib/paths/paths_test.go
+++ b/hugolib/paths/paths_test.go
@@ -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{}{
diff --git a/hugolib/resource_chain_babel_test.go b/hugolib/resource_chain_babel_test.go
index 61b2ef6d5..9e5c9c4a5 100644
--- a/hugolib/resource_chain_babel_test.go
+++ b/hugolib/resource_chain_babel_test.go
@@ -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)
diff --git a/hugolib/resource_chain_test.go b/hugolib/resource_chain_test.go
index 9ea1d8529..a367237ab 100644
--- a/hugolib/resource_chain_test.go
+++ b/hugolib/resource_chain_test.go
@@ -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,
})
diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go
index 6bc39e97c..2035c235f 100644
--- a/hugolib/robotstxt_test.go
+++ b/hugolib/robotstxt_test.go
@@ -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)
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 51187a003..7eb0d01de 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -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,
diff --git a/hugolib/site.go b/hugolib/site.go
index 12714892d..9921dcc97 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -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()]
}
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index 1961dd06f..f3455f369 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -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 (
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
index 365679a32..e25991164 100644
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -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"},
diff --git a/hugolib/template_test.go b/hugolib/template_test.go
index f487cec67..abb6d32f9 100644
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -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
)
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index 09988f972..451022e5c 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -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 {
diff --git a/langs/config.go b/langs/config.go
index 3b1da89ec..fe4ed9d14 100644
--- a/langs/config.go
+++ b/langs/config.go
@@ -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)
}
diff --git a/langs/i18n/i18n_test.go b/langs/i18n/i18n_test.go
index 18c122010..be20ca3c8 100644
--- a/langs/i18n/i18n_test.go
+++ b/langs/i18n/i18n_test.go
@@ -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")
diff --git a/langs/language.go b/langs/language.go
index bdfde573c..a86d82ba3 100644
--- a/langs/language.go
+++ b/langs/language.go
@@ -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)
}
diff --git a/langs/language_test.go b/langs/language_test.go
index 97abe77cc..8557d781a 100644
--- a/langs/language_test.go
+++ b/langs/language_test.go
@@ -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")
diff --git a/markup/asciidocext/convert_test.go b/markup/asciidocext/convert_test.go
index 4c183f7bb..14110bb04 100644
--- a/markup/asciidocext/convert_test.go
+++ b/markup/asciidocext/convert_test.go
@@ -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
diff --git a/markup/blackfriday/convert_test.go b/markup/blackfriday/convert_test.go
index 414905a76..5b1c1a71f 100644
--- a/markup/blackfriday/convert_test.go
+++ b/markup/blackfriday/convert_test.go
@@ -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{
diff --git a/markup/highlight/config_test.go b/markup/highlight/config_test.go
index 3c3747622..ab92ecf36 100644
--- a/markup/highlight/config_test.go
+++ b/markup/highlight/config_test.go
@@ -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)
diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go
index a3562cd24..d4a101709 100644
--- a/markup/markup_config/config.go
+++ b/markup/markup_config/config.go
@@ -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 {
diff --git a/markup/markup_config/config_test.go b/markup/markup_config/config_test.go
index 4a1f1232b..08d7b5995 100644
--- a/markup/markup_config/config_test.go
+++ b/markup/markup_config/config_test.go
@@ -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,
diff --git a/markup/markup_test.go b/markup/markup_test.go
index 6e7fe2059..71d39075d 100644
--- a/markup/markup_test.go
+++ b/markup/markup_test.go
@@ -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)
diff --git a/markup/mmark/convert_test.go b/markup/mmark/convert_test.go
index 01d3e8cbb..414e023ab 100644
--- a/markup/mmark/convert_test.go
+++ b/markup/mmark/convert_test.go
@@ -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)
diff --git a/markup/org/convert_test.go b/markup/org/convert_test.go
index 96c388457..e3676fc34 100644
--- a/markup/org/convert_test.go
+++ b/markup/org/convert_test.go
@@ -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{})
diff --git a/media/mediaType.go b/media/mediaType.go
index 21a91524d..817ea1ba8 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -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"]
diff --git a/minifiers/config.go b/minifiers/config.go
index fc707ce37..675e5d2b8 100644
--- a/minifiers/config.go
+++ b/minifiers/config.go
@@ -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 {
diff --git a/minifiers/config_test.go b/minifiers/config_test.go
index 851863842..fca56b7f1 100644
--- a/minifiers/config_test.go
+++ b/minifiers/config_test.go
@@ -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)
diff --git a/minifiers/minifiers_test.go b/minifiers/minifiers_test.go
index 97fe4e465..37e017420 100644
--- a/minifiers/minifiers_test.go
+++ b/minifiers/minifiers_test.go
@@ -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{}{
diff --git a/modules/collect.go b/modules/collect.go
index db79f434e..163eda74a 100644
--- a/modules/collect.go
+++ b/modules/collect.go
@@ -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)
}
}
diff --git a/modules/npm/package_builder.go b/modules/npm/package_builder.go
index fe5d19832..a073b3d8f 100644
--- a/modules/npm/package_builder.go
+++ b/modules/npm/package_builder.go
@@ -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
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 9a081121a..a52f43c8b 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -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
}
diff --git a/publisher/htmlElementsCollector_test.go b/publisher/htmlElementsCollector_test.go
index 2400b1612..9ede80095 100644
--- a/publisher/htmlElementsCollector_test.go
+++ b/publisher/htmlElementsCollector_test.go
@@ -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))
diff --git a/related/inverted_index.go b/related/inverted_index.go
index 2e0ea295d..329a8c998 100644
--- a/related/inverted_index.go
+++ b/related/inverted_index.go
@@ -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")
}
diff --git a/resources/page/pagemeta/page_frontmatter_test.go b/resources/page/pagemeta/page_frontmatter_test.go
index 7c4c77a91..6e3833b0f 100644
--- a/resources/page/pagemeta/page_frontmatter_test.go
+++ b/resources/page/pagemeta/page_frontmatter_test.go
@@ -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"},
diff --git a/resources/page/pagination_test.go b/resources/page/pagination_test.go
index 8d4f857d7..07ad6233b 100644
--- a/resources/page/pagination_test.go
+++ b/resources/page/pagination_test.go
@@ -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} {
diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go
index 187930461..1a0a6586a 100644
--- a/resources/page/testhelpers_test.go
+++ b/resources/page/testhelpers_test.go
@@ -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 {
diff --git a/resources/resource_metadata.go b/resources/resource_metadata.go
index a0c232c67..0dd0945e3 100644
--- a/resources/resource_metadata.go
+++ b/resources/resource_metadata.go
@@ -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)
}
}
diff --git a/resources/resource_transformers/htesting/testhelpers.go b/resources/resource_transformers/htesting/testhelpers.go
index 8eacf7da4..21333eccb 100644
--- a/resources/resource_transformers/htesting/testhelpers.go
+++ b/resources/resource_transformers/htesting/testhelpers.go
@@ -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")
diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go
index ceed22f34..84f571f17 100644
--- a/resources/resource_transformers/js/options.go
+++ b/resources/resource_transformers/js/options.go
@@ -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
diff --git a/resources/testhelpers_test.go b/resources/testhelpers_test.go
index 32e4213ea..12dc8efe8 100644
--- a/resources/testhelpers_test.go
+++ b/resources/testhelpers_test.go
@@ -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")
diff --git a/source/filesystem_test.go b/source/filesystem_test.go
index 5dc187978..e6dc9ce16 100644
--- a/source/filesystem_test.go
+++ b/source/filesystem_test.go
@@ -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")
diff --git a/tpl/cast/docshelper.go b/tpl/cast/docshelper.go
index 035db1740..a3cc26de5 100644
--- a/tpl/cast/docshelper.go
+++ b/tpl/cast/docshelper.go
@@ -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
}
diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go
index 1e0569751..3faf46930 100644
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -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))
}
diff --git a/tpl/data/init_test.go b/tpl/data/init_test.go
index fedce8e5c..9174d42a6 100644
--- a/tpl/data/init_test.go
+++ b/tpl/data/init_test.go
@@ -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)
diff --git a/tpl/data/resources_test.go b/tpl/data/resources_test.go
index 18061c052..1bf6d769f 100644
--- a/tpl/data/resources_test.go
+++ b/tpl/data/resources_test.go
@@ -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))
}
diff --git a/tpl/encoding/encoding.go b/tpl/encoding/encoding.go
index 09e2b94bc..ce25151a0 100644
--- a/tpl/encoding/encoding.go
+++ b/tpl/encoding/encoding.go
@@ -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
}
diff --git a/tpl/hugo/init_test.go b/tpl/hugo/init_test.go
index c94a883fd..9b1b14be4 100644
--- a/tpl/hugo/init_test.go
+++ b/tpl/hugo/init_test.go
@@ -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)
diff --git a/tpl/images/images_test.go b/tpl/images/images_test.go
index b1b1e1cfd..2a2b79478 100644
--- a/tpl/images/images_test.go
+++ b/tpl/images/images_test.go
@@ -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)})
diff --git a/tpl/os/os_test.go b/tpl/os/os_test.go
index 3adb6f8c2..bbc0d018c 100644
--- a/tpl/os/os_test.go
+++ b/tpl/os/os_test.go
@@ -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)})
diff --git a/tpl/path/path_test.go b/tpl/path/path_test.go
index ce453b9a1..dc0761f2f 100644
--- a/tpl/path/path_test.go
+++ b/tpl/path/path_test.go
@@ -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{}
diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go
index da2b4ca3b..850def00e 100644
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -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:
diff --git a/tpl/site/init_test.go b/tpl/site/init_test.go
index f4a7935ad..f4c2ecd5c 100644
--- a/tpl/site/init_test.go
+++ b/tpl/site/init_test.go
@@ -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)
diff --git a/tpl/strings/init_test.go b/tpl/strings/init_test.go
index b356896cf..dd15418c8 100644
--- a/tpl/strings/init_test.go
+++ b/tpl/strings/init_test.go
@@ -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
diff --git a/tpl/strings/strings_test.go b/tpl/strings/strings_test.go
index 6e14a408c..18c033793 100644
--- a/tpl/strings/strings_test.go
+++ b/tpl/strings/strings_test.go
@@ -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{}
diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
index 67e957924..6ddf13b76 100644
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -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 {
diff --git a/tpl/transform/remarshal_test.go b/tpl/transform/remarshal_test.go
index eb5f4253c..9f3e05e61 100644
--- a/tpl/transform/remarshal_test.go
+++ b/tpl/transform/remarshal_test.go
@@ -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))
diff --git a/tpl/transform/transform_test.go b/tpl/transform/transform_test.go
index b98d82d27..1dbf97f98 100644
--- a/tpl/transform/transform_test.go
+++ b/tpl/transform/transform_test.go
@@ -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 {
diff --git a/tpl/transform/unmarshal_test.go b/tpl/transform/unmarshal_test.go
index ec81c316a..85e3610d1 100644
--- a/tpl/transform/unmarshal_test.go
+++ b/tpl/transform/unmarshal_test.go
@@ -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
diff --git a/tpl/urls/init_test.go b/tpl/urls/init_test.go
index f88aaf398..27b21144a 100644
--- a/tpl/urls/init_test.go
+++ b/tpl/urls/init_test.go
@@ -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
diff --git a/tpl/urls/urls_test.go b/tpl/urls/urls_test.go
index 9c005d2df..23068c243 100644
--- a/tpl/urls/urls_test.go
+++ b/tpl/urls/urls_test.go
@@ -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{}