1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 05:48:56 +01:00

Complete #17 sort_natural filter

This commit is contained in:
Oliver Steele 2017-07-12 13:10:19 -04:00
parent 2e5cc60c90
commit 3c242c40f1
4 changed files with 81 additions and 22 deletions

View File

@ -32,7 +32,6 @@ In brief, these aren't implemented:
- The `cycle` and `tablerow` tags
- `{% when a or b %}`
- The `sort_natural` filter
- Loop ranges `{% for a in 1...10 %}`
- Error modes
- Whitespace control

View File

@ -5,13 +5,13 @@ import (
"sort"
)
type genericSortable []interface{}
// Sort any []interface{} value.
func Sort(data []interface{}) {
sort.Sort(genericSortable(data))
}
type genericSortable []interface{}
// Len is part of sort.Interface.
func (s genericSortable) Len() int {
return len(s)

View File

@ -9,6 +9,7 @@ import (
"net/url"
"reflect"
"regexp"
"sort"
"strings"
"time"
"unicode"
@ -145,6 +146,7 @@ func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
fd.AddFilter("replace_first", func(s, old, new string) string {
return strings.Replace(s, old, new, 1)
})
fd.AddFilter("sort_natural", sortNaturalFilter)
fd.AddFilter("slice", func(s string, start int, length func(int) int) string {
// runes aren't bytes; don't use slice
n := length(1)
@ -218,36 +220,86 @@ func joinFilter(array []interface{}, sep func(string) string) interface{} {
}
func reverseFilter(array []interface{}) interface{} {
out := make([]interface{}, len(array))
result := make([]interface{}, len(array))
for i, x := range array {
out[len(out)-1-i] = x
result[len(result)-1-i] = x
}
return out
return result
}
func sortFilter(array []interface{}, key interface{}) []interface{} {
out := make([]interface{}, len(array))
copy(out, array)
result := make([]interface{}, len(array))
copy(result, array)
if key == nil {
evaluator.Sort(out)
evaluator.Sort(result)
} else {
evaluator.SortByProperty(out, key.(string), true)
evaluator.SortByProperty(result, key.(string), true)
}
return out
return result
}
func sortNaturalFilter(array []interface{}, key interface{}) interface{} {
result := make([]interface{}, len(array))
copy(result, array)
switch {
case reflect.ValueOf(array).Len() == 0:
case key != nil:
sort.Sort(keySortable{result, func(m interface{}) string {
rv := reflect.ValueOf(m)
if rv.Kind() != reflect.Map {
return ""
}
ev := rv.MapIndex(reflect.ValueOf(key))
if ev.CanInterface() {
if s, ok := ev.Interface().(string); ok {
return strings.ToLower(s)
}
}
return ""
}})
case reflect.TypeOf(array[0]).Kind() == reflect.String:
sort.Sort(keySortable{result, func(s interface{}) string {
return strings.ToUpper(s.(string))
}})
}
return result
}
type keySortable struct {
slice []interface{}
keyFn func(interface{}) string
}
// Len is part of sort.Interface.
func (s keySortable) Len() int {
return len(s.slice)
}
// Swap is part of sort.Interface.
func (s keySortable) Swap(i, j int) {
a := s.slice
a[i], a[j] = a[j], a[i]
}
// Less is part of sort.Interface.
func (s keySortable) Less(i, j int) bool {
k, sl := s.keyFn, s.slice
a, b := k(sl[i]), k(sl[j])
return a < b
}
func splitFilter(s, sep string) interface{} {
out := strings.Split(s, sep)
result := strings.Split(s, sep)
// This matches Jekyll's observed behavior.
// TODO test case
if len(out) > 0 && out[len(out)-1] == "" {
out = out[:len(out)-1]
if len(result) > 0 && result[len(result)-1] == "" {
result = result[:len(result)-1]
}
return out
return result
}
func uniqFilter(array []interface{}) []interface{} {
out := []interface{}{}
result := []interface{}{}
seenInts := map[int]bool{}
seenStrings := map[string]bool{}
seen := func(item interface{}) bool {
@ -275,7 +327,7 @@ func uniqFilter(array []interface{}) []interface{} {
// }
// the O(n^2) case:
// TODO use == if the values are comparable
for _, v := range out {
for _, v := range result {
if reflect.DeepEqual(item, v) {
return true
}
@ -285,8 +337,8 @@ func uniqFilter(array []interface{}) []interface{} {
}
for _, e := range array {
if !seen(e) {
out = append(out, e)
result = append(result, e)
}
}
return out
return result
}

View File

@ -36,8 +36,10 @@ var filterTests = []struct {
{`dup_ints | uniq | join`, "1, 2, 3"},
{`dup_strings | uniq | join`, "one, two, three"},
{`dup_maps | uniq | map: "name" | join`, "m1, m2, m3"},
{`mixed_case_list | sort_natural | join`, "a, B, c"},
{`mixed_case_objects | sort_natural: 'key' | map: 'key' | join`, "a, B, c"},
// date filters
// date filters``
{`article.published_at | date`, "Fri, Jul 17, 15"},
{`article.published_at | date: "%a, %b %d, %y"`, "Fri, Jul 17, 15"},
{`article.published_at | date: "%Y"`, "2015"},
@ -157,8 +159,14 @@ var filterTestBindings = map[string]interface{}{
"article": map[string]interface{}{
"published_at": timeMustParse("2015-07-17T15:04:05Z"),
},
"empty_list": []interface{}{},
"fruits": []string{"apples", "oranges", "peaches", "plums"},
"empty_list": []interface{}{},
"fruits": []string{"apples", "oranges", "peaches", "plums"},
"mixed_case_list": []string{"c", "a", "B"},
"mixed_case_objects": []map[string]interface{}{
map[string]interface{}{"key": "c"},
map[string]interface{}{"key": "a"},
map[string]interface{}{"key": "B"},
},
"obj": map[string]interface{}{
"a": 1,
},