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