peterbourgon / ff

Flags-first package for configuration
Apache License 2.0
1.34k stars 59 forks source link

feature request: camelCase flag to snake_case env #99

Closed lmittmann closed 1 year ago

lmittmann commented 1 year ago

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

peterbourgon commented 1 year ago

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!

lmittmann commented 1 year ago

@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)
            }
        })
    }
}
peterbourgon commented 1 year ago

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.