diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index e36e814d3..bbc81227f 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -28,6 +28,17 @@ and other basic tools; these are listed in the ## General +### default +Checks whether a given value is set and returns a default value if it is not. +"Set" in this context means true for booleans; non-zero for numeric types; +non-zero length for strings, arrays, slices, and maps; any struct value; or +non-nil for any other types. + +e.g. + + {{ .Params.font | default "Roboto" }} → default is "Roboto" + {{ default "Roboto" .Params.font }} → default is "Roboto" + ### delimit Loops through any array, slice or map and returns a string of all the values separated by the delimiter. There is an optional third parameter that lets you choose a different delimiter to go between the last two values. Maps will be sorted by the keys, and only a slice of the values will be returned, keeping a consistent output order. diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 1cbc7d32b..8a0672726 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -1239,6 +1239,44 @@ func dateFormat(layout string, v interface{}) (string, error) { return t.Format(layout), nil } +// dfault checks whether a given value is set and returns a default value if it +// is not. "Set" in this context means true for booleans; non-zero for numeric +// types; non-zero length for strings, arrays, slices, and maps; any struct +// value; or non-nil for any other types. +func dfault(dflt, given interface{}) interface{} { + g := reflect.ValueOf(given) + if !g.IsValid() { + return dflt + } + + set := false + + switch g.Kind() { + case reflect.Bool: + set = g.Bool() + case reflect.String, reflect.Array, reflect.Slice, reflect.Map: + set = g.Len() != 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + set = g.Int() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + set = g.Uint() != 0 + case reflect.Float32, reflect.Float64: + set = g.Float() != 0 + case reflect.Complex64, reflect.Complex128: + set = g.Complex() != 0 + case reflect.Struct: + set = true + default: + set = !g.IsNil() + } + + if set { + return given + } + + return dflt +} + // safeHTMLAttr returns a given string as html/template HTMLAttr content. // // safeHTMLAttr is currently disabled, pending further discussion @@ -1537,6 +1575,7 @@ func init() { "chomp": chomp, "countrunes": countRunes, "countwords": countWords, + "default": dfault, "dateFormat": dateFormat, "delimit": delimit, "dict": dictionary, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 878b31d20..ae1161f27 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -18,9 +18,6 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/spf13/cast" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "html/template" "math/rand" "path" @@ -29,6 +26,10 @@ import ( "strings" "testing" "time" + + "github.com/spf13/cast" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" ) type tstNoStringer struct { @@ -1843,6 +1844,44 @@ func TestDateFormat(t *testing.T) { } } +func TestDefault(t *testing.T) { + for i, this := range []struct { + dflt interface{} + given interface{} + expected interface{} + }{ + {"5", 0, "5"}, + + {"test1", "set", "set"}, + {"test2", "", "test2"}, + + {[2]int{10, 20}, [2]int{1, 2}, [2]int{1, 2}}, + {[2]int{10, 20}, [0]int{}, [2]int{10, 20}}, + + {[]string{"one"}, []string{"uno"}, []string{"uno"}}, + {[]string{"one"}, []string{}, []string{"one"}}, + + {map[string]int{"one": 1}, map[string]int{"uno": 1}, map[string]int{"uno": 1}}, + {map[string]int{"one": 1}, map[string]int{}, map[string]int{"one": 1}}, + + {10, 1, 1}, + {10, 0, 10}, + + {float32(10), float32(1), float32(1)}, + {float32(10), 0, float32(10)}, + + {complex(2, -2), complex(1, -1), complex(1, -1)}, + {complex(2, -2), complex(0, 0), complex(2, -2)}, + + {struct{ f string }{f: "one"}, struct{ f string }{}, struct{ f string }{}}, + } { + res := dfault(this.dflt, this.given) + if !reflect.DeepEqual(this.expected, res) { + t.Errorf("[%d] default returned %v, but expected %v", i, res, this.expected) + } + } +} + func TestSafeHTML(t *testing.T) { for i, this := range []struct { str string