mirror of
https://github.com/danog/liquid.git
synced 2025-01-22 14:41:19 +01:00
Move generics to own package
This commit is contained in:
parent
612f456745
commit
f52d00f836
@ -3,9 +3,12 @@ package expressions
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"github.com/osteele/liquid/generics"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This allows adding and removing references to fmt in the rules below,
|
||||
// without having to edit the import statement to avoid erorrs each time.
|
||||
_ = fmt.Sprint("")
|
||||
}
|
||||
|
||||
@ -79,25 +82,24 @@ expr1:
|
||||
rel:
|
||||
expr1
|
||||
| expr EQ expr {
|
||||
a, b := $1, $3
|
||||
fa, fb := $1, $3
|
||||
$$ = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) == 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return generics.Equal(a, b)
|
||||
}
|
||||
}
|
||||
| expr '<' expr {
|
||||
a, b := $1, $3
|
||||
fa, fb := $1, $3
|
||||
$$ = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) < 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return generics.Less(a, b)
|
||||
}
|
||||
}
|
||||
| expr '>' expr {
|
||||
a, b := $1, $3
|
||||
fa, fb := $1, $3
|
||||
$$ = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) > 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return generics.Less(b, a)
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/osteele/liquid/errors"
|
||||
"github.com/osteele/liquid/generics"
|
||||
)
|
||||
|
||||
type InterpreterError string
|
||||
@ -33,9 +34,9 @@ func sortFilter(in []interface{}, key interface{}) []interface{} {
|
||||
out[i] = v
|
||||
}
|
||||
if key == nil {
|
||||
genericSort(out)
|
||||
generics.Sort(out)
|
||||
} else {
|
||||
sortByProperty(out, key.(string))
|
||||
generics.SortByProperty(out, key.(string))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@ -87,7 +88,7 @@ func makeFilter(f valueFn, name string, param valueFn) valueFn {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch e := r.(type) {
|
||||
case genericError:
|
||||
case generics.GenericError:
|
||||
panic(InterpreterError(e.Error()))
|
||||
default:
|
||||
// fmt.Println(string(debug.Stack()))
|
||||
@ -99,7 +100,7 @@ func makeFilter(f valueFn, name string, param valueFn) valueFn {
|
||||
if param != nil {
|
||||
args = append(args, param(ctx))
|
||||
}
|
||||
out, err := genericApply(fr, args)
|
||||
out, err := generics.Apply(fr, args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,233 +0,0 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type genericError string
|
||||
|
||||
func (e genericError) Error() string { return string(e) }
|
||||
|
||||
func genericErrorf(format string, a ...interface{}) error {
|
||||
return genericError(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
type genericSortable []interface{}
|
||||
|
||||
func genericSort(data []interface{}) {
|
||||
sort.Sort(genericSortable(data))
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (s genericSortable) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (s genericSortable) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface.
|
||||
func (s genericSortable) Less(i, j int) bool {
|
||||
return genericSameTypeCompare(s[i], s[j]) < 0
|
||||
}
|
||||
|
||||
func sortByProperty(data []interface{}, key string) {
|
||||
sort.Sort(sortableByProperty{data, key})
|
||||
}
|
||||
|
||||
type sortableByProperty struct {
|
||||
data []interface{}
|
||||
key string
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (s sortableByProperty) Len() int {
|
||||
return len(s.data)
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (s sortableByProperty) Swap(i, j int) {
|
||||
data := s.data
|
||||
data[i], data[j] = data[j], data[i]
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface.
|
||||
func (s sortableByProperty) Less(i, j int) bool {
|
||||
// index returns the value at the s.key, if in is a map that contains this key
|
||||
index := func(in interface{}) interface{} {
|
||||
rt := reflect.ValueOf(in)
|
||||
if rt.Kind() == reflect.Map && rt.Type().Key().Kind() == reflect.String {
|
||||
return rt.MapIndex(reflect.ValueOf(s.key)).Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
a, b := index(s.data[i]), index(s.data[j])
|
||||
// TODO implement nil-first vs. nil last
|
||||
switch {
|
||||
case a == nil:
|
||||
return true
|
||||
case b == nil:
|
||||
return false
|
||||
default:
|
||||
// TODO relax same type requirement
|
||||
return genericSameTypeCompare(a, b) < 0
|
||||
}
|
||||
}
|
||||
|
||||
// genericApply applies a function to arguments, converting them as necessary.
|
||||
// The conversion follows Liquid semantics, which are more aggressive than
|
||||
// Go conversion. The function should return one or two values; the second value,
|
||||
// if present, should be an error.
|
||||
func genericApply(fn reflect.Value, args []interface{}) (interface{}, error) {
|
||||
in := convertArguments(fn, args)
|
||||
outs := fn.Call(in)
|
||||
if len(outs) > 1 && outs[1].Interface() != nil {
|
||||
switch e := outs[1].Interface().(type) {
|
||||
case error:
|
||||
return nil, e
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
return outs[0].Interface(), nil
|
||||
}
|
||||
|
||||
// Convert val to the type. This is a more aggressive conversion, that will
|
||||
// recursively create new map and slice values as necessary. It doesn't
|
||||
// handle circular references.
|
||||
func convertType(val interface{}, t reflect.Type) reflect.Value {
|
||||
r := reflect.ValueOf(val)
|
||||
if r.Type().ConvertibleTo(t) {
|
||||
return r.Convert(t)
|
||||
}
|
||||
if reflect.PtrTo(r.Type()) == t {
|
||||
return reflect.ValueOf(&val)
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
|
||||
break
|
||||
}
|
||||
x := reflect.MakeSlice(t, 0, r.Len())
|
||||
for i := 0; i < r.Len(); i++ {
|
||||
c := convertType(r.Index(i).Interface(), t.Elem())
|
||||
x = reflect.Append(x, c)
|
||||
}
|
||||
return x
|
||||
}
|
||||
panic(genericErrorf("convertType: can't convert %#v<%s> to %v", val, r.Type(), t))
|
||||
}
|
||||
|
||||
// Convert args to match the input types of function fn.
|
||||
func convertArguments(fn reflect.Value, in []interface{}) []reflect.Value {
|
||||
rt := fn.Type()
|
||||
out := make([]reflect.Value, rt.NumIn())
|
||||
for i, arg := range in {
|
||||
if i < rt.NumIn() {
|
||||
out[i] = convertType(arg, rt.In(i))
|
||||
}
|
||||
}
|
||||
for i := len(in); i < rt.NumIn(); i++ {
|
||||
out[i] = reflect.Zero(rt.In(i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func genericSameTypeCompare(av, bv interface{}) int {
|
||||
a, b := reflect.ValueOf(av), reflect.ValueOf(bv)
|
||||
if a.Kind() != b.Kind() {
|
||||
panic(fmt.Errorf("genericSameTypeCompare called on different types: %v and %v", a, b))
|
||||
}
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if a.Int() < b.Int() {
|
||||
return -1
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if a.Float() < b.Float() {
|
||||
return -1
|
||||
}
|
||||
case reflect.String:
|
||||
if a.String() < b.String() {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
panic(genericErrorf("unimplemented generic same-type comparison for %v and %v", a, b))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func GenericCompare(a, b reflect.Value) int {
|
||||
if a.Interface() == b.Interface() {
|
||||
return 0
|
||||
}
|
||||
ak, bk := a.Kind(), b.Kind()
|
||||
// _ = ak.Convert
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
if b.Kind() == reflect.Bool {
|
||||
switch {
|
||||
case a.Bool() && b.Bool():
|
||||
return 0
|
||||
case a.Bool():
|
||||
return 1
|
||||
case b.Bool():
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if hasIntKind(b) {
|
||||
if a.Int() < b.Int() {
|
||||
return -1
|
||||
}
|
||||
if a.Int() > b.Int() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if hasFloatKind(b) {
|
||||
return GenericCompare(reflect.ValueOf(float64(a.Int())), b)
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if hasIntKind(b) {
|
||||
b = reflect.ValueOf(float64(b.Int()))
|
||||
}
|
||||
if hasFloatKind(b) {
|
||||
if a.Float() < b.Float() {
|
||||
return -1
|
||||
}
|
||||
if a.Float() > b.Float() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
panic(genericErrorf("unimplemented: comparison of %v<%s> with %v<%s>", a, ak, b, bk))
|
||||
}
|
||||
|
||||
func hasIntKind(n reflect.Value) bool {
|
||||
switch n.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasFloatKind(n reflect.Value) bool {
|
||||
switch n.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
@ -6,14 +6,17 @@ import __yyfmt__ "fmt"
|
||||
//line expressions.y:2
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/osteele/liquid/generics"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This allows adding and removing references to fmt in the rules below,
|
||||
// without having to edit the import statement to avoid erorrs each time.
|
||||
_ = fmt.Sprint("")
|
||||
}
|
||||
|
||||
//line expressions.y:13
|
||||
//line expressions.y:16
|
||||
type yySymType struct {
|
||||
yys int
|
||||
name string
|
||||
@ -466,13 +469,13 @@ yydefault:
|
||||
|
||||
case 1:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line expressions.y:27
|
||||
//line expressions.y:30
|
||||
{
|
||||
yylex.(*lexer).val = yyDollar[1].f
|
||||
}
|
||||
case 2:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line expressions.y:28
|
||||
//line expressions.y:31
|
||||
{
|
||||
name, expr := yyDollar[2].name, yyDollar[4].f
|
||||
yylex.(*lexer).val = func(ctx Context) interface{} {
|
||||
@ -482,21 +485,21 @@ yydefault:
|
||||
}
|
||||
case 3:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line expressions.y:37
|
||||
//line expressions.y:40
|
||||
{
|
||||
val := yyDollar[1].val
|
||||
yyVAL.f = func(_ Context) interface{} { return val }
|
||||
}
|
||||
case 4:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line expressions.y:38
|
||||
//line expressions.y:41
|
||||
{
|
||||
name := yyDollar[1].name
|
||||
yyVAL.f = func(ctx Context) interface{} { return ctx.Get(name) }
|
||||
}
|
||||
case 5:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line expressions.y:39
|
||||
//line expressions.y:42
|
||||
{
|
||||
e, attr := yyDollar[1].f, yyDollar[3].name
|
||||
yyVAL.f = func(ctx Context) interface{} {
|
||||
@ -514,7 +517,7 @@ yydefault:
|
||||
}
|
||||
case 6:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line expressions.y:54
|
||||
//line expressions.y:57
|
||||
{
|
||||
e, i := yyDollar[1].f, yyDollar[3].f
|
||||
yyVAL.f = func(ctx Context) interface{} {
|
||||
@ -535,44 +538,44 @@ yydefault:
|
||||
}
|
||||
case 8:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line expressions.y:75
|
||||
//line expressions.y:78
|
||||
{
|
||||
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, nil)
|
||||
}
|
||||
case 9:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line expressions.y:76
|
||||
//line expressions.y:79
|
||||
{
|
||||
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, yyDollar[4].f)
|
||||
}
|
||||
case 11:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line expressions.y:81
|
||||
//line expressions.y:84
|
||||
{
|
||||
a, b := yyDollar[1].f, yyDollar[3].f
|
||||
fa, fb := yyDollar[1].f, yyDollar[3].f
|
||||
yyVAL.f = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) == 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return a == b
|
||||
}
|
||||
}
|
||||
case 12:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line expressions.y:88
|
||||
//line expressions.y:91
|
||||
{
|
||||
a, b := yyDollar[1].f, yyDollar[3].f
|
||||
fa, fb := yyDollar[1].f, yyDollar[3].f
|
||||
yyVAL.f = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) < 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return generics.Less(a, b)
|
||||
}
|
||||
}
|
||||
case 13:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line expressions.y:95
|
||||
//line expressions.y:98
|
||||
{
|
||||
a, b := yyDollar[1].f, yyDollar[3].f
|
||||
fa, fb := yyDollar[1].f, yyDollar[3].f
|
||||
yyVAL.f = func(ctx Context) interface{} {
|
||||
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
|
||||
return GenericCompare(aref, bref) > 0
|
||||
a, b := fa(ctx), fb(ctx)
|
||||
return generics.Less(b, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
generics/compare.go
Normal file
111
generics/compare.go
Normal file
@ -0,0 +1,111 @@
|
||||
package generics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Equal returns a bool indicating whether a == b after conversion.
|
||||
func Equal(a, b interface{}) bool {
|
||||
return genericCompare(reflect.ValueOf(a), reflect.ValueOf(b)) == 0
|
||||
}
|
||||
|
||||
// Less returns a bool indicating whether a < b.
|
||||
func Less(a, b interface{}) bool {
|
||||
return genericCompare(reflect.ValueOf(a), reflect.ValueOf(b)) < 0
|
||||
}
|
||||
|
||||
func genericSameTypeCompare(av, bv interface{}) int {
|
||||
a, b := reflect.ValueOf(av), reflect.ValueOf(bv)
|
||||
if a.Kind() != b.Kind() {
|
||||
panic(fmt.Errorf("genericSameTypeCompare called on different types: %v and %v", a, b))
|
||||
}
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if a.Int() < b.Int() {
|
||||
return -1
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if a.Float() < b.Float() {
|
||||
return -1
|
||||
}
|
||||
case reflect.String:
|
||||
if a.String() < b.String() {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
panic(genericErrorf("unimplemented generic same-type comparison for %v and %v", a, b))
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func genericCompare(a, b reflect.Value) int {
|
||||
if a.Interface() == b.Interface() {
|
||||
return 0
|
||||
}
|
||||
ak, bk := a.Kind(), b.Kind()
|
||||
// _ = ak.Convert
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
if b.Kind() == reflect.Bool {
|
||||
switch {
|
||||
case a.Bool() && b.Bool():
|
||||
return 0
|
||||
case a.Bool():
|
||||
return 1
|
||||
case b.Bool():
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if hasIntKind(b) {
|
||||
if a.Int() < b.Int() {
|
||||
return -1
|
||||
}
|
||||
if a.Int() > b.Int() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if hasFloatKind(b) {
|
||||
return genericCompare(reflect.ValueOf(float64(a.Int())), b)
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if hasIntKind(b) {
|
||||
b = reflect.ValueOf(float64(b.Int()))
|
||||
}
|
||||
if hasFloatKind(b) {
|
||||
if a.Float() < b.Float() {
|
||||
return -1
|
||||
}
|
||||
if a.Float() > b.Float() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
panic(genericErrorf("unimplemented: comparison of %v<%s> with %v<%s>", a, ak, b, bk))
|
||||
}
|
||||
|
||||
func hasIntKind(n reflect.Value) bool {
|
||||
switch n.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasFloatKind(n reflect.Value) bool {
|
||||
switch n.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
74
generics/generics.go
Normal file
74
generics/generics.go
Normal file
@ -0,0 +1,74 @@
|
||||
package generics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// GenericError is an error regarding generic conversion.
|
||||
type GenericError string
|
||||
|
||||
func (e GenericError) Error() string { return string(e) }
|
||||
|
||||
func genericErrorf(format string, a ...interface{}) error {
|
||||
return GenericError(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Apply applies a function to arguments, converting them as necessary.
|
||||
// The conversion follows Liquid semantics, which are more aggressive than
|
||||
// Go conversion. The function should return one or two values; the second value,
|
||||
// if present, should be an error.
|
||||
func Apply(fn reflect.Value, args []interface{}) (interface{}, error) {
|
||||
in := convertArguments(fn, args)
|
||||
outs := fn.Call(in)
|
||||
if len(outs) > 1 && outs[1].Interface() != nil {
|
||||
switch e := outs[1].Interface().(type) {
|
||||
case error:
|
||||
return nil, e
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
return outs[0].Interface(), nil
|
||||
}
|
||||
|
||||
// Convert val to the type. This is a more aggressive conversion, that will
|
||||
// recursively create new map and slice values as necessary. It doesn't
|
||||
// handle circular references.
|
||||
func convertType(val interface{}, t reflect.Type) reflect.Value {
|
||||
r := reflect.ValueOf(val)
|
||||
if r.Type().ConvertibleTo(t) {
|
||||
return r.Convert(t)
|
||||
}
|
||||
if reflect.PtrTo(r.Type()) == t {
|
||||
return reflect.ValueOf(&val)
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
|
||||
break
|
||||
}
|
||||
x := reflect.MakeSlice(t, 0, r.Len())
|
||||
for i := 0; i < r.Len(); i++ {
|
||||
c := convertType(r.Index(i).Interface(), t.Elem())
|
||||
x = reflect.Append(x, c)
|
||||
}
|
||||
return x
|
||||
}
|
||||
panic(genericErrorf("convertType: can't convert %#v<%s> to %v", val, r.Type(), t))
|
||||
}
|
||||
|
||||
// Convert args to match the input types of function fn.
|
||||
func convertArguments(fn reflect.Value, in []interface{}) []reflect.Value {
|
||||
rt := fn.Type()
|
||||
out := make([]reflect.Value, rt.NumIn())
|
||||
for i, arg := range in {
|
||||
if i < rt.NumIn() {
|
||||
out[i] = convertType(arg, rt.In(i))
|
||||
}
|
||||
}
|
||||
for i := len(in); i < rt.NumIn(); i++ {
|
||||
out[i] = reflect.Zero(rt.In(i))
|
||||
}
|
||||
return out
|
||||
}
|
72
generics/sort.go
Normal file
72
generics/sort.go
Normal file
@ -0,0 +1,72 @@
|
||||
package generics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type genericSortable []interface{}
|
||||
|
||||
// Sort any []interface{} value.
|
||||
func Sort(data []interface{}) {
|
||||
sort.Sort(genericSortable(data))
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (s genericSortable) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (s genericSortable) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface.
|
||||
func (s genericSortable) Less(i, j int) bool {
|
||||
return genericSameTypeCompare(s[i], s[j]) < 0
|
||||
}
|
||||
|
||||
// SortByProperty sorts maps on their key indices.
|
||||
func SortByProperty(data []interface{}, key string) {
|
||||
sort.Sort(sortableByProperty{data, key})
|
||||
}
|
||||
|
||||
type sortableByProperty struct {
|
||||
data []interface{}
|
||||
key string
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (s sortableByProperty) Len() int {
|
||||
return len(s.data)
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (s sortableByProperty) Swap(i, j int) {
|
||||
data := s.data
|
||||
data[i], data[j] = data[j], data[i]
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface.
|
||||
func (s sortableByProperty) Less(i, j int) bool {
|
||||
// index returns the value at the s.key, if in is a map that contains this key
|
||||
index := func(in interface{}) interface{} {
|
||||
rt := reflect.ValueOf(in)
|
||||
if rt.Kind() == reflect.Map && rt.Type().Key().Kind() == reflect.String {
|
||||
return rt.MapIndex(reflect.ValueOf(s.key)).Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
a, b := index(s.data[i]), index(s.data[j])
|
||||
// TODO implement nil-first vs. nil last
|
||||
switch {
|
||||
case a == nil:
|
||||
return true
|
||||
case b == nil:
|
||||
return false
|
||||
default:
|
||||
// TODO relax same type requirement
|
||||
return genericSameTypeCompare(a, b) < 0
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user