From 5d0748ce51d0a86843bfd6569fd3cd18fa20ed5a Mon Sep 17 00:00:00 2001 From: digitalcraftsman Date: Sun, 12 Mar 2017 23:04:12 +0100 Subject: [PATCH] tpl: Add union template func --- docs/content/templates/functions.md | 17 ++++++++++ tpl/tplimpl/template_funcs.go | 50 ++++++++++++++++++++++++++++ tpl/tplimpl/template_funcs_test.go | 51 +++++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index f20aebebc..e4c885c3c 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -214,6 +214,23 @@ e.g. +### union +Given two arrays (or slices) A and B, this function will return a new array that contains the elements or objects that belong to either A or to B or to both. The elements supported are strings, integers and floats (only float64). + +``` +{{ union (slice 1 2 3) (slice 3 4 5) }} + + +{{ union (slice 1 2 3) nil }} + + +{{ union nil (slice 1 2 3) }} + + +{{ union nil nil }} + +``` + ### isset Returns true if the parameter is set. Takes either a slice, array or channel and an index or a map and a key as input. diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index 05382825b..987a00d98 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -399,6 +399,55 @@ func intersect(l1, l2 interface{}) (interface{}, error) { } } +// union returns the union of the given sets, l1 and l2. l1 and +// l2 must be of the same type and may be either arrays or slices. +// If l1 and l2 aren't of the same type then l1 will be returned. +// If either l1 or l2 is nil then the non-nil list will be returned. +func union(l1, l2 interface{}) (interface{}, error) { + if l1 == nil && l2 == nil { + return nil, errors.New("both arrays/slices have to be of the same type") + } else if l1 == nil && l2 != nil { + return l2, nil + } else if l1 != nil && l2 == nil { + return l1, nil + } + + l1v := reflect.ValueOf(l1) + l2v := reflect.ValueOf(l2) + + switch l1v.Kind() { + case reflect.Array, reflect.Slice: + switch l2v.Kind() { + case reflect.Array, reflect.Slice: + r := reflect.MakeSlice(l1v.Type(), 0, 0) + + if l1v.Type() != l2v.Type() { + return r.Interface(), nil + } + + for i := 0; i < l1v.Len(); i++ { + elem := l1v.Index(i) + if !in(r.Interface(), elem.Interface()) { + r = reflect.Append(r, elem) + } + } + + for j := 0; j < l2v.Len(); j++ { + elem := l2v.Index(j) + if !in(r.Interface(), elem.Interface()) { + r = reflect.Append(r, elem) + } + } + + return r.Interface(), nil + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String()) + } + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String()) + } +} + type imageHandler struct { imageConfigCache map[string]image.Config sync.RWMutex @@ -2193,6 +2242,7 @@ func (t *templateFuncster) initFuncMap() { "time": asTime, "trim": trim, "truncate": truncate, + "union": union, "upper": upper, "urlize": t.PathSpec.URLize, "where": where, diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go index 491eac56c..355de182f 100644 --- a/tpl/tplimpl/template_funcs_test.go +++ b/tpl/tplimpl/template_funcs_test.go @@ -184,6 +184,7 @@ trim: {{ trim "++Batman--" "+-" }} truncate: {{ "this is a very long text" | truncate 10 " ..." }} truncate: {{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }} upper: {{upper "BatMan"}} +union: {{ union (slice 1 2 3) (slice 3 4 5) }} urlize: {{ "Bat Man" | urlize }} ` @@ -260,6 +261,7 @@ trim: Batman truncate: this is a ... truncate: With Markdown … upper: BATMAN +union: [1 2 3 4 5] urlize: bat-man ` @@ -988,8 +990,6 @@ func TestIntersect(t *testing.T) { {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}}, {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}}, {[]string{}, []string{}, []string{}}, - {[]string{"a", "b"}, nil, make([]interface{}, 0)}, - {nil, []string{"a", "b"}, make([]interface{}, 0)}, {nil, nil, make([]interface{}, 0)}, {[]string{"1", "2"}, []int{1, 2}, []string{}}, {[]int{1, 2}, []string{"1", "2"}, []int{}}, @@ -1021,6 +1021,53 @@ func TestIntersect(t *testing.T) { } } +func TestUnion(t *testing.T) { + t.Parallel() + for i, this := range []struct { + sequence1 interface{} + sequence2 interface{} + expect interface{} + isErr bool + }{ + {[]string{"a", "b", "c", "c"}, []string{"a", "b", "b"}, []string{"a", "b", "c"}, false}, + {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b", "c"}, false}, + {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{"a", "b", "c", "d", "e"}, false}, + {[]string{}, []string{}, []string{}, false}, + {[]string{"a", "b"}, nil, []string{"a", "b"}, false}, + {nil, []string{"a", "b"}, []string{"a", "b"}, false}, + {nil, nil, make([]interface{}, 0), true}, + {[]string{"1", "2"}, []int{1, 2}, make([]string, 0), false}, + {[]int{1, 2}, []string{"1", "2"}, make([]int, 0), false}, + {[]int{1, 2, 3}, []int{3, 4, 5}, []int{1, 2, 3, 4, 5}, false}, + {[]int{1, 2, 3}, []int{1, 2, 3}, []int{1, 2, 3}, false}, + {[]int{1, 2, 4}, []int{2, 4}, []int{1, 2, 4}, false}, + {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4, 1}, false}, + {[]int{1, 2, 4}, []int{3, 6}, []int{1, 2, 4, 3, 6}, false}, + {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4, 1.1}, false}, + } { + results, err := union(this.sequence1, this.sequence2) + if err != nil && !this.isErr { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) && !this.isErr { + t.Errorf("[%d] got %v but expected %v", i, results, this.expect) + } + } + + _, err1 := union("not an array or slice", []string{"a"}) + + if err1 == nil { + t.Error("Expected error for non array as first arg") + } + + _, err2 := union([]string{"a"}, "not an array or slice") + + if err2 == nil { + t.Error("Expected error for non array as second arg") + } +} + func TestIsSet(t *testing.T) { t.Parallel()