shithub: hugo

Download patch

ref: 7e223b3baaef68d6e6f99e28f162362c81deffba
parent: 8a6e70605350255920100c5c085bb9ea6576d972
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Tue Nov 24 09:11:42 EST 2020

Allow setting the delimiter used for setting config via OS env, e.g. HUGO_

Fixes #7829

--- a/common/maps/params.go
+++ b/common/maps/params.go
@@ -84,7 +84,7 @@
 }
 
 func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {
-	keySegments := strings.Split(strings.ToLower(keyStr), separator)
+	keySegments := strings.Split(keyStr, separator)
 	if len(keySegments) == 0 {
 		return nil, "", nil, nil
 	}
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -427,6 +427,8 @@
 To set config params, prefix the name with `HUGO_PARAMS_`
 {{% /note %}}
 
+{{< new-in "0.79.0" >}} If you are using snake_cased variable names, the above will not work, so since Hugo 0.79.0 Hugo determines the delimiter to use by the first character after `HUGO`. This allows you to define environment variables on the form `HUGOxPARAMSxAPI_KEY=abcdefgh`, using any [allowed](https://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names#:~:text=So%20names%20may%20contain%20any,not%20begin%20with%20a%20digit.) delimiter.
+
 {{< todo >}}
 Test and document setting params via JSON env var.
 {{< /todo >}}
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -18,6 +18,8 @@
 	"path/filepath"
 	"strings"
 
+	"github.com/gohugoio/hugo/common/types"
+
 	"github.com/gobwas/glob"
 	hglob "github.com/gohugoio/hugo/hugofs/glob"
 
@@ -166,45 +168,59 @@
 		}
 	}
 
+	const delim = "__env__delim"
+
 	// Apply environment overrides
 	if len(d.Environ) > 0 {
-		// Extract all that start with the HUGO_ prefix
-		const hugoEnvPrefix = "HUGO_"
-		var hugoEnv []string
+		// Extract all that start with the HUGO prefix.
+		// The delimiter is the following rune, usually "_".
+		const hugoEnvPrefix = "HUGO"
+		var hugoEnv []types.KeyValueStr
 		for _, v := range d.Environ {
 			key, val := config.SplitEnvVar(v)
 			if strings.HasPrefix(key, hugoEnvPrefix) {
-				hugoEnv = append(hugoEnv, strings.ToLower(strings.TrimPrefix(key, hugoEnvPrefix)), val)
+				delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
+				if len(delimiterAndKey) < 2 {
+					continue
+				}
+				// Allow delimiters to be case sensitive.
+				// It turns out there isn't that many allowed special
+				// chars in environment variables when used in Bash and similar,
+				// so variables on the form HUGOxPARAMSxFOO=bar is one option.
+				key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
+				key = strings.ToLower(key)
+				hugoEnv = append(hugoEnv, types.KeyValueStr{
+					Key:   key,
+					Value: val,
+				})
+
 			}
 		}
 
-		if len(hugoEnv) > 0 {
-			for i := 0; i < len(hugoEnv); i += 2 {
-				key, valStr := strings.ToLower(hugoEnv[i]), hugoEnv[i+1]
+		for _, env := range hugoEnv {
+			existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, v.Get)
+			if err != nil {
+				return v, configFiles, err
+			}
 
-				existing, nestedKey, owner, err := maps.GetNestedParamFn(key, "_", v.Get)
+			if existing != nil {
+				val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
 				if err != nil {
-					return v, configFiles, err
+					continue
 				}
 
-				if existing != nil {
-					val, err := metadecoders.Default.UnmarshalStringTo(valStr, existing)
-					if err != nil {
-						continue
-					}
-
-					if owner != nil {
-						owner[nestedKey] = val
-					} else {
-						v.Set(key, val)
-					}
-				} else if nestedKey != "" {
-					owner[nestedKey] = valStr
+				if owner != nil {
+					owner[nestedKey] = val
 				} else {
-					v.Set(key, valStr)
+					v.Set(env.Key, val)
 				}
+			} else if nestedKey != "" {
+				owner[nestedKey] = env.Value
+			} else {
+				v.Set(env.Key, env.Value)
 			}
 		}
+
 	}
 
 	// We made this a Glob pattern in Hugo 0.75, we don't need both.
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -492,6 +492,11 @@
 floatSlice = [3.14, 5.19]
 stringSlice = ["a", "b"]
 
+[params]
+[params.api_config]
+api_key="default_key"
+another_key="default another_key"
+
 [imaging]
 anchor = "smart"
 quality = 75 
@@ -508,6 +513,10 @@
 		"HUGO_STRINGSLICE", `["c", "d"]`,
 		"HUGO_INTSLICE", `[5, 8, 9]`,
 		"HUGO_FLOATSLICE", `[5.32]`,
+		// https://github.com/gohugoio/hugo/issues/7829
+		"HUGOxPARAMSxAPI_CONFIGxAPI_KEY", "new_key",
+		// Delimiters are case sensitive.
+		"HUGOxPARAMSxAPI_CONFIGXANOTHER_KEY", "another_key",
 	)
 
 	b.Build(BuildCfg{})
@@ -523,5 +532,7 @@
 	c.Assert(cfg.Get("stringSlice"), qt.DeepEquals, []interface{}{"c", "d"})
 	c.Assert(cfg.Get("floatSlice"), qt.DeepEquals, []interface{}{5.32})
 	c.Assert(cfg.Get("intSlice"), qt.DeepEquals, []interface{}{5, 8, 9})
+	c.Assert(cfg.Get("params.api_config.api_key"), qt.Equals, "new_key")
+	c.Assert(cfg.Get("params.api_config.another_key"), qt.Equals, "default another_key")
 
 }