1
0
mirror of https://github.com/danog/liquid.git synced 2024-12-03 12:47:47 +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 - The `cycle` and `tablerow` tags
- `{% when a or b %}` - `{% when a or b %}`
- The `sort_natural` filter
- Loop ranges `{% for a in 1...10 %}` - Loop ranges `{% for a in 1...10 %}`
- Error modes - Error modes
- Whitespace control - Whitespace control

View File

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

View File

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"sort"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@ -145,6 +146,7 @@ func AddStandardFilters(fd FilterDictionary) { // nolint: gocyclo
fd.AddFilter("replace_first", func(s, old, new string) string { fd.AddFilter("replace_first", func(s, old, new string) string {
return strings.Replace(s, old, new, 1) 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 { fd.AddFilter("slice", func(s string, start int, length func(int) int) string {
// runes aren't bytes; don't use slice // runes aren't bytes; don't use slice
n := length(1) n := length(1)
@ -218,36 +220,86 @@ func joinFilter(array []interface{}, sep func(string) string) interface{} {
} }
func reverseFilter(array []interface{}) interface{} { func reverseFilter(array []interface{}) interface{} {
out := make([]interface{}, len(array)) result := make([]interface{}, len(array))
for i, x := range 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{} { func sortFilter(array []interface{}, key interface{}) []interface{} {
out := make([]interface{}, len(array)) result := make([]interface{}, len(array))
copy(out, array) copy(result, array)
if key == nil { if key == nil {
evaluator.Sort(out) evaluator.Sort(result)
} else { } 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{} { func splitFilter(s, sep string) interface{} {
out := strings.Split(s, sep) result := strings.Split(s, sep)
// This matches Jekyll's observed behavior. // This matches Jekyll's observed behavior.
// TODO test case // TODO test case
if len(out) > 0 && out[len(out)-1] == "" { if len(result) > 0 && result[len(result)-1] == "" {
out = out[:len(out)-1] result = result[:len(result)-1]
} }
return out return result
} }
func uniqFilter(array []interface{}) []interface{} { func uniqFilter(array []interface{}) []interface{} {
out := []interface{}{} result := []interface{}{}
seenInts := map[int]bool{} seenInts := map[int]bool{}
seenStrings := map[string]bool{} seenStrings := map[string]bool{}
seen := func(item interface{}) bool { seen := func(item interface{}) bool {
@ -275,7 +327,7 @@ func uniqFilter(array []interface{}) []interface{} {
// } // }
// the O(n^2) case: // the O(n^2) case:
// TODO use == if the values are comparable // TODO use == if the values are comparable
for _, v := range out { for _, v := range result {
if reflect.DeepEqual(item, v) { if reflect.DeepEqual(item, v) {
return true return true
} }
@ -285,8 +337,8 @@ func uniqFilter(array []interface{}) []interface{} {
} }
for _, e := range array { for _, e := range array {
if !seen(e) { 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_ints | uniq | join`, "1, 2, 3"},
{`dup_strings | uniq | join`, "one, two, three"}, {`dup_strings | uniq | join`, "one, two, three"},
{`dup_maps | uniq | map: "name" | join`, "m1, m2, m3"}, {`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`, "Fri, Jul 17, 15"},
{`article.published_at | date: "%a, %b %d, %y"`, "Fri, Jul 17, 15"}, {`article.published_at | date: "%a, %b %d, %y"`, "Fri, Jul 17, 15"},
{`article.published_at | date: "%Y"`, "2015"}, {`article.published_at | date: "%Y"`, "2015"},
@ -157,8 +159,14 @@ var filterTestBindings = map[string]interface{}{
"article": map[string]interface{}{ "article": map[string]interface{}{
"published_at": timeMustParse("2015-07-17T15:04:05Z"), "published_at": timeMustParse("2015-07-17T15:04:05Z"),
}, },
"empty_list": []interface{}{}, "empty_list": []interface{}{},
"fruits": []string{"apples", "oranges", "peaches", "plums"}, "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{}{ "obj": map[string]interface{}{
"a": 1, "a": 1,
}, },