peterbourgon / ff

Flags-first package for configuration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feature request: camelCase flag to snake_case env

lmittmann opened this issue · comments

Great package. It would be nice if a camelCase flag names could be set by an env variable in snake_case.

Example:

fs := flag.NewFlagSet("my-program", flag.ContinueOnError)
rpcURL = fs.String("rpcURL", ...)

This works: $ RPCURL="https://..." my-program
This does not work (would be nice though): $ RPC_URL="https://..." my-program

I'm not aware of a way to reliably tokenize camel case identifiers in this way. If you know of one, please re-open with a link!

@peterbourgon you could e.g. this:

https://go.dev/play/p/6C-kW-qMWQT

func camelToSnake(camel string) string {
	snake := make([]byte, 0, len(camel))

	for i, c := range []byte(camel) {
		if isCapital(c) {
			if i > 0 && (!isCapital(camel[i-1]) || i+1 < len(camel) && !isCapital(camel[i+1])) {
				snake = append(snake, '_')
			}
			c += 'a' - 'A'
		}
		snake = append(snake, c)
	}
	return string(snake)
}

func isCapital(c byte) bool {
	return 'A' <= c && c <= 'Z'
}

func TestCamelToSnake(t *testing.T) {
	tests := []struct {
		Camel string
		Want  string
	}{
		{"camel", "camel"},
		{"Camel", "camel"},
		{"CamelCase", "camel_case"},
		{"camelCase", "camel_case"},
		{"URL", "url"},
		{"rpcURL", "rpc_url"},
		{"aaaBBBCcc", "aaa_bbb_ccc"},
		{"aaaAAAAaa", "aaa_aaa_aaa"},
		{"a1", "a1"},
		{"a1B2", "a1_b2"},
	}

	for _, test := range tests {
		t.Run(test.Camel, func(t *testing.T) {
			got := camelToSnake(test.Camel)
			if test.Want != got {
				t.Errorf("want %q, got %q", test.Want, got)
			}
		})
	}
}

Ehhh not sure it's OK to assume camel can be interpreted as raw bytes, and not convinced that some of those test cases necessarily tokenize in the way which is asserted. I appreciate the effort! But this is in my judgment too fragile.