diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go index fd32c08a6..a5e2d09fd 100644 --- a/config/defaultConfigProvider.go +++ b/config/defaultConfigProvider.go @@ -197,6 +197,12 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) { defer c.mu.Unlock() k = strings.ToLower(k) + const ( + languagesKey = "languages" + paramsKey = "params" + menusKey = "menus" + ) + if k == "" { rs, f := c.root.GetMergeStrategy() if f && rs == maps.ParamsMergeStrategyNone { @@ -210,8 +216,44 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) { // 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) + if pppi, ok := c.root[kk]; ok { + ppp := pppi.(maps.Params) + if kk == languagesKey { + // Languages is currently a special case. + // We may have languages with menus or params in the + // right map that is not present in the left map. + // With the default merge strategy those items will not + // be passed over. + var hasParams, hasMenus bool + for _, rv := range pp { + if lkp, ok := rv.(maps.Params); ok { + _, hasMenus = lkp[menusKey] + _, hasParams = lkp[paramsKey] + } + } + + if hasMenus || hasParams { + for _, lv := range ppp { + if lkp, ok := lv.(maps.Params); ok { + if hasMenus { + if _, ok := lkp[menusKey]; !ok { + p := maps.Params{} + p.SetDefaultMergeStrategy(maps.ParamsMergeStrategyShallow) + lkp[menusKey] = p + } + } + if hasParams { + if _, ok := lkp[paramsKey]; !ok { + p := maps.Params{} + p.SetDefaultMergeStrategy(maps.ParamsMergeStrategyShallow) + lkp[paramsKey] = p + } + } + } + } + } + } + ppp.Merge(pp) } else { // We need to use the default merge strategy for // this key. diff --git a/hugolib/config.go b/hugolib/config.go index 90ac7eb01..a0ce98042 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -93,20 +93,6 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid } } - // 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 diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 65cb246b9..c5e77c946 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -76,7 +76,7 @@ func TestLoadMultiConfig(t *testing.T) { c.Assert(cfg.GetString("DontChange"), qt.Equals, "same") } -func TestLoadConfigFromTheme(t *testing.T) { +func TestLoadConfigFromThemes(t *testing.T) { t.Parallel() c := qt.New(t) @@ -185,11 +185,15 @@ name = "menu-theme" ` - buildForStrategy := func(t testing.TB, s string) *sitesBuilder { - mainConfig := strings.ReplaceAll(mainConfigTemplate, "MERGE_PARAMS", s) + buildForConfig := func(mainConfig, themeConfig string) *sitesBuilder { b := newTestSitesBuilder(t) b.WithConfigFile("toml", mainConfig).WithThemeConfigFile("toml", themeConfig) - return b.CreateSites().Build(BuildCfg{}) + return b.Build(BuildCfg{}) + } + + buildForStrategy := func(t testing.TB, s string) *sitesBuilder { + mainConfig := strings.ReplaceAll(mainConfigTemplate, "MERGE_PARAMS", s) + return buildForConfig(mainConfig, themeConfig) } c.Run("Merge default", func(c *qt.C) { @@ -316,6 +320,64 @@ name = "menu-theme" }) }) + c.Run("Merge no params in project", func(c *qt.C) { + b := buildForConfig( + "baseURL=\"https://example.org\"\ntheme = \"test-theme\"\n", + "[params]\np1 = \"p1 theme\"\n", + ) + + got := b.Cfg.Get("").(maps.Params) + + b.Assert(got["params"], qt.DeepEquals, maps.Params{ + "p1": "p1 theme", + }) + }) + + c.Run("Merge language no menus or params in project", func(c *qt.C) { + b := buildForConfig( + ` +theme = "test-theme" +baseURL = "https://example.com/" + +[languages] +[languages.en] +languageName = "English" + +`, + ` +[languages] +[languages.en] +languageName = "EnglishTheme" + +[languages.en.params] +p1="themep1" + +[[languages.en.menus.main]] +name = "menu-theme" +`, + ) + + got := b.Cfg.Get("").(maps.Params) + + b.Assert(got["languages"], qt.DeepEquals, + maps.Params{ + "en": maps.Params{ + "languagename": "English", + "menus": maps.Params{ + "main": []map[string]interface{}{ + { + "name": "menu-theme", + }, + }, + }, + "params": maps.Params{ + "p1": "themep1", + }, + }, + }, + ) + }) + } func TestLoadConfigFromThemeDir(t *testing.T) { diff --git a/hugolib/language_test.go b/hugolib/language_test.go index 16dcbcb03..da8ecd22b 100644 --- a/hugolib/language_test.go +++ b/hugolib/language_test.go @@ -54,3 +54,28 @@ weight = 1 b.AssertFileContent("public/index.html", "Hello: Hello") }) } + +func TestLanguageBugs(t *testing.T) { + c := qt.New(t) + + // Issue #8672 + c.Run("Config with language, menu in root only", func(c *qt.C) { + b := newTestSitesBuilder(c) + b.WithConfigFile("toml", ` +theme = "test-theme" +[[menus.foo]] +name = "foo-a" +[languages.en] + +`, + ) + + b.WithThemeConfigFile("toml", `[languages.en]`) + + b.Build(BuildCfg{}) + + menus := b.H.Sites[0].Menus() + c.Assert(menus, qt.HasLen, 1) + + }) +}