Repository: caarlos0/env (go)

Issue:
`UseFieldNameByDefault` doesn't support acronyms
First of all, this is a great feature 🚀

# Quick description of the issue
I have a field called `HTTPVersion`.
When using the `UseFieldNameByDefault` option, it translates it to `H_TT_P_VERSION`, when I expect it to be `HTTP_VERSION`.


# A demo of the issue
```console
basel@MM main1 % cat main.go 
package main

import (
        "fmt"
        "github.com/caarlos0/env/v10"
        "log"
)

type Config struct {
        HTTPVersion string
}

func main() {
        cfg := &Config{}
        opts := env.Options{
                UseFieldNameByDefault: true,
        }

        // Load env vars.
        if err := env.ParseWithOptions(cfg, opts); err != nil {
                log.Fatal(err)
        }

        // Print the loaded data.
        fmt.Printf("%+v\n", cfg)
}
basel@MM main1 % 
basel@MM main1 % go build -o app . && HTTP_VERSION=2.0 ./app
&{HTTPVersion:}
basel@MM main1 % go build -o app . && H_TT_P_VERSION=2.0 ./app
&{HTTPVersion:2.0}
basel@MM main1 % 
```


# Suggestion
Looks like it comes down to `toEnvName`.
I've tried a quick, simple and efficient fix, which will preserve acronyms (i.e. it will translate it to `HTTP_VERSION`).
I'll be happy to open a PR in case it's relevant.


Pull request:
fix: UseFieldNameByDefault when uppercased word in name
closes #295

Patch:
diff --git a/env.go b/env.go
index 5c9d806..99a9484 100644
--- a/env.go
+++ b/env.go
@@ -301,8 +301,16 @@ const underscore rune = '_'
 func toEnvName(input string) string {
 	var output []rune
 	for i, c := range input {
-		if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
-			output = append(output, underscore)
+		if c == underscore {
+			continue
+		}
+		if len(output) > 0 && unicode.IsUpper(c) {
+			if len(input) > i+1 {
+				peek := rune(input[i+1])
+				if unicode.IsLower(peek) || unicode.IsLower(rune(input[i-1])) {
+					output = append(output, underscore)
+				}
+			}
 		}
 		output = append(output, unicode.ToUpper(c))
 	}


Test patch:
diff --git a/env_test.go b/env_test.go
index 084bc29..65628ed 100644
--- a/env_test.go
+++ b/env_test.go
@@ -1742,32 +1742,43 @@ func TestComplePrefix(t *testing.T) {
 
 func TestNoEnvKey(t *testing.T) {
 	type Config struct {
-		Foo    string
-		FooBar string
-		bar    string
+		Foo      string
+		FooBar   string
+		HTTPPort int
+		bar      string
 	}
 	var cfg Config
 	isNoErr(t, ParseWithOptions(&cfg, Options{
 		UseFieldNameByDefault: true,
 		Environment: map[string]string{
-			"FOO":     "fooval",
-			"FOO_BAR": "foobarval",
+			"FOO":       "fooval",
+			"FOO_BAR":   "foobarval",
+			"HTTP_PORT": "10",
 		},
 	}))
 	isEqual(t, "fooval", cfg.Foo)
 	isEqual(t, "foobarval", cfg.FooBar)
+	isEqual(t, 10, cfg.HTTPPort)
 	isEqual(t, "", cfg.bar)
 }
 
 func TestToEnv(t *testing.T) {
 	for in, out := range map[string]string{
-		"Foo":      "FOO",
-		"FooBar":   "FOO_BAR",
-		"fooBar":   "FOO_BAR",
-		"Foo_Bar":  "FOO_BAR",
-		"Foo__Bar": "FOO__BAR",
+		"Foo":          "FOO",
+		"FooBar":       "FOO_BAR",
+		"FOOBar":       "FOO_BAR",
+		"Foo____Bar":   "FOO_BAR",
+		"fooBar":       "FOO_BAR",
+		"Foo_Bar":      "FOO_BAR",
+		"Foo__Bar":     "FOO_BAR",
+		"HTTPPort":     "HTTP_PORT",
+		"SSHPort":      "SSH_PORT",
+		"_SSH___Port_": "SSH_PORT",
+		"_PortHTTP":    "PORT_HTTP",
 	} {
-		isEqual(t, out, toEnvName(in))
+		t.Run(in, func(t *testing.T) {
+			isEqual(t, out, toEnvName(in))
+		})
 	}
 }