]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
config: Fix _merge issue when key doesn't exist on the left side
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 23 Apr 2025 14:27:14 +0000 (16:27 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 24 Apr 2025 11:56:27 +0000 (13:56 +0200)
Fixes #13643
Fixes #13646

config/allconfig/allconfig.go
config/allconfig/allconfig_integration_test.go
config/allconfig/alldecoders.go
config/allconfig/load.go
config/commonConfig.go
config/services/servicesConfig.go
hugolib/config_test.go

index e73153a94b55d096697888708ab348399742d3c6..d3ee28490c0c022660e1acb1b7ff60ca6269d356 100644 (file)
@@ -800,28 +800,56 @@ func (c *Configs) IsZero() bool {
 
 func (c *Configs) Init() error {
        var languages langs.Languages
-       defaultContentLanguage := c.Base.DefaultContentLanguage
-       for k, v := range c.LanguageConfigMap {
-               languageConf := v.Languages[k]
-               language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
-               if err != nil {
-                       return err
+
+       var langKeys []string
+       var hasEn bool
+
+       const en = "en"
+
+       for k := range c.LanguageConfigMap {
+               langKeys = append(langKeys, k)
+               if k == en {
+                       hasEn = true
                }
-               languages = append(languages, language)
        }
 
-       // Sort the sites by language weight (if set) or lang.
-       sort.Slice(languages, func(i, j int) bool {
-               li := languages[i]
-               lj := languages[j]
+       // Sort the LanguageConfigSlice by language weight (if set) or lang.
+       sort.Slice(langKeys, func(i, j int) bool {
+               ki := langKeys[i]
+               kj := langKeys[j]
+               lki := c.LanguageConfigMap[ki]
+               lkj := c.LanguageConfigMap[kj]
+               li := lki.Languages[ki]
+               lj := lkj.Languages[kj]
                if li.Weight != lj.Weight {
                        return li.Weight < lj.Weight
                }
-               return li.Lang < lj.Lang
+               return ki < kj
        })
 
-       for _, l := range languages {
-               c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
+       // See issue #13646.
+       defaultConfigLanguageFallback := en
+       if !hasEn {
+               // Pick the first one.
+               defaultConfigLanguageFallback = langKeys[0]
+       }
+
+       if c.Base.DefaultContentLanguage == "" {
+               c.Base.DefaultContentLanguage = defaultConfigLanguageFallback
+       }
+
+       for _, k := range langKeys {
+               v := c.LanguageConfigMap[k]
+               if v.DefaultContentLanguage == "" {
+                       v.DefaultContentLanguage = defaultConfigLanguageFallback
+               }
+               c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
+               languageConf := v.Languages[k]
+               language, err := langs.NewLanguage(k, c.Base.DefaultContentLanguage, v.TimeZone, languageConf)
+               if err != nil {
+                       return err
+               }
+               languages = append(languages, language)
        }
 
        // Filter out disabled languages.
@@ -836,12 +864,12 @@ func (c *Configs) Init() error {
 
        var languagesDefaultFirst langs.Languages
        for _, l := range languages {
-               if l.Lang == defaultContentLanguage {
+               if l.Lang == c.Base.DefaultContentLanguage {
                        languagesDefaultFirst = append(languagesDefaultFirst, l)
                }
        }
        for _, l := range languages {
-               if l.Lang != defaultContentLanguage {
+               if l.Lang != c.Base.DefaultContentLanguage {
                        languagesDefaultFirst = append(languagesDefaultFirst, l)
                }
        }
@@ -927,17 +955,48 @@ func (c Configs) GetByLang(lang string) config.AllProvider {
        return nil
 }
 
+func newDefaultConfig() *Config {
+       return &Config{
+               Taxonomies: map[string]string{"tag": "tags", "category": "categories"},
+               Sitemap:    config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"},
+               RootConfig: RootConfig{
+                       Environment:          hugo.EnvironmentProduction,
+                       TitleCaseStyle:       "AP",
+                       PluralizeListTitles:  true,
+                       CapitalizeListTitles: true,
+                       StaticDir:            []string{"static"},
+                       SummaryLength:        70,
+                       Timeout:              "60s",
+
+                       CommonDirs: config.CommonDirs{
+                               ArcheTypeDir: "archetypes",
+                               ContentDir:   "content",
+                               ResourceDir:  "resources",
+                               PublishDir:   "public",
+                               ThemesDir:    "themes",
+                               AssetDir:     "assets",
+                               LayoutDir:    "layouts",
+                               I18nDir:      "i18n",
+                               DataDir:      "data",
+                       },
+               },
+       }
+}
+
 // fromLoadConfigResult creates a new Config from res.
 func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
        if !res.Cfg.IsSet("languages") {
                // We need at least one
                lang := res.Cfg.GetString("defaultContentLanguage")
+               if lang == "" {
+                       lang = "en"
+               }
                res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
        }
        bcfg := res.BaseConfig
        cfg := res.Cfg
 
-       all := &Config{}
+       all := newDefaultConfig()
 
        err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
        if err != nil {
@@ -947,6 +1006,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
        langConfigMap := make(map[string]*Config)
 
        languagesConfig := cfg.GetStringMap("languages")
+
        var isMultihost bool
 
        if err := all.CompileConfig(logger); err != nil {
index cae04ba8546a11a55bca42430abdefd39f2fb967..03394720302a834e6c2e8d8c335fcd1e98cbc400 100644 (file)
@@ -5,6 +5,7 @@ import (
        "testing"
 
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/common/hugo"
        "github.com/gohugoio/hugo/config/allconfig"
        "github.com/gohugoio/hugo/hugolib"
        "github.com/gohugoio/hugo/media"
@@ -234,3 +235,62 @@ baseURL = "https://example.com"
        b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
        b.Assert(len(s), qt.Equals, 6)
 }
+
+func TestMergeDeep(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+theme = ["theme1", "theme2"]
+_merge = "deep"
+-- themes/theme1/hugo.toml --
+[sitemap]
+filename = 'mysitemap.xml'
+[services]
+[services.googleAnalytics]
+id = 'foo bar'
+[taxonomies]
+  foo = 'bars'
+-- themes/theme2/config/_default/hugo.toml --
+[taxonomies]
+  bar = 'baz'
+-- layouts/home.html --
+GA ID: {{ site.Config.Services.GoogleAnalytics.ID }}.
+
+`
+
+       b := hugolib.Test(t, files)
+
+       conf := b.H.Configs
+       base := conf.Base
+
+       b.Assert(base.Environment, qt.Equals, hugo.EnvironmentProduction)
+       b.Assert(base.BaseURL, qt.Equals, "https://example.com")
+       b.Assert(base.Sitemap.Filename, qt.Equals, "mysitemap.xml")
+       b.Assert(base.Taxonomies, qt.DeepEquals, map[string]string{"bar": "baz", "foo": "bars"})
+
+       b.AssertFileContent("public/index.html", "GA ID: foo bar.")
+}
+
+func TestDefaultConfigLanguageBlankWhenNoEnglishExists(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+[languages]
+[languages.nn]
+weight = 20
+[languages.sv]
+weight = 10
+[languages.sv.taxonomies]
+  tag = "taggar"
+-- layouts/all.html --
+All.
+`
+
+       b := hugolib.Test(t, files)
+
+       b.Assert(b.H.Conf.DefaultContentLanguage(), qt.Equals, "sv")
+}
index 0bf8508d96cef6b4d2eee3c5a410e2e4dd12c0b3..1cfa1afc4e01c624aef2a16eb9aa55a49c141baf 100644 (file)
@@ -249,14 +249,18 @@ var allDecoderSetups = map[string]decodeWeight{
                key: "sitemap",
                decode: func(d decodeWeight, p decodeConfig) error {
                        var err error
-                       p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
+                       if p.p.IsSet(d.key) {
+                               p.c.Sitemap, err = config.DecodeSitemap(p.c.Sitemap, p.p.GetStringMap(d.key))
+                       }
                        return err
                },
        },
        "taxonomies": {
                key: "taxonomies",
                decode: func(d decodeWeight, p decodeConfig) error {
-                       p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+                       if p.p.IsSet(d.key) {
+                               p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+                       }
                        return nil
                },
        },
@@ -306,15 +310,17 @@ var allDecoderSetups = map[string]decodeWeight{
                        }
 
                        // Validate defaultContentLanguage.
-                       var found bool
-                       for lang := range p.c.Languages {
-                               if lang == p.c.DefaultContentLanguage {
-                                       found = true
-                                       break
+                       if p.c.DefaultContentLanguage != "" {
+                               var found bool
+                               for lang := range p.c.Languages {
+                                       if lang == p.c.DefaultContentLanguage {
+                                               found = true
+                                               break
+                                       }
+                               }
+                               if !found {
+                                       return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
                                }
-                       }
-                       if !found {
-                               return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
                        }
 
                        return nil
index 9289cf294f293af0775c0644129942e9bea83f33..2d9185f6f52a15f458696e69d322deffdbe2f226 100644 (file)
@@ -159,63 +159,9 @@ func (l configLoader) applyConfigAliases() error {
 
 func (l configLoader) applyDefaultConfig() error {
        defaultSettings := maps.Params{
-               "baseURL":                              "",
-               "cleanDestinationDir":                  false,
-               "watch":                                false,
-               "contentDir":                           "content",
-               "resourceDir":                          "resources",
-               "publishDir":                           "public",
-               "publishDirOrig":                       "public",
-               "themesDir":                            "themes",
-               "assetDir":                             "assets",
-               "layoutDir":                            "layouts",
-               "i18nDir":                              "i18n",
-               "dataDir":                              "data",
-               "archetypeDir":                         "archetypes",
-               "configDir":                            "config",
-               "staticDir":                            "static",
-               "buildDrafts":                          false,
-               "buildFuture":                          false,
-               "buildExpired":                         false,
-               "params":                               maps.Params{},
-               "environment":                          hugo.EnvironmentProduction,
-               "uglyURLs":                             false,
-               "verbose":                              false,
-               "ignoreCache":                          false,
-               "canonifyURLs":                         false,
-               "relativeURLs":                         false,
-               "removePathAccents":                    false,
-               "titleCaseStyle":                       "AP",
-               "taxonomies":                           maps.Params{"tag": "tags", "category": "categories"},
-               "permalinks":                           maps.Params{},
-               "sitemap":                              maps.Params{"priority": -1, "filename": "sitemap.xml"},
-               "menus":                                maps.Params{},
-               "disableLiveReload":                    false,
-               "pluralizeListTitles":                  true,
-               "capitalizeListTitles":                 true,
-               "forceSyncStatic":                      false,
-               "footnoteAnchorPrefix":                 "",
-               "footnoteReturnLinkContents":           "",
-               "newContentEditor":                     "",
-               "paginate":                             0,  // Moved into the paginator struct in Hugo v0.128.0.
-               "paginatePath":                         "", // Moved into the paginator struct in Hugo v0.128.0.
-               "summaryLength":                        70,
-               "rssLimit":                             -1,
-               "sectionPagesMenu":                     "",
-               "disablePathToLower":                   false,
-               "hasCJKLanguage":                       false,
-               "enableEmoji":                          false,
-               "defaultContentLanguage":               "en",
-               "defaultContentLanguageInSubdir":       false,
-               "enableMissingTranslationPlaceholders": false,
-               "enableGitInfo":                        false,
-               "ignoreFiles":                          make([]string, 0),
-               "disableAliases":                       false,
-               "debug":                                false,
-               "disableFastRender":                    false,
-               "timeout":                              "60s",
-               "timeZone":                             "",
-               "enableInlineShortcodes":               false,
+               // These dirs are used early/before we build the config struct.
+               "themesDir": "themes",
+               "configDir": "config",
        }
 
        l.cfg.SetDefaults(defaultSettings)
index 3dfd9b40969163915bc618c7bf19acc697d9bf99..9470786724fc7471880b09ea6244c5080d74c97e 100644 (file)
@@ -17,6 +17,7 @@ import (
        "fmt"
        "net/http"
        "regexp"
+       "slices"
        "sort"
        "strings"
 
@@ -28,7 +29,6 @@ import (
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/mitchellh/mapstructure"
        "github.com/spf13/cast"
-       "slices"
 )
 
 type BaseConfig struct {
index f302244d4aa923eb08995b3b3e60893bfb2b6545..f9d5e1a6e6cede46a91f9afb37740a9bd1ed466d 100644 (file)
@@ -101,6 +101,9 @@ func DecodeConfig(cfg config.Provider) (c Config, err error) {
 
        if c.RSS.Limit == 0 {
                c.RSS.Limit = cfg.GetInt(rssLimitKey)
+               if c.RSS.Limit == 0 {
+                       c.RSS.Limit = -1
+               }
        }
 
        return
index cbf821ee76a321b5cdfc2fd7f121c94c68d72104..4275fb02aa106685d2ab0b8ff7bd3f3a37d171c9 100644 (file)
@@ -475,7 +475,7 @@ name = "menu-theme"
                })
        })
 
-       // Issue #8724
+       // Issue #8724 ##13643
        for _, mergeStrategy := range []string{"none", "shallow"} {
                c.Run(fmt.Sprintf("Merge with sitemap config in theme, mergestrategy %s", mergeStrategy), func(c *qt.C) {
                        smapConfigTempl := `[sitemap]
@@ -495,7 +495,7 @@ name = "menu-theme"
                                b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Disable: false, Priority: -1, Filename: "sitemap.xml"})
                                b.AssertFileContent("public/sitemap.xml", "schemas/sitemap")
                        } else {
-                               b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Disable: false, Priority: -1, Filename: "sitemap.xml"})
+                               b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Disable: false, Priority: 0.5, Filename: "sitemap.xml"})
                                b.AssertFileContent("public/sitemap.xml", "<changefreq>monthly</changefreq>")
                        }
                })