Using an interface allows people to use their favourite flag system
with viper without being restricted to the semantics of pflag or the
standard library.
This change introduce two new functions `BindFlagValues` and
`BindFlagValue` that behave like `BindFlags` and `BindFlag` but using
the new interface as values.
This change also introduces two internal structures to transform
`*pflag.FlagSet` and `*pflag.Flag` into the new interface. This way,
viper keeps working as expected for people that are currently using the
pflag package without breaking backwards compatibility.
Signed-off-by: David Calavera <david.calavera@gmail.com>
This patch refactors the IsSet function to examine the keys in order
to see if a key is set instead of simply checking if a value is nil.
This change is necessary due to the fact that default values via
flag bindings will result in the old logic always being true for
the IsSet function due to a type's default value such as 0 for an
integer or an empty string for a string. While a type's default
value may be preferable when getting the value for a key, it
results in a false positive when determining if a key is actually
set. This change enables users to detect whether a key is set by
only returning a flag's value if it has changed.
This patch alters the way flags are handled to coincide with the
documentation on the Viper README. The documentation indicated that flag
bindings were late, when in fact they were very, very early. This patch
changes flag bindings to behave as late bindings.
This patch adds a feature, if enabled, will infer a value's type from
its default value no matter from where else the value is set. This is
particularly important when working with environment variables. For
example:
package main
import (
"fmt"
"os"
"github.com/spf13/viper"
)
func print(name string, val interface{}) {
fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val)
}
func main() {
viper.BindEnv("mykey", "MYPREFIX_MYKEY")
viper.SetDefault("mykey", []string{})
os.Setenv("MYPREFIX_MYKEY", "a b c")
v1 := viper.GetStringSlice("mykey")
v2 := viper.Get("mykey")
print("v1", v1)
print("v2", v2)
}
When this program is executed the following is emitted:
[0]akutz@pax:ex$ ./ex1
v1 []string [a b c]
v2 string a b c
[0]akutz@pax:ex$
You may wonder, why is this important? Just use the GetStringSlice
function. Well, it *becomes* important when dealing with marshaling.
If we update the above program to this:
package main
import (
"fmt"
"os"
"github.com/spf13/viper"
)
type Data struct {
MyKey []string
}
func print(name string, val interface{}) {
fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val)
}
func main() {
viper.BindEnv("mykey", "MYPREFIX_MYKEY")
viper.SetDefault("mykey", []string{})
os.Setenv("MYPREFIX_MYKEY", "a b c")
v1 := viper.GetStringSlice("mykey")
v2 := viper.Get("mykey")
print("v1", v1)
print("v2", v2)
d := &Data{}
viper.Marshal(d)
print("d.MyKey", d.MyKey)
}
Now we can see the issue when we execute the updated program:
[0]akutz@pax:ex$ ./ex2
v1 []string [a b c]
v2 string a b c
d.MyKey []string []
[0]akutz@pax:ex$
The marshalled data structure's field MyKey is empty when in fact it
should have a string slice equal to, in value, []string {"a", "b",
"c"}.
The problem is that viper's Marshal function calls AllSettings which
ultimately uses the Get function. The Get function does try to infer
the value's type, but it does so using the type of the value retrieved
using this logic:
Get has the behavior of returning the value associated with the
first place from where it is set. Viper will check in the
following order:
* override
* flag
* env
* config file
* key/value store
* default
While the above order is the one we want when retrieving the values,
this patch enables users to decide if it's the order they want to be
used when inferring a value's type. To that end the function
SetTypeByDefaultValue is introduced. When SetTypeByDefaultValue(true)
is called, a call to the Get function will now first check a key's
default value, if set, when inferring a value's type. This is
demonstrated using a modified version of the same program above:
package main
import (
"fmt"
"os"
"github.com/spf13/viper"
)
type Data struct {
MyKey []string
}
func print(name string, val interface{}) {
fmt.Printf("%-15[1]s%-15[2]T%[2]v\n", name, val)
}
func main() {
viper.BindEnv("mykey", "MYPREFIX_MYKEY")
viper.SetDefault("mykey", []string{})
os.Setenv("MYPREFIX_MYKEY", "a b c")
v1 := viper.GetStringSlice("mykey")
v2 := viper.Get("mykey")
print("v1", v1)
print("v2", v2)
d1 := &Data{}
viper.Marshal(d1)
print("d1.MyKey", d1.MyKey)
viper.SetTypeByDefaultValue(true)
d2 := &Data{}
viper.Marshal(d2)
print("d2.MyKey", d2.MyKey)
}
Now the following is emitted:
[0]akutz@pax:ex$ ./ex3
v1 []string [a b c]
v2 string a b c
d1.MyKey []string []
d2.MyKey []string [a b c]
[0]akutz@pax:ex$
This reverts commit 8d9577a72e.
The commit is reasonable enough, but this is a major breaking change for Hugo.
We have to figure out how to handle this before we introduce this one.
See https://github.com/spf13/hugo/issues/1129