From 2dc308cb36dbcbf5c21842a88e42fe281720e96f Mon Sep 17 00:00:00 2001 From: Julian Peterson Date: Sun, 6 Jun 2021 21:34:30 +1000 Subject: [PATCH] added "and" & "or" logic operators --- functions/functions_test.go | 2 +- functions/logic.go | 25 +++++ functions/logic_test.go | 202 +++++++++++++++++++++++++++++++++++- 3 files changed, 227 insertions(+), 2 deletions(-) diff --git a/functions/functions_test.go b/functions/functions_test.go index f13ff87..aabe50d 100644 --- a/functions/functions_test.go +++ b/functions/functions_test.go @@ -28,7 +28,7 @@ func helperNewTemplate(t *testing.T, tpl string) *template.Template { // TestAll provides unit test coverage for All() func TestAll(t *testing.T) { fn := All(nil) - assert.Len(t, fn, 56, "weakly ensuring functions haven't been added/removed without updating tests") + assert.Len(t, fn, 58, "weakly ensuring functions haven't been added/removed without updating tests") } // TestCombineFunctionLists provides unit test coverage for CombineFunctionLists diff --git a/functions/logic.go b/functions/logic.go index d15d099..10d70fc 100644 --- a/functions/logic.go +++ b/functions/logic.go @@ -8,7 +8,9 @@ import ( // LogicFunctions perform logical operations func LogicFunctions() template.FuncMap { return template.FuncMap{ + "and": And, "isZero": IsZero, + "or": Or, "when": When, "whenEmpty": WhenEmpty, } @@ -46,3 +48,26 @@ func IsZero(val interface{}) bool { } return v.IsZero() } + +// And returns the value of the last expression if all expressions evaluate to non-zero, or empty string otherwise +func And(expr ...interface{}) interface{} { + if len(expr) == 0 { + return "" + } + for _, e := range expr { + if IsZero(e) { + return "" + } + } + return expr[len(expr)-1] +} + +// Or returns the first expression that evaluates to non-zero, or empty string if none do +func Or(expr ...interface{}) interface{} { + for _, e := range expr { + if !IsZero(e) { + return e + } + } + return "" +} diff --git a/functions/logic_test.go b/functions/logic_test.go index 6fbe224..5a12812 100644 --- a/functions/logic_test.go +++ b/functions/logic_test.go @@ -11,7 +11,7 @@ import ( // TestLogicFunctions provides unit test coverage for LogicFunctions func TestLogicFunctions(t *testing.T) { fn := LogicFunctions() - assert.Len(t, fn, 3, "weakly ensuring functions haven't been added/removed without updating tests") + assert.Len(t, fn, 5, "weakly ensuring functions haven't been added/removed without updating tests") } // TestWhen provides unit test coverage for When() @@ -332,3 +332,203 @@ func TestIsZero(t *testing.T) { }) } } + +// TestAnd provides unit test coverage for And() +func TestAnd(t *testing.T) { + type Args struct { + A interface{} + B interface{} + C interface{} + D interface{} + } + + tests := []struct { + name string + template string + args Args + want string + wantErr bool + }{ + { + name: "no args", + template: `{{ and }}`, + args: Args{}, + want: "", + }, + { + name: "bool false", + template: `{{ and .A }}`, + args: Args{ + A: false, + }, + want: "", + }, + { + name: "bool true", + template: `{{ and .A }}`, + args: Args{ + A: true, + }, + want: "true", + }, + { + name: "int 9", + template: `{{ and .A }}`, + args: Args{ + A: 9, + }, + want: "9", + }, + { + name: "2 empty args", + template: `{{ and .A .B }}`, + args: Args{ + A: "", + B: 0, + }, + want: "", + }, + { + name: "3 true args", + template: `{{ and .A .B .C }}`, + args: Args{ + A: 2, + B: 3, + C: "X", + }, + want: "X", + }, + { + name: "3 args, 1 false", + template: `{{ and .A .B .C }}`, + args: Args{ + A: 2, + B: 0, + C: "X", + }, + want: "", + }, + } + + for _, st := range tests { + tt := st + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var got bytes.Buffer + + tpl := helperNewTemplate(t, tt.template) + err := tpl.ExecuteTemplate(&got, testTemplateName, tt.args) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got.String()) + }) + } +} + +// TestOr provides unit test coverage for Or() +func TestOr(t *testing.T) { + type Args struct { + A interface{} + B interface{} + C interface{} + D interface{} + } + + tests := []struct { + name string + template string + args Args + want string + wantErr bool + }{ + { + name: "no args", + template: `{{ or }}`, + args: Args{}, + want: "", + }, + { + name: "bool false", + template: `{{ or .A }}`, + args: Args{ + A: false, + }, + want: "", + }, + { + name: "bool true", + template: `{{ or .A }}`, + args: Args{ + A: true, + }, + want: "true", + }, + { + name: "int 9", + template: `{{ or .A }}`, + args: Args{ + A: 9, + }, + want: "9", + }, + { + name: "2 empty args", + template: `{{ or .A .B }}`, + args: Args{ + A: "", + B: 0, + }, + want: "", + }, + { + name: "3 true args", + template: `{{ or .A .B .C }}`, + args: Args{ + A: 2, + B: 3, + C: "X", + }, + want: "2", + }, + { + name: "3 args, first false", + template: `{{ or .A .B .C }}`, + args: Args{ + A: 0, + B: 2, + C: "X", + }, + want: "2", + }, + { + name: "3 args, middle false", + template: `{{ or .A .B .C }}`, + args: Args{ + A: 2, + B: 0, + C: "X", + }, + want: "2", + }, + } + + for _, st := range tests { + tt := st + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var got bytes.Buffer + + tpl := helperNewTemplate(t, tt.template) + err := tpl.ExecuteTemplate(&got, testTemplateName, tt.args) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got.String()) + }) + } +}