Misc YAML adjustments

Closes #14067
This commit is contained in:
Bjørn Erik Pedersen
2025-10-18 17:42:43 +02:00
parent a8e0ca9254
commit bd50c9c7e7
11 changed files with 128 additions and 24 deletions

View File

@@ -14,7 +14,9 @@
package maps
import (
"errors"
"fmt"
xmaps "maps"
"strings"
"github.com/spf13/cast"
@@ -101,7 +103,6 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
for k, v := range pp {
if k == MergeStrategyKey {
continue
}
@@ -115,6 +116,9 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
}
}
} else if !noUpdate {
if vvv, ok := v.(Params); ok {
v = xmaps.Clone(vvv)
}
p[k] = v
}
@@ -266,9 +270,17 @@ func CleanConfigStringMapString(m map[string]string) map[string]string {
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
// map[string]any.
func CleanConfigStringMap(m map[string]any) map[string]any {
return doCleanConfigStringMap(m, 0)
}
func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
if len(m) == 0 {
return m
}
const maxDepth = 1000
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
if _, found := m[MergeStrategyKey]; !found {
return m
}
@@ -280,9 +292,9 @@ func CleanConfigStringMap(m map[string]any) map[string]any {
}
switch v2 := v.(type) {
case map[string]any:
m2[k] = CleanConfigStringMap(v2)
m2[k] = doCleanConfigStringMap(v2, depth+1)
case Params:
var p Params = CleanConfigStringMap(v2)
var p Params = doCleanConfigStringMap(v2, depth+1)
m2[k] = p
case map[string]string:
m2[k] = CleanConfigStringMapString(v2)

View File

@@ -56,7 +56,7 @@ import (
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/spf13/afero"
xmaps "golang.org/x/exp/maps"
xmaps "maps"
)
// InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
@@ -1054,6 +1054,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
mergedConfig := config.New()
var differentRootKeys []string
switch x := v.(type) {
case nil:
case maps.Params:
_, found := x["params"]
if !found {
@@ -1129,6 +1130,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
langConfigMap[k] = clone
case maps.ParamsMergeStrategy:
default:
panic(fmt.Sprintf("unknown type in languages config: %T", v))

View File

@@ -40,7 +40,13 @@ import (
//lint:ignore ST1005 end user message.
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")
func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("failed to load config: %v", r)
}
}()
if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
d.Environ = os.Environ()
}
@@ -59,7 +65,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
return nil, fmt.Errorf("failed to load config: %w", err)
}
configs, err := fromLoadConfigResult(d.Fs, d.Logger, res)
configs, err = fromLoadConfigResult(d.Fs, d.Logger, res)
if err != nil {
return nil, fmt.Errorf("failed to create config from result: %w", err)
}
@@ -93,7 +99,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
loggers.SetGlobalLogger(d.Logger)
return configs, nil
return
}
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
@@ -538,11 +544,12 @@ func (l configLoader) loadConfig(configName string) (string, error) {
return filename, nil
}
func (l configLoader) deleteMergeStrategies() {
func (l configLoader) deleteMergeStrategies() (err error) {
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
params[len(params)-1].Params.DeleteMergeStrategy()
return false
})
return
}
func (l configLoader) wrapFileError(err error, filename string) error {

View File

@@ -14,11 +14,13 @@
package config
import (
"errors"
"fmt"
"slices"
"strings"
"sync"
xmaps "golang.org/x/exp/maps"
xmaps "maps"
"github.com/spf13/cast"
@@ -237,12 +239,16 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
func (c *defaultConfigProvider) Keys() []string {
c.mu.RLock()
defer c.mu.RUnlock()
return xmaps.Keys(c.root)
return slices.Collect(xmaps.Keys(c.root))
}
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
var walk func(params ...maps.KeyParams)
walk = func(params ...maps.KeyParams) {
maxDepth := 1000
var walk func(depth int, params ...maps.KeyParams)
walk = func(depth int, params ...maps.KeyParams) {
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
if walkFn(params...) {
return
}
@@ -253,11 +259,11 @@ func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams)
paramsplus1 := make([]maps.KeyParams, i+1)
copy(paramsplus1, params)
paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
walk(paramsplus1...)
walk(depth+1, paramsplus1...)
}
}
}
walk(maps.KeyParams{Key: "", Params: c.root})
walk(0, maps.KeyParams{Key: "", Params: c.root})
}
func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {

1
go.mod
View File

@@ -75,7 +75,6 @@ require (
github.com/yuin/goldmark-emoji v1.0.6
go.uber.org/automaxprocs v1.5.3
gocloud.dev v0.43.0
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
golang.org/x/image v0.32.0
golang.org/x/mod v0.29.0
golang.org/x/net v0.46.0

4
go.sum
View File

@@ -267,8 +267,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018141905-f04b60c79bd1 h1:95D/7I2Vv3N9icWWlayjoxp7rSzjcr6PaGXLw8DZzgY=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018141905-f04b60c79bd1/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eRwuAnt/zKbGqlFO2ZszpIe24YpOVLf+N+4I=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg=
@@ -594,8 +592,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=

View File

@@ -1570,3 +1570,82 @@ title: "P1 us"
`
Test(t, files)
}
func TestConfigYAMLAnchorsMerge(t *testing.T) {
t.Parallel()
files := `
-- hugo.yaml --
definitions:
params: &params
p1: p1alias
theme: "mytheme"
defaultContentLanguage: en
defaultContentLanguageInSubdir: true
params:
<<: *params
languages:
en:
weight: 1
no:
weight: 2
params:
<<: *params
p2: p2no
sv:
weight: 3
params: *params
-- layouts/all.html --
Params: {{ site.Params }}|
-- themes/mytheme/hugo.yaml --
definitions:
params: &params
p1: p1aliastheme
p2: p2aliastheme
params: *params
`
b := Test(t, files)
b.AssertFileContent("public/en/index.html", "Params: map[p1:p1alias p2:p2aliastheme]|")
b.AssertFileContent("public/no/index.html", "Params: map[p1:p1alias p2:p2no]|")
b.AssertFileContent("public/sv/index.html", "Params: map[p1:p1alias p2:p2aliastheme]|")
}
func TestConfigYAMLAnchorsCyclicReference(t *testing.T) {
t.Parallel()
files := `
-- hugo.yaml --
definitions:
params: &params
p1: p1alias
params:
p3: *params
languages:
en:
weight: 1
sv:
weight: 2
params: *params
-- layouts/all.html --
Params: {{ site.Params }}|
`
for range 3 {
b := Test(t, files)
b.AssertFileContent("public/index.html", "Params: map[p3:map[p1:p1alias]]|")
b.AssertFileContent("public/sv/index.html", "Params: map[p1:p1alias p3:map[p1:p1alias]]|")
}
}

View File

@@ -21,11 +21,12 @@ import (
"strings"
"time"
xmaps "maps"
"github.com/bep/logg"
"github.com/gobuffalo/flect"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/converter"
xmaps "golang.org/x/exp/maps"
"github.com/gohugoio/hugo/source"

View File

@@ -23,6 +23,7 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"sort"
"strings"
"sync"
@@ -48,9 +49,10 @@ import (
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/resources"
xmaps "maps"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/tpl/tplimplinit"
xmaps "golang.org/x/exp/maps"
// Loads the template funcs namespaces.
@@ -999,7 +1001,7 @@ func (w *WhatChanged) Changes() []identity.Identity {
if w == nil || w.ids == nil {
return nil
}
return xmaps.Keys(w.ids)
return slices.Collect(xmaps.Keys(w.ids))
}
func (w *WhatChanged) Drain() []identity.Identity {

View File

@@ -16,6 +16,7 @@ package navigation
import (
"html/template"
"slices"
"sort"
"github.com/gohugoio/hugo/common/maps"
@@ -25,7 +26,6 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
"slices"
)
var smc = newMenuCache()

View File

@@ -24,7 +24,7 @@ import (
"sync"
"time"
xmaps "golang.org/x/exp/maps"
xmaps "maps"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/maps"