diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go index a40a02372..6b8a4568c 100644 --- a/config/allconfig/alldecoders.go +++ b/config/allconfig/alldecoders.go @@ -150,7 +150,7 @@ var allDecoderSetups = map[string]decodeWeight{ key: "outputs", decode: func(d decodeWeight, p decodeConfig) error { defaults := createDefaultOutputFormats(p.c.OutputFormats.Config) - m := p.p.GetStringMap("outputs") + m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs")) p.c.Outputs = make(map[string][]string) for k, v := range m { s := types.ToStringSlicePreserveString(v) diff --git a/config/allconfig/load.go b/config/allconfig/load.go index b9bb38aaf..8551039a0 100644 --- a/config/allconfig/load.go +++ b/config/allconfig/load.go @@ -293,11 +293,19 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error { } else { l.cfg.Set(env.Key, val) } - } else if nestedKey != "" { - owner[nestedKey] = env.Value } else { - // The container does not exist yet. - l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value) + if nestedKey != "" { + owner[nestedKey] = env.Value + } else { + var val any = env.Value + if _, ok := allDecoderSetups[env.Key]; ok { + // A map. + val, err = metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{}) + } + if err == nil { + l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), val) + } + } } } diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 100144ca1..9720522ad 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -1419,3 +1419,118 @@ Home. b.Assert(len(b.H.Sites), qt.Equals, 1) } + +func TestLoadConfigYamlEnvVar(t *testing.T) { + + defaultEnv := []string{`HUGO_OUTPUTS=home: ['json']`} + + runVariant := func(t testing.TB, files string, env []string) *IntegrationTestBuilder { + if env == nil { + env = defaultEnv + } + + b := NewIntegrationTestBuilder( + IntegrationTestConfig{ + T: t, + TxtarString: files, + Environ: env, + BuildCfg: BuildCfg{SkipRender: true}, + }, + ).Build() + + outputs := b.H.Configs.Base.Outputs + if env == nil { + home := outputs["home"] + b.Assert(home, qt.Not(qt.IsNil)) + b.Assert(home, qt.DeepEquals, []string{"json"}) + } + + return b + + } + + t.Run("with empty slice", func(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"] +[outputs] +home = ["html"] + + ` + b := runVariant(t, files, []string{`HUGO_OUTPUTS=section: []`}) + outputs := b.H.Configs.Base.Outputs + b.Assert(outputs, qt.DeepEquals, map[string][]string{ + "home": {"html"}, + "page": {"html"}, + "rss": {"rss"}, + "section": nil, + "taxonomy": {"html", "rss"}, + "term": {"html", "rss"}, + }) + + }) + + t.Run("with existing outputs", func(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"] +[outputs] +home = ["html"] + + ` + + runVariant(t, files, nil) + + }) + + { + t.Run("with existing outputs direct", func(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"] +[outputs] +home = ["html"] + + ` + runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"}) + + }) + } + + t.Run("without existing outputs", func(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"] + + ` + + runVariant(t, files, nil) + + }) + + t.Run("without existing outputs direct", func(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"] + ` + + runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"}) + + }) + +} diff --git a/parser/metadecoders/decoder.go b/parser/metadecoders/decoder.go index 93eb32e47..40b3a336c 100644 --- a/parser/metadecoders/decoder.go +++ b/parser/metadecoders/decoder.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/maps" "github.com/niklasfasching/go-org/org" xml "github.com/clbanning/mxj/v2" @@ -90,7 +91,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) { switch typ.(type) { case string: return data, nil - case map[string]any: + case map[string]any, maps.Params: format := d.FormatFromContentString(data) return d.UnmarshalToMap([]byte(data), format) case []any: