config: Fix merge of config with map[string]string values.
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 22 Jun 2021 07:53:37 +0000 (09:53 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 22 Jun 2021 19:38:28 +0000 (21:38 +0200)
Fixes #8679

common/maps/maps.go
common/maps/maps_test.go
common/maps/params.go
config/compositeConfig.go
config/configProvider.go
config/defaultConfigProvider.go
config/defaultConfigProvider_test.go
hugolib/config.go
hugolib/content_render_hooks_test.go

index 5fb0790092f7bef5b141fb4e5881c996e67307c5..79fcc23d0002ceead8ca480d32d20ab08feb570c 100644 (file)
@@ -49,6 +49,15 @@ func ToParamsAndPrepare(in interface{}) (Params, bool) {
        return m, true
 }
 
+// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
+func MustToParamsAndPrepare(in interface{}) Params {
+       if p, ok := ToParamsAndPrepare(in); ok {
+               return p
+       } else {
+               panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
+       }
+}
+
 // ToStringMap converts in to map[string]interface{}.
 func ToStringMap(in interface{}) map[string]interface{} {
        m, _ := ToStringMapE(in)
index dbe97a15aef54de72ed5e3928add34d5a2645eff..ba3c2508757e7b260ff73b7f71aa7c79abe7460d 100644 (file)
@@ -21,10 +21,10 @@ import (
        qt "github.com/frankban/quicktest"
 )
 
-func TestToLower(t *testing.T) {
+func TestPrepareParams(t *testing.T) {
        tests := []struct {
-               input    map[string]interface{}
-               expected map[string]interface{}
+               input    Params
+               expected Params
        }{
                {
                        map[string]interface{}{
@@ -47,6 +47,9 @@ func TestToLower(t *testing.T) {
                                "gHi": map[string]interface{}{
                                        "J": 25,
                                },
+                               "jKl": map[string]string{
+                                       "M": "26",
+                               },
                        },
                        Params{
                                "abc": 32,
@@ -60,13 +63,16 @@ func TestToLower(t *testing.T) {
                                "ghi": Params{
                                        "j": 25,
                                },
+                               "jkl": Params{
+                                       "m": "26",
+                               },
                        },
                },
        }
 
        for i, test := range tests {
                t.Run(fmt.Sprint(i), func(t *testing.T) {
-                       // ToLower modifies input.
+                       // PrepareParams modifies input.
                        PrepareParams(test.input)
                        if !reflect.DeepEqual(test.expected, test.input) {
                                t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
index 7e94d593b596c77f998034b74199752eec539135..e5a1bd07db17fcc17ebd74c07a125a188e3c0cf6 100644 (file)
@@ -226,7 +226,7 @@ func toMergeStrategy(v interface{}) ParamsMergeStrategy {
 // 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 nested map[interface{}]interface{}, map[string]interface{},map[string]string  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 {
@@ -236,7 +236,7 @@ func PrepareParams(m Params) {
                        v = toMergeStrategy(v)
                        retyped = true
                } else {
-                       switch v.(type) {
+                       switch vv := v.(type) {
                        case map[interface{}]interface{}:
                                var p Params = cast.ToStringMap(v)
                                v = p
@@ -247,6 +247,14 @@ func PrepareParams(m Params) {
                                v = p
                                PrepareParams(p)
                                retyped = true
+                       case map[string]string:
+                               p := make(Params)
+                               for k, v := range vv {
+                                       p[k] = v
+                               }
+                               v = p
+                               PrepareParams(p)
+                               retyped = true
                        }
                }
 
index c68419533f5b0c7396cde898a974f46fc0052c1e..92bb165b7c33c58d09b396fde68e3a6403177db8 100644 (file)
@@ -104,6 +104,10 @@ func (c *compositeConfig) Set(key string, value interface{}) {
        c.layer.Set(key, value)
 }
 
+func (c *compositeConfig) SetDefaults(params maps.Params) {
+       c.layer.SetDefaults(params)
+}
+
 func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
        panic("not supported")
 }
index 92206ca9e3e3df01dc185f5fe6b304760354250f..481524c3b2be671ee424e3cd2c5b2ad31c42978e 100644 (file)
@@ -30,6 +30,7 @@ type Provider interface {
        Get(key string) interface{}
        Set(key string, value interface{})
        Merge(key string, value interface{})
+       SetDefaults(params maps.Params)
        SetDefaultMergeStrategy()
        WalkParams(walkFn func(params ...KeyParams) bool)
        IsSet(key string) bool
index d9c9db7f1dd7db8a3114d2a905a221aa0010ae8e..fd32c08a69203035a123a809c4175391094c0322 100644 (file)
@@ -163,10 +163,9 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
        }
 
        switch vv := v.(type) {
-       case map[string]interface{}:
-               var p maps.Params = vv
+       case map[string]interface{}, map[interface{}]interface{}, map[string]string:
+               p := maps.MustToParamsAndPrepare(vv)
                v = p
-               maps.PrepareParams(p)
        }
 
        key, m := c.getNestedKeyAndMap(k, true)
@@ -183,6 +182,16 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
        m[key] = v
 }
 
+// SetDefaults will set values from params if not already set.
+func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
+       maps.PrepareParams(params)
+       for k, v := range params {
+               if _, found := c.root[k]; !found {
+                       c.root[k] = v
+               }
+       }
+}
+
 func (c *defaultConfigProvider) Merge(k string, v interface{}) {
        c.mu.Lock()
        defer c.mu.Unlock()
@@ -226,10 +235,9 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
        }
 
        switch vv := v.(type) {
-       case map[string]interface{}:
-               var p maps.Params = vv
+       case map[string]interface{}, map[interface{}]interface{}, map[string]string:
+               p := maps.MustToParamsAndPrepare(vv)
                v = p
-               maps.PrepareParams(p)
        }
 
        key, m := c.getNestedKeyAndMap(k, true)
index 834165d9602c2109ba9110eab402b7019e49bbac..6752ab2e551c3328d3e2f4af604ce39bb359d850 100644 (file)
@@ -204,6 +204,85 @@ func TestDefaultConfigProvider(t *testing.T) {
                })
        })
 
+       // Issue #8679
+       c.Run("Merge typed maps", func(c *qt.C) {
+
+               for _, left := range []interface{}{
+                       map[string]string{
+                               "c": "cv1",
+                       },
+                       map[string]interface{}{
+                               "c": "cv1",
+                       },
+                       map[interface{}]interface{}{
+                               "c": "cv1",
+                       },
+               } {
+                       cfg := New()
+
+                       cfg.Set("", map[string]interface{}{
+                               "b": left,
+                       })
+
+                       cfg.Merge("", maps.Params{
+                               "b": maps.Params{
+                                       "c": "cv2",
+                                       "d": "dv2",
+                               },
+                       })
+
+                       c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                               "b": maps.Params{
+                                       "c": "cv1",
+                                       "d": "dv2",
+                               },
+                       })
+               }
+
+               for _, left := range []interface{}{
+                       map[string]string{
+                               "b": "bv1",
+                       },
+                       map[string]interface{}{
+                               "b": "bv1",
+                       },
+                       map[interface{}]interface{}{
+                               "b": "bv1",
+                       },
+               } {
+
+                       for _, right := range []interface{}{
+                               map[string]string{
+                                       "b": "bv2",
+                                       "c": "cv2",
+                               },
+                               map[string]interface{}{
+                                       "b": "bv2",
+                                       "c": "cv2",
+                               },
+                               map[interface{}]interface{}{
+                                       "b": "bv2",
+                                       "c": "cv2",
+                               },
+                       } {
+                               cfg := New()
+
+                               cfg.Set("a", left)
+
+                               cfg.Merge("a", right)
+
+                               c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+                                       "a": maps.Params{
+                                               "b": "bv1",
+                                               "c": "cv2",
+                                       },
+                               })
+                       }
+
+               }
+
+       })
+
        c.Run("IsSet", func(c *qt.C) {
                cfg := New()
 
index cad8451990db41690faa7935ca444e5faee34b45..90ac7eb0172727f7eae585b7b6c6b87b9245d12d 100644 (file)
@@ -64,10 +64,6 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
 
        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)
@@ -78,6 +74,10 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
                }
        }
 
+       if err := l.applyConfigDefaults(); err != nil {
+               return l.cfg, configFiles, err
+       }
+
        if d.AbsConfigDir != "" {
                dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, d.AbsConfigDir, l.Environment)
                if err == nil {
@@ -298,7 +298,7 @@ func (l configLoader) applyConfigDefaults() error {
                "enableInlineShortcodes":               false,
        }
 
-       l.cfg.Merge("", defaultSettings)
+       l.cfg.SetDefaults(defaultSettings)
 
        return nil
 }
index 9791683052961ecae337356b48c76b599edc6a7b..1d7a4f8e3109aa7d9ac4afd7b3d13e722f07311f 100644 (file)
@@ -56,6 +56,7 @@ title: P1
        b.AssertFileContent("public/p1/index.html", `Link First Link|PARTIAL1_EDITED PARTIAL2_EDITEDEND`)
 }
 
+
 func TestRenderHooks(t *testing.T) {
        config := `
 baseURL="https://example.org"