]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Misc YAML adjustments
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 18 Oct 2025 15:42:43 +0000 (17:42 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 20 Oct 2025 14:05:05 +0000 (16:05 +0200)
Closes #14067

common/maps/params.go
config/allconfig/allconfig.go
config/allconfig/load.go
config/defaultConfigProvider.go
go.mod
go.sum
hugolib/config_test.go
hugolib/page__meta.go
hugolib/site.go
navigation/menu.go
related/inverted_index.go

index 819f796e4bab6228e9a0fa23ceae6eb3d5622662..aa1dce20c75cc1b5c049b81fefc544e317c61f51 100644 (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)
index 3366defd78c0fae23be299568796b2cf2172cace..c21a18f94fa0be958d0ce39b8998a742c0ae1149 100644 (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))
 
index 5466f678b9c8ac2b3d15ca7f4cff187dbc7e2fa5..318a1700a2d4e7a489e026ea632c4f93ce856347 100644 (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 {
index 8c1d63851c5392b8e31261db89c182b1680e9ad4..11cadbbbad687e740aff1d9aed8365f16ffa65f7 100644 (file)
 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 {
diff --git a/go.mod b/go.mod
index 440536bb0c36747a93cedc243dc0ec78eadc9028..aecdec0ee44a64bb0acf3ab1c3adbcc7e2424d40 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index cfe459fd8163937b98a62ed28023a2a29f19c956..ee5bc631a9b99dc609382bf8ff2d95a98d1d6769 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -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=
index 635283836b8a5bb3a5f8f4f84b5a627956749bf5..99dbac0022913501a086b556398ec51f514bf48f 100644 (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]]|")
+
+       }
+}
index f04e0bd56eed92aab44b68fecddeb232445e624c..00251186a01d1a8d01a4259c39e3090436cd4229 100644 (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"
 
index 247e1cff9a5220cb9c4c9e8ad9f62327a94bd33b..3c89b1ee3f8e235db26d7f3d906709318fb8e3d8 100644 (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 {
index a971f2e7441a45a54ff17180e7bbc631398650aa..5d4c5b93eaded726474ba8cde3cf32f75e3da59c 100644 (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()
index b8f1ad3e2b214cde65d88a48cd6aff8e8b753076..ebed77baa7848a00a3ece72d8d99e61ed3f3a042 100644 (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"