Fix: Getting the value of a StringToString pflag (#874) - viper - [fork] go viper port for 9front
 (HTM) git clone git@git.drkhsh.at/viper.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 3856c05f99260a778852dc112cdfc3ea3f0e1a9e
 (DIR) parent c6ee9808ab8547ad8021467022fbc955d750a7ea
 (HTM) Author: Trevor Foster <trevor.foster@hotmail.ca>
       Date:   Sat,  9 May 2020 05:38:39 -0400
       
       Fix: Getting the value of a StringToString pflag (#874)
       
       * add parsing for stringToString flags
       
       * add logic to return flags default if not val set, add a test
       
       * extract parsing into single func
       
       * add a few more cases
       
       * return nil if unable to parse instead of panicing
       
       * return map[string]interface in order to work with cast.ToStringMap
       
       * mostly copy pflags implementation of the conversion to a stringtostring
       Diffstat:
         M viper.go                            |      28 ++++++++++++++++++++++++++++
         M viper_test.go                       |      47 +++++++++++++++++++++++++++++++
       
       2 files changed, 75 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/viper.go b/viper.go
       @@ -1083,6 +1083,8 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
                                s = strings.TrimSuffix(s, "]")
                                res, _ := readAsCSV(s)
                                return cast.ToIntSlice(res)
       +                case "stringToString":
       +                        return stringToStringConv(flag.ValueString())
                        default:
                                return flag.ValueString()
                        }
       @@ -1158,6 +1160,8 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
                                        s = strings.TrimSuffix(s, "]")
                                        res, _ := readAsCSV(s)
                                        return cast.ToIntSlice(res)
       +                        case "stringToString":
       +                                return stringToStringConv(flag.ValueString())
                                default:
                                        return flag.ValueString()
                                }
       @@ -1177,6 +1181,30 @@ func readAsCSV(val string) ([]string, error) {
                return csvReader.Read()
        }
        
       +// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79
       +// alterations are: errors are swallowed, map[string]interface{} is returned in order to enable cast.ToStringMap
       +func stringToStringConv(val string) interface{} {
       +        val = strings.Trim(val, "[]")
       +        // An empty string would cause an empty map
       +        if len(val) == 0 {
       +                return map[string]interface{}{}
       +        }
       +        r := csv.NewReader(strings.NewReader(val))
       +        ss, err := r.Read()
       +        if err != nil {
       +                return nil
       +        }
       +        out := make(map[string]interface{}, len(ss))
       +        for _, pair := range ss {
       +                kv := strings.SplitN(pair, "=", 2)
       +                if len(kv) != 2 {
       +                        return nil
       +                }
       +                out[kv[0]] = kv[1]
       +        }
       +        return out
       +}
       +
        // IsSet checks to see if the key has been set in any of the data locations.
        // IsSet is case-insensitive for a key.
        func IsSet(key string) bool { return v.IsSet(key) }
 (DIR) diff --git a/viper_test.go b/viper_test.go
       @@ -970,6 +970,53 @@ func TestBindPFlag(t *testing.T) {
                assert.Equal(t, "testing_mutate", Get("testvalue"))
        }
        
       +func TestBindPFlagStringToString(t *testing.T) {
       +        tests := []struct {
       +                Expected map[string]string
       +                Value    string
       +        }{
       +                {map[string]string{}, ""},
       +                {map[string]string{"yo": "hi"}, "yo=hi"},
       +                {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"},
       +                {map[string]string{"yo": ""}, "yo="},
       +                {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"},
       +        }
       +
       +        v := New() // create independent Viper object
       +        defaultVal := map[string]string{}
       +        v.SetDefault("stringtostring", defaultVal)
       +
       +        for _, testValue := range tests {
       +                flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
       +                flagSet.StringToString("stringtostring", testValue.Expected, "test")
       +
       +                for _, changed := range []bool{true, false} {
       +                        flagSet.VisitAll(func(f *pflag.Flag) {
       +                                f.Value.Set(testValue.Value)
       +                                f.Changed = changed
       +                        })
       +
       +                        err := v.BindPFlags(flagSet)
       +                        if err != nil {
       +                                t.Fatalf("error binding flag set, %v", err)
       +                        }
       +
       +                        type TestMap struct {
       +                                StringToString map[string]string
       +                        }
       +                        val := &TestMap{}
       +                        if err := v.Unmarshal(val); err != nil {
       +                                t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
       +                        }
       +                        if changed {
       +                                assert.Equal(t, testValue.Expected, val.StringToString)
       +                        } else {
       +                                assert.Equal(t, defaultVal, val.StringToString)
       +                        }
       +                }
       +        }
       +}
       +
        func TestBoundCaseSensitivity(t *testing.T) {
                assert.Equal(t, "brown", Get("eyes"))