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:
parent
2e5cc60c90
commit
3c242c40f1
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user