diff --git a/defaults.go b/defaults.go index b9f97966..65bdb13a 100644 --- a/defaults.go +++ b/defaults.go @@ -31,6 +31,14 @@ func dfault(d interface{}, given ...interface{}) interface{} { return given[0] } +// Similar to dfault but only considers nil as unset. 0, false, "" are considered set +func safeDefault(d interface{}, given ...interface{}) interface{} { + if given == nil || len(given) == 0 || given[0] == nil { + return d + } + return given[0] +} + // empty returns true if the given value has the zero value for its type. func empty(given interface{}) bool { g := reflect.ValueOf(given) @@ -69,6 +77,16 @@ func coalesce(v ...interface{}) interface{} { return nil } +// safeCoalesce returns the first non-nil value +func safeCoalesce(v ...interface{}) interface{} { + for _, val := range v { + if val != nil { + return val + } + } + return nil +} + // all returns true if empty(x) is false for all values x in the list. // If the list is empty, return true. func all(v ...interface{}) bool { diff --git a/defaults_test.go b/defaults_test.go index a35ebf62..4ed57813 100644 --- a/defaults_test.go +++ b/defaults_test.go @@ -30,6 +30,55 @@ func TestDefault(t *testing.T) { } } +func TestSafeDefault(t *testing.T) { + tpl := `{{"" | safeDefault "foo"}}` + if err := runt(tpl, ""); err != nil { + t.Error(err) + } + tpl = `{{safeDefault "foo" 234}}` + if err := runt(tpl, "234"); err != nil { + t.Error(err) + } + tpl = `{{safeDefault "foo" 2.34}}` + if err := runt(tpl, "2.34"); err != nil { + t.Error(err) + } + + tpl = `{{ .Nothing | safeDefault "123" }}` + if err := runt(tpl, "123"); err != nil { + t.Error(err) + } + tpl = `{{ safeDefault "123" }}` + if err := runt(tpl, "123"); err != nil { + t.Error(err) + } + + tpl = `{{ .Nothing | safeDefault true }}` + if err := runt(tpl, "true"); err != nil { + t.Error(err) + } + tpl = `{{ false | safeDefault true }}` + if err := runt(tpl, "false"); err != nil { + t.Error(err) + } + tpl = `{{ true | safeDefault false }}` + if err := runt(tpl, "true"); err != nil { + t.Error(err) + } + tpl = `{{ .Nothing | safeDefault 100 }}` + if err := runt(tpl, "100"); err != nil { + t.Error(err) + } + tpl = `{{ 0 | safeDefault 100 }}` + if err := runt(tpl, "0"); err != nil { + t.Error(err) + } + tpl = `{{ 55 | safeDefault 0 }}` + if err := runt(tpl, "55"); err != nil { + t.Error(err) + } +} + func TestEmpty(t *testing.T) { tpl := `{{if empty 1}}1{{else}}0{{end}}` if err := runt(tpl, "0"); err != nil { @@ -84,6 +133,27 @@ func TestCoalesce(t *testing.T) { } } +func TestSafeCoalesce(t *testing.T) { + tests := map[string]string{ + `{{ safeCoalesce 1 }}`: "1", + `{{ safeCoalesce "" 0 nil 2 }}`: "", + `{{ safeCoalesce nil 0 "" 2 }}`: "0", + `{{ $two := 2 }}{{ safeCoalesce nil $two "" 0 }}`: "2", + `{{ $two := 2 }}{{ safeCoalesce "" $two 0 0 0 }}`: "", + `{{ $two := 2 }}{{ safeCoalesce "" $two 3 4 5 }}`: "", + `{{ safeCoalesce }}`: "", + } + for tpl, expect := range tests { + assert.NoError(t, runt(tpl, expect)) + } + + dict := map[string]interface{}{"top": map[string]interface{}{}} + tpl := `{{ coalesce .top.NoSuchThing .bottom .bottom.dollar "airplane"}}` + if err := runtv(tpl, "airplane", dict); err != nil { + t.Error(err) + } +} + func TestAll(t *testing.T) { tests := map[string]string{ `{{ all 1 }}`: "true", diff --git a/docs/defaults.md b/docs/defaults.md index b68cd83f..f13ae7f2 100644 --- a/docs/defaults.md +++ b/docs/defaults.md @@ -25,6 +25,18 @@ The definition of "empty" depends on type: For structs, there is no definition of empty, so a struct will never return the default. +## safeDefault + +Similar to `default` but only considers `nil` as unset value. `0`, `false`, +`""` are not considered unset, unlike `default`. + +``` +safeDefault true .Enabled +``` + +In the above, if `.Enabled` evaluates to `false`, same will be returned. +However, `default` would return `true` here. + ## empty The `empty` function returns `true` if the given value is considered empty, and @@ -58,6 +70,16 @@ The above will first check to see if `.name` is empty. If it is not, it will ret that value. If it _is_ empty, `coalesce` will evaluate `.parent.name` for emptiness. Finally, if both `.name` and `.parent.name` are empty, it will return `Matt`. +## safeCoalesce + +Similar to `coalesce` but only considers `nil` as unset value. + +``` +safeCoalesce nil 0 1 2 +``` + +The above evaluates to `0`. + ## all The `all` function takes a list of values and returns true if all values are non-empty. diff --git a/functions.go b/functions.go index 57fcec1d..30da586f 100644 --- a/functions.go +++ b/functions.go @@ -237,8 +237,10 @@ var genericMap = map[string]interface{}{ // Defaults "default": dfault, + "safeDefault": safeDefault, "empty": empty, "coalesce": coalesce, + "safeCoalesce": safeCoalesce, "all": all, "any": any, "compact": compact,