mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
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
This commit is contained in:
parent
56534beaf6
commit
153332706a
2 changed files with 181 additions and 40 deletions
|
@ -882,32 +882,51 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) {
|
||||||
return nil, errors.New("can't iterate over a nil value")
|
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
|
// Create a list of pairs that will be used to do the sort
|
||||||
p := pairList{SortAsc: true, SliceType: reflect.SliceOf(seqv.Type().Elem())}
|
p := pairList{SortAsc: true, SliceType: reflect.SliceOf(seqv.Type().Elem())}
|
||||||
p.Pairs = make([]pair, seqv.Len())
|
p.Pairs = make([]pair, seqv.Len())
|
||||||
|
|
||||||
|
var sortByField string
|
||||||
for i, l := range args {
|
for i, l := range args {
|
||||||
dStr, err := cast.ToStringE(l)
|
dStr, err := cast.ToStringE(l)
|
||||||
switch {
|
switch {
|
||||||
case i == 0 && err != nil:
|
case i == 0 && err != nil:
|
||||||
p.SortByField = ""
|
sortByField = ""
|
||||||
case i == 0 && err == nil:
|
case i == 0 && err == nil:
|
||||||
p.SortByField = dStr
|
sortByField = dStr
|
||||||
case i == 1 && err == nil && dStr == "desc":
|
case i == 1 && err == nil && dStr == "desc":
|
||||||
p.SortAsc = false
|
p.SortAsc = false
|
||||||
case i == 1:
|
case i == 1:
|
||||||
p.SortAsc = true
|
p.SortAsc = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path := strings.Split(strings.Trim(sortByField, "."), ".")
|
||||||
|
|
||||||
switch seqv.Kind() {
|
switch seqv.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
for i := 0; i < seqv.Len(); i++ {
|
for i := 0; i < seqv.Len(); i++ {
|
||||||
p.Pairs[i].Key = reflect.ValueOf(i)
|
p.Pairs[i].Key = reflect.ValueOf(i)
|
||||||
p.Pairs[i].Value = seqv.Index(i)
|
p.Pairs[i].Value = seqv.Index(i)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
if p.SortByField == "" {
|
|
||||||
p.SortByField = "value"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
|
@ -915,10 +934,22 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) {
|
||||||
for i := 0; i < seqv.Len(); i++ {
|
for i := 0; i < seqv.Len(); i++ {
|
||||||
p.Pairs[i].Key = keys[i]
|
p.Pairs[i].Key = keys[i]
|
||||||
p.Pairs[i].Value = seqv.MapIndex(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
|
return p.sort(), nil
|
||||||
}
|
}
|
||||||
|
@ -929,12 +960,12 @@ func Sort(seq interface{}, args ...interface{}) (interface{}, error) {
|
||||||
type pair struct {
|
type pair struct {
|
||||||
Key reflect.Value
|
Key reflect.Value
|
||||||
Value reflect.Value
|
Value reflect.Value
|
||||||
|
SortByValue reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// A slice of pairs that implements sort.Interface to sort by Value.
|
// A slice of pairs that implements sort.Interface to sort by Value.
|
||||||
type pairList struct {
|
type pairList struct {
|
||||||
Pairs []pair
|
Pairs []pair
|
||||||
SortByField string
|
|
||||||
SortAsc bool
|
SortAsc bool
|
||||||
SliceType reflect.Type
|
SliceType reflect.Type
|
||||||
}
|
}
|
||||||
|
@ -942,25 +973,7 @@ type pairList struct {
|
||||||
func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] }
|
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) Len() int { return len(p.Pairs) }
|
||||||
func (p pairList) Less(i, j int) bool {
|
func (p pairList) Less(i, j int) bool {
|
||||||
var truth bool
|
return Lt(p.Pairs[i].SortByValue.Interface(), p.Pairs[j].SortByValue.Interface())
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sorts a pairList and returns a slice of sorted values
|
// sorts a pairList and returns a slice of sorted values
|
||||||
|
|
|
@ -1081,6 +1081,10 @@ func TestSort(t *testing.T) {
|
||||||
MyFloat float64
|
MyFloat float64
|
||||||
MyString string
|
MyString string
|
||||||
}
|
}
|
||||||
|
type mid struct {
|
||||||
|
Tst TstX
|
||||||
|
}
|
||||||
|
|
||||||
for i, this := range []struct {
|
for i, this := range []struct {
|
||||||
sequence interface{}
|
sequence interface{}
|
||||||
sortByField interface{}
|
sortByField interface{}
|
||||||
|
@ -1091,6 +1095,8 @@ func TestSort(t *testing.T) {
|
||||||
{[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}},
|
{[]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{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}},
|
{[]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
|
// 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{"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}},
|
{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",
|
"asc",
|
||||||
[]ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}},
|
[]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{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}},
|
||||||
{[]string{"class3", "class1", "class2"}, "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 result interface{}
|
||||||
var err error
|
var err error
|
||||||
|
@ -1133,6 +1254,12 @@ func TestSort(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
result, err = Sort(this.sequence, this.sortByField, this.sortAsc)
|
result, err = Sort(this.sequence, this.sortByField, this.sortAsc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
t.Errorf("[%d] failed: %s", i, err)
|
t.Errorf("[%d] failed: %s", i, err)
|
||||||
continue
|
continue
|
||||||
|
@ -1141,6 +1268,7 @@ func TestSort(t *testing.T) {
|
||||||
t.Errorf("[%d] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReturnWhenSet(t *testing.T) {
|
func TestReturnWhenSet(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue