URI: 
       Make sort tpl func accept field/key chaining arg - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 153332706a78d387225bc1d9e04bc8270cf49aff
   DIR parent 56534beaf684f374327b6d01be26d15ae41c8985
  HTML Author: Tatsushi Demachi <tdemachi@gmail.com>
       Date:   Fri,  7 Aug 2015 20:05:18 +0900
       
       Make sort tpl func accept field/key chaining arg
       
       'sort' template function used to accept only each element's struct field
       name, method name and map key name as its second argument. This extends
       it to accept a field/method/key chaining key string like
       'Params.foo.bar' as the argument. It evaluates sub elements of each
       array or map elements and sorts by them.
       
       Typical use case would be sorting pages by user defined front matter
       value. For example, sorting pages by 'Params.foo.bar' is possible by
       writing the following template code
       
           {{ range sort .Data.Pages "Params.foo.bar" }}
               {{ .Content }}
           {{ end }}
       
       It ignores all leading and trailing dots so "Params.foo.bar" can be
       written in ".Params.foo.bar"
       
       This also fixes the issue that 'sort' cannot evaluate a pointer value.
       
       Fix #1330
       
       Diffstat:
         M tpl/template_funcs.go               |      79 ++++++++++++++++++-------------
         M tpl/template_funcs_test.go          |     142 +++++++++++++++++++++++++++++--
       
       2 files changed, 181 insertions(+), 40 deletions(-)
       ---
   DIR diff --git 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
   DIR diff --git 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)
       +                        }
                        }
                }
        }