diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index a7cc38af7..3ebe23ae7 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -882,32 +882,51 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) { return nil, errors.New("can't iterate over a nil value") } + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.Map: + // ok + default: + return nil, errors.New("can't sort " + reflect.ValueOf(seq).Type().String()) + } + // Create a list of pairs that will be used to do the sort p := pairList{SortAsc: true, SliceType: reflect.SliceOf(seqv.Type().Elem())} p.Pairs = make([]pair, seqv.Len()) + var sortByField string for i, l := range args { dStr, err := cast.ToStringE(l) switch { case i == 0 && err != nil: - p.SortByField = "" + sortByField = "" case i == 0 && err == nil: - p.SortByField = dStr + sortByField = dStr case i == 1 && err == nil && dStr == "desc": p.SortAsc = false case i == 1: p.SortAsc = true } } + path := strings.Split(strings.Trim(sortByField, "."), ".") switch seqv.Kind() { case reflect.Array, reflect.Slice: for i := 0; i < seqv.Len(); i++ { p.Pairs[i].Key = reflect.ValueOf(i) p.Pairs[i].Value = seqv.Index(i) - } - if p.SortByField == "" { - p.SortByField = "value" + if sortByField == "" || sortByField == "value" { + p.Pairs[i].SortByValue = p.Pairs[i].Value + } else { + v := p.Pairs[i].Value + var err error + for _, elemName := range path { + v, err = evaluateSubElem(v, elemName) + if err != nil { + return nil, err + } + } + p.Pairs[i].SortByValue = v + } } case reflect.Map: @@ -915,10 +934,22 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) { for i := 0; i < seqv.Len(); i++ { p.Pairs[i].Key = keys[i] p.Pairs[i].Value = seqv.MapIndex(keys[i]) + if sortByField == "" { + p.Pairs[i].SortByValue = p.Pairs[i].Key + } else if sortByField == "value" { + p.Pairs[i].SortByValue = p.Pairs[i].Value + } else { + v := p.Pairs[i].Value + var err error + for _, elemName := range path { + v, err = evaluateSubElem(v, elemName) + if err != nil { + return nil, err + } + } + p.Pairs[i].SortByValue = v + } } - - default: - return nil, errors.New("can't sort " + reflect.ValueOf(seq).Type().String()) } return p.sort(), nil } @@ -927,40 +958,22 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) { // https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw // A data structure to hold a key/value pair. type pair struct { - Key reflect.Value - Value reflect.Value + Key reflect.Value + Value reflect.Value + SortByValue reflect.Value } // A slice of pairs that implements sort.Interface to sort by Value. type pairList struct { - Pairs []pair - SortByField string - SortAsc bool - SliceType reflect.Type + Pairs []pair + SortAsc bool + SliceType reflect.Type } func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] } func (p pairList) Len() int { return len(p.Pairs) } func (p pairList) Less(i, j int) bool { - var truth bool - switch { - case p.SortByField == "value": - iVal := p.Pairs[i].Value - jVal := p.Pairs[j].Value - truth = Lt(iVal.Interface(), jVal.Interface()) - - case p.SortByField != "": - if p.Pairs[i].Value.FieldByName(p.SortByField).IsValid() { - iVal := p.Pairs[i].Value.FieldByName(p.SortByField) - jVal := p.Pairs[j].Value.FieldByName(p.SortByField) - truth = Lt(iVal.Interface(), jVal.Interface()) - } - default: - iVal := p.Pairs[i].Key - jVal := p.Pairs[j].Key - truth = Lt(iVal.Interface(), jVal.Interface()) - } - return truth + return Lt(p.Pairs[i].SortByValue.Interface(), p.Pairs[j].SortByValue.Interface()) } // sorts a pairList and returns a slice of sorted values diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index c2e9baff9..d86eeba1c 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -1081,6 +1081,10 @@ func TestSort(t *testing.T) { MyFloat float64 MyString string } + type mid struct { + Tst TstX + } + for i, this := range []struct { sequence interface{} sortByField interface{} @@ -1091,6 +1095,8 @@ func TestSort(t *testing.T) { {[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}}, {[]int{1, 2, 3, 4, 5}, nil, "asc", []int{1, 2, 3, 4, 5}}, {[]int{5, 4, 3, 1, 2}, nil, "asc", []int{1, 2, 3, 4, 5}}, + // test sort key parameter is focibly set empty + {[]string{"class3", "class1", "class2"}, map[int]string{1: "a"}, "asc", []string{"class1", "class2", "class3"}}, // test map sorting by keys {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []int{10, 20, 30, 40, 50}}, {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []int{30, 20, 10, 40, 50}}, @@ -1122,9 +1128,124 @@ func TestSort(t *testing.T) { "asc", []ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}}, }, - // Test sort desc + // test sort desc {[]string{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}}, {[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}}, + // test sort by struct's method + { + []TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}}, + "TstRv", + "asc", + []TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, + }, + { + []*TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}}, + "TstRp", + "asc", + []*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, + }, + // test map sorting by struct's method + { + map[string]TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}}, + "TstRv", + "asc", + []TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, + }, + { + map[string]*TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}}, + "TstRp", + "asc", + []*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, + }, + // test sort by dot chaining key argument + { + []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, + "foo.A", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, + ".foo.A", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, + "foo.TstRv", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + []map[string]*TstX{{"foo": &TstX{A: "e", B: "f"}}, {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}}, + "foo.TstRp", + "asc", + []map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}}, + }, + { + []map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}}, + "foo.Tst.A", + "asc", + []map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}}, + }, + { + []map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}}, + "foo.Tst.TstRv", + "asc", + []map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}}, + }, + // test map sorting by dot chaining key argument + { + map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}}, + "foo.A", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}}, + ".foo.A", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}}, + "foo.TstRv", + "asc", + []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, + }, + { + map[string]map[string]*TstX{"1": {"foo": &TstX{A: "e", B: "f"}}, "2": {"foo": &TstX{A: "a", B: "b"}}, "3": {"foo": &TstX{A: "c", B: "d"}}}, + "foo.TstRp", + "asc", + []map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}}, + }, + { + map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}}, + "foo.Tst.A", + "asc", + []map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}}, + }, + { + map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}}, + "foo.Tst.TstRv", + "asc", + []map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}}, + }, + // test error cases + {(*[]TstX)(nil), nil, "asc", false}, + {TstX{A: "a", B: "b"}, nil, "asc", false}, + { + []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, + "foo.NotAvailable", + "asc", + false, + }, + { + map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}}, + "foo.NotAvailable", + "asc", + false, + }, } { var result interface{} var err error @@ -1133,12 +1254,19 @@ func TestSort(t *testing.T) { } else { result, err = Sort(this.sequence, this.sortByField, this.sortAsc) } - if err != nil { - t.Errorf("[%d] failed: %s", i, err) - continue - } - if !reflect.DeepEqual(result, this.expect) { - t.Errorf("[%d] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect) + + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] Sort didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect) + } } } }