mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-25 21:21:22 +03:00
config: Fix _merge issue when key doesn't exist on the left side
Fixes #13643 Fixes #13646
This commit is contained in:
parent
61a286595e
commit
179aea11ac
7 changed files with 165 additions and 90 deletions
|
@ -800,30 +800,58 @@ func (c *Configs) IsZero() bool {
|
||||||
|
|
||||||
func (c *Configs) Init() error {
|
func (c *Configs) Init() error {
|
||||||
var languages langs.Languages
|
var languages langs.Languages
|
||||||
defaultContentLanguage := c.Base.DefaultContentLanguage
|
|
||||||
for k, v := range c.LanguageConfigMap {
|
var langKeys []string
|
||||||
|
var hasEn bool
|
||||||
|
|
||||||
|
const en = "en"
|
||||||
|
|
||||||
|
for k := range c.LanguageConfigMap {
|
||||||
|
langKeys = append(langKeys, k)
|
||||||
|
if k == en {
|
||||||
|
hasEn = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ki < kj
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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]
|
languageConf := v.Languages[k]
|
||||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
language, err := langs.NewLanguage(k, c.Base.DefaultContentLanguage, v.TimeZone, languageConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
languages = append(languages, language)
|
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]
|
|
||||||
if li.Weight != lj.Weight {
|
|
||||||
return li.Weight < lj.Weight
|
|
||||||
}
|
|
||||||
return li.Lang < lj.Lang
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, l := range languages {
|
|
||||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out disabled languages.
|
// Filter out disabled languages.
|
||||||
var n int
|
var n int
|
||||||
for _, l := range languages {
|
for _, l := range languages {
|
||||||
|
@ -836,12 +864,12 @@ func (c *Configs) Init() error {
|
||||||
|
|
||||||
var languagesDefaultFirst langs.Languages
|
var languagesDefaultFirst langs.Languages
|
||||||
for _, l := range languages {
|
for _, l := range languages {
|
||||||
if l.Lang == defaultContentLanguage {
|
if l.Lang == c.Base.DefaultContentLanguage {
|
||||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, l := range languages {
|
for _, l := range languages {
|
||||||
if l.Lang != defaultContentLanguage {
|
if l.Lang != c.Base.DefaultContentLanguage {
|
||||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -927,17 +955,48 @@ func (c Configs) GetByLang(lang string) config.AllProvider {
|
||||||
return nil
|
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.
|
// fromLoadConfigResult creates a new Config from res.
|
||||||
func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
|
func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
|
||||||
if !res.Cfg.IsSet("languages") {
|
if !res.Cfg.IsSet("languages") {
|
||||||
// We need at least one
|
// We need at least one
|
||||||
lang := res.Cfg.GetString("defaultContentLanguage")
|
lang := res.Cfg.GetString("defaultContentLanguage")
|
||||||
|
if lang == "" {
|
||||||
|
lang = "en"
|
||||||
|
}
|
||||||
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
|
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
|
||||||
}
|
}
|
||||||
bcfg := res.BaseConfig
|
bcfg := res.BaseConfig
|
||||||
cfg := res.Cfg
|
cfg := res.Cfg
|
||||||
|
|
||||||
all := &Config{}
|
all := newDefaultConfig()
|
||||||
|
|
||||||
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
|
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -947,6 +1006,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||||
langConfigMap := make(map[string]*Config)
|
langConfigMap := make(map[string]*Config)
|
||||||
|
|
||||||
languagesConfig := cfg.GetStringMap("languages")
|
languagesConfig := cfg.GetStringMap("languages")
|
||||||
|
|
||||||
var isMultihost bool
|
var isMultihost bool
|
||||||
|
|
||||||
if err := all.CompileConfig(logger); err != nil {
|
if err := all.CompileConfig(logger); err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
"github.com/gohugoio/hugo/config/allconfig"
|
"github.com/gohugoio/hugo/config/allconfig"
|
||||||
"github.com/gohugoio/hugo/hugolib"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
|
@ -234,3 +235,62 @@ baseURL = "https://example.com"
|
||||||
b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
|
b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
|
||||||
b.Assert(len(s), qt.Equals, 6)
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -249,14 +249,18 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||||
key: "sitemap",
|
key: "sitemap",
|
||||||
decode: func(d decodeWeight, p decodeConfig) error {
|
decode: func(d decodeWeight, p decodeConfig) error {
|
||||||
var err 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
|
return err
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"taxonomies": {
|
"taxonomies": {
|
||||||
key: "taxonomies",
|
key: "taxonomies",
|
||||||
decode: func(d decodeWeight, p decodeConfig) error {
|
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
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -306,15 +310,17 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate defaultContentLanguage.
|
// Validate defaultContentLanguage.
|
||||||
var found bool
|
if p.c.DefaultContentLanguage != "" {
|
||||||
for lang := range p.c.Languages {
|
var found bool
|
||||||
if lang == p.c.DefaultContentLanguage {
|
for lang := range p.c.Languages {
|
||||||
found = true
|
if lang == p.c.DefaultContentLanguage {
|
||||||
break
|
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
|
return nil
|
||||||
|
|
|
@ -159,63 +159,9 @@ func (l configLoader) applyConfigAliases() error {
|
||||||
|
|
||||||
func (l configLoader) applyDefaultConfig() error {
|
func (l configLoader) applyDefaultConfig() error {
|
||||||
defaultSettings := maps.Params{
|
defaultSettings := maps.Params{
|
||||||
"baseURL": "",
|
// These dirs are used early/before we build the config struct.
|
||||||
"cleanDestinationDir": false,
|
"themesDir": "themes",
|
||||||
"watch": false,
|
"configDir": "config",
|
||||||
"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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.cfg.SetDefaults(defaultSettings)
|
l.cfg.SetDefaults(defaultSettings)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/herrors"
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseConfig struct {
|
type BaseConfig struct {
|
||||||
|
|
|
@ -101,6 +101,9 @@ func DecodeConfig(cfg config.Provider) (c Config, err error) {
|
||||||
|
|
||||||
if c.RSS.Limit == 0 {
|
if c.RSS.Limit == 0 {
|
||||||
c.RSS.Limit = cfg.GetInt(rssLimitKey)
|
c.RSS.Limit = cfg.GetInt(rssLimitKey)
|
||||||
|
if c.RSS.Limit == 0 {
|
||||||
|
c.RSS.Limit = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -475,7 +475,7 @@ name = "menu-theme"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Issue #8724
|
// Issue #8724 ##13643
|
||||||
for _, mergeStrategy := range []string{"none", "shallow"} {
|
for _, mergeStrategy := range []string{"none", "shallow"} {
|
||||||
c.Run(fmt.Sprintf("Merge with sitemap config in theme, mergestrategy %s", mergeStrategy), func(c *qt.C) {
|
c.Run(fmt.Sprintf("Merge with sitemap config in theme, mergestrategy %s", mergeStrategy), func(c *qt.C) {
|
||||||
smapConfigTempl := `[sitemap]
|
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.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Disable: false, Priority: -1, Filename: "sitemap.xml"})
|
||||||
b.AssertFileContent("public/sitemap.xml", "schemas/sitemap")
|
b.AssertFileContent("public/sitemap.xml", "schemas/sitemap")
|
||||||
} else {
|
} 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>")
|
b.AssertFileContent("public/sitemap.xml", "<changefreq>monthly</changefreq>")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue