diff --git a/defaults.go b/defaults.go index b9f97966..2276200c 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) @@ -59,6 +67,10 @@ func empty(given interface{}) bool { } } +func nonNil(given interface{}) bool { + return given != nil +} + // coalesce returns the first non-empty value. func coalesce(v ...interface{}) interface{} { for _, val := range v { @@ -69,6 +81,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..551c4d04 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 { @@ -64,6 +113,40 @@ func TestEmpty(t *testing.T) { } } +func TestNonNil(t *testing.T) { + tpl := `{{if nonNil 1}}1{{else}}0{{end}}` + if err := runt(tpl, "1"); err != nil { + t.Error(err) + } + + tpl = `{{if nonNil 0}}1{{else}}0{{end}}` + if err := runt(tpl, "1"); err != nil { + t.Error(err) + } + tpl = `{{if nonNil ""}}1{{else}}0{{end}}` + if err := runt(tpl, "1"); err != nil { + t.Error(err) + } + tpl = `{{if nonNil 0.0}}1{{else}}0{{end}}` + if err := runt(tpl, "1"); err != nil { + t.Error(err) + } + tpl = `{{if nonNil false}}1{{else}}0{{end}}` + if err := runt(tpl, "1"); err != nil { + t.Error(err) + } + + dict := map[string]interface{}{"top": map[string]interface{}{}} + tpl = `{{if nonNil .top.NoSuchThing}}1{{else}}0{{end}}` + if err := runtv(tpl, "0", dict); err != nil { + t.Error(err) + } + tpl = `{{if nonNil .bottom.NoSuchThing}}1{{else}}0{{end}}` + if err := runtv(tpl, "0", dict); err != nil { + t.Error(err) + } +} + func TestCoalesce(t *testing.T) { tests := map[string]string{ `{{ coalesce 1 }}`: "1", @@ -84,6 +167,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..e4b75c1e 100644 --- a/docs/defaults.md +++ b/docs/defaults.md @@ -25,11 +25,26 @@ 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 `false` otherwise. The empty values are listed in the `default` section. +## nonNil +The `nonNil` function returns `true` if the given value is not `nil`. + ``` empty .Foo ``` @@ -58,6 +73,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..49411f47 100644 --- a/functions.go +++ b/functions.go @@ -22,8 +22,7 @@ import ( // // Use this to pass the functions into the template engine: // -// tpl := template.New("foo").Funcs(sprig.FuncMap())) -// +// tpl := template.New("foo").Funcs(sprig.FuncMap())) func FuncMap() template.FuncMap { return HtmlFuncMap() } @@ -237,8 +236,11 @@ var genericMap = map[string]interface{}{ // Defaults "default": dfault, + "safeDefault": safeDefault, "empty": empty, + "nonNil": nonNil, "coalesce": coalesce, + "safeCoalesce": safeCoalesce, "all": all, "any": any, "compact": compact, @@ -336,20 +338,20 @@ var genericMap = map[string]interface{}{ "mustChunk": mustChunk, // Crypto: - "bcrypt": bcrypt, - "htpasswd": htpasswd, - "genPrivateKey": generatePrivateKey, - "derivePassword": derivePassword, - "buildCustomCert": buildCustomCertificate, - "genCA": generateCertificateAuthority, - "genCAWithKey": generateCertificateAuthorityWithPEMKey, - "genSelfSignedCert": generateSelfSignedCertificate, + "bcrypt": bcrypt, + "htpasswd": htpasswd, + "genPrivateKey": generatePrivateKey, + "derivePassword": derivePassword, + "buildCustomCert": buildCustomCertificate, + "genCA": generateCertificateAuthority, + "genCAWithKey": generateCertificateAuthorityWithPEMKey, + "genSelfSignedCert": generateSelfSignedCertificate, "genSelfSignedCertWithKey": generateSelfSignedCertificateWithPEMKey, - "genSignedCert": generateSignedCertificate, - "genSignedCertWithKey": generateSignedCertificateWithPEMKey, - "encryptAES": encryptAES, - "decryptAES": decryptAES, - "randBytes": randBytes, + "genSignedCert": generateSignedCertificate, + "genSignedCertWithKey": generateSignedCertificateWithPEMKey, + "encryptAES": encryptAES, + "decryptAES": decryptAES, + "randBytes": randBytes, // UUIDs: "uuidv4": uuidv4,