Skip to content

Commit

Permalink
Add functions safeDefault and safeCoalesce
Browse files Browse the repository at this point in the history
  • Loading branch information
govindbalaji-s committed Oct 21, 2023
1 parent 581758e commit 10f69a1
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
18 changes: 18 additions & 0 deletions defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
70 changes: 70 additions & 0 deletions defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }}`: "<no value>",
}
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",
Expand Down
22 changes: 22 additions & 0 deletions docs/defaults.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 10f69a1

Please sign in to comment.