|
|
@@ -0,0 +1,840 @@
|
|
|
+package jmespath
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "math"
|
|
|
+ "sort"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "unicode/utf8"
|
|
|
+)
|
|
|
+
|
|
|
+type jpFunction func(arguments []interface{}) (interface{}, error)
|
|
|
+
|
|
|
+type jpType string
|
|
|
+
|
|
|
+const (
|
|
|
+ jpUnknown jpType = "unknown"
|
|
|
+ jpNumber jpType = "number"
|
|
|
+ jpString jpType = "string"
|
|
|
+ jpArray jpType = "array"
|
|
|
+ jpObject jpType = "object"
|
|
|
+ jpArrayNumber jpType = "array[number]"
|
|
|
+ jpArrayString jpType = "array[string]"
|
|
|
+ jpExpref jpType = "expref"
|
|
|
+ jpAny jpType = "any"
|
|
|
+)
|
|
|
+
|
|
|
+type functionEntry struct {
|
|
|
+ name string
|
|
|
+ arguments []argSpec
|
|
|
+ handler jpFunction
|
|
|
+ hasExpRef bool
|
|
|
+}
|
|
|
+
|
|
|
+type argSpec struct {
|
|
|
+ types []jpType
|
|
|
+ variadic bool
|
|
|
+}
|
|
|
+
|
|
|
+type byExprString struct {
|
|
|
+ intr *treeInterpreter
|
|
|
+ node ASTNode
|
|
|
+ items []interface{}
|
|
|
+ hasError bool
|
|
|
+}
|
|
|
+
|
|
|
+func (a *byExprString) Len() int {
|
|
|
+ return len(a.items)
|
|
|
+}
|
|
|
+func (a *byExprString) Swap(i, j int) {
|
|
|
+ a.items[i], a.items[j] = a.items[j], a.items[i]
|
|
|
+}
|
|
|
+func (a *byExprString) Less(i, j int) bool {
|
|
|
+ first, err := a.intr.Execute(a.node, a.items[i])
|
|
|
+ if err != nil {
|
|
|
+ a.hasError = true
|
|
|
+ // Return a dummy value.
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ ith, ok := first.(string)
|
|
|
+ if !ok {
|
|
|
+ a.hasError = true
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ second, err := a.intr.Execute(a.node, a.items[j])
|
|
|
+ if err != nil {
|
|
|
+ a.hasError = true
|
|
|
+ // Return a dummy value.
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ jth, ok := second.(string)
|
|
|
+ if !ok {
|
|
|
+ a.hasError = true
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return ith < jth
|
|
|
+}
|
|
|
+
|
|
|
+type byExprFloat struct {
|
|
|
+ intr *treeInterpreter
|
|
|
+ node ASTNode
|
|
|
+ items []interface{}
|
|
|
+ hasError bool
|
|
|
+}
|
|
|
+
|
|
|
+func (a *byExprFloat) Len() int {
|
|
|
+ return len(a.items)
|
|
|
+}
|
|
|
+func (a *byExprFloat) Swap(i, j int) {
|
|
|
+ a.items[i], a.items[j] = a.items[j], a.items[i]
|
|
|
+}
|
|
|
+func (a *byExprFloat) Less(i, j int) bool {
|
|
|
+ first, err := a.intr.Execute(a.node, a.items[i])
|
|
|
+ if err != nil {
|
|
|
+ a.hasError = true
|
|
|
+ // Return a dummy value.
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ ith, ok := first.(float64)
|
|
|
+ if !ok {
|
|
|
+ a.hasError = true
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ second, err := a.intr.Execute(a.node, a.items[j])
|
|
|
+ if err != nil {
|
|
|
+ a.hasError = true
|
|
|
+ // Return a dummy value.
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ jth, ok := second.(float64)
|
|
|
+ if !ok {
|
|
|
+ a.hasError = true
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return ith < jth
|
|
|
+}
|
|
|
+
|
|
|
+type functionCaller struct {
|
|
|
+ functionTable map[string]functionEntry
|
|
|
+}
|
|
|
+
|
|
|
+func newFunctionCaller() *functionCaller {
|
|
|
+ caller := &functionCaller{}
|
|
|
+ caller.functionTable = map[string]functionEntry{
|
|
|
+ "length": functionEntry{
|
|
|
+ name: "length",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpString, jpArray, jpObject}},
|
|
|
+ },
|
|
|
+ handler: jpfLength,
|
|
|
+ },
|
|
|
+ "starts_with": functionEntry{
|
|
|
+ name: "starts_with",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpString}},
|
|
|
+ argSpec{types: []jpType{jpString}},
|
|
|
+ },
|
|
|
+ handler: jpfStartsWith,
|
|
|
+ },
|
|
|
+ "abs": functionEntry{
|
|
|
+ name: "abs",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfAbs,
|
|
|
+ },
|
|
|
+ "avg": functionEntry{
|
|
|
+ name: "avg",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArrayNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfAvg,
|
|
|
+ },
|
|
|
+ "ceil": functionEntry{
|
|
|
+ name: "ceil",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfCeil,
|
|
|
+ },
|
|
|
+ "contains": functionEntry{
|
|
|
+ name: "contains",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArray, jpString}},
|
|
|
+ argSpec{types: []jpType{jpAny}},
|
|
|
+ },
|
|
|
+ handler: jpfContains,
|
|
|
+ },
|
|
|
+ "ends_with": functionEntry{
|
|
|
+ name: "ends_with",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpString}},
|
|
|
+ argSpec{types: []jpType{jpString}},
|
|
|
+ },
|
|
|
+ handler: jpfEndsWith,
|
|
|
+ },
|
|
|
+ "floor": functionEntry{
|
|
|
+ name: "floor",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfFloor,
|
|
|
+ },
|
|
|
+ "map": functionEntry{
|
|
|
+ name: "amp",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpExpref}},
|
|
|
+ argSpec{types: []jpType{jpArray}},
|
|
|
+ },
|
|
|
+ handler: jpfMap,
|
|
|
+ hasExpRef: true,
|
|
|
+ },
|
|
|
+ "max": functionEntry{
|
|
|
+ name: "max",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArrayNumber, jpArrayString}},
|
|
|
+ },
|
|
|
+ handler: jpfMax,
|
|
|
+ },
|
|
|
+ "merge": functionEntry{
|
|
|
+ name: "merge",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpObject}, variadic: true},
|
|
|
+ },
|
|
|
+ handler: jpfMerge,
|
|
|
+ },
|
|
|
+ "max_by": functionEntry{
|
|
|
+ name: "max_by",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArray}},
|
|
|
+ argSpec{types: []jpType{jpExpref}},
|
|
|
+ },
|
|
|
+ handler: jpfMaxBy,
|
|
|
+ hasExpRef: true,
|
|
|
+ },
|
|
|
+ "sum": functionEntry{
|
|
|
+ name: "sum",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArrayNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfSum,
|
|
|
+ },
|
|
|
+ "min": functionEntry{
|
|
|
+ name: "min",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArrayNumber, jpArrayString}},
|
|
|
+ },
|
|
|
+ handler: jpfMin,
|
|
|
+ },
|
|
|
+ "min_by": functionEntry{
|
|
|
+ name: "min_by",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArray}},
|
|
|
+ argSpec{types: []jpType{jpExpref}},
|
|
|
+ },
|
|
|
+ handler: jpfMinBy,
|
|
|
+ hasExpRef: true,
|
|
|
+ },
|
|
|
+ "type": functionEntry{
|
|
|
+ name: "type",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpAny}},
|
|
|
+ },
|
|
|
+ handler: jpfType,
|
|
|
+ },
|
|
|
+ "keys": functionEntry{
|
|
|
+ name: "keys",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpObject}},
|
|
|
+ },
|
|
|
+ handler: jpfKeys,
|
|
|
+ },
|
|
|
+ "values": functionEntry{
|
|
|
+ name: "values",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpObject}},
|
|
|
+ },
|
|
|
+ handler: jpfValues,
|
|
|
+ },
|
|
|
+ "sort": functionEntry{
|
|
|
+ name: "sort",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArrayString, jpArrayNumber}},
|
|
|
+ },
|
|
|
+ handler: jpfSort,
|
|
|
+ },
|
|
|
+ "sort_by": functionEntry{
|
|
|
+ name: "sort_by",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArray}},
|
|
|
+ argSpec{types: []jpType{jpExpref}},
|
|
|
+ },
|
|
|
+ handler: jpfSortBy,
|
|
|
+ hasExpRef: true,
|
|
|
+ },
|
|
|
+ "join": functionEntry{
|
|
|
+ name: "join",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpString}},
|
|
|
+ argSpec{types: []jpType{jpArrayString}},
|
|
|
+ },
|
|
|
+ handler: jpfJoin,
|
|
|
+ },
|
|
|
+ "reverse": functionEntry{
|
|
|
+ name: "reverse",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpArray, jpString}},
|
|
|
+ },
|
|
|
+ handler: jpfReverse,
|
|
|
+ },
|
|
|
+ "to_array": functionEntry{
|
|
|
+ name: "to_array",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpAny}},
|
|
|
+ },
|
|
|
+ handler: jpfToArray,
|
|
|
+ },
|
|
|
+ "to_string": functionEntry{
|
|
|
+ name: "to_string",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpAny}},
|
|
|
+ },
|
|
|
+ handler: jpfToString,
|
|
|
+ },
|
|
|
+ "to_number": functionEntry{
|
|
|
+ name: "to_number",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpAny}},
|
|
|
+ },
|
|
|
+ handler: jpfToNumber,
|
|
|
+ },
|
|
|
+ "not_null": functionEntry{
|
|
|
+ name: "not_null",
|
|
|
+ arguments: []argSpec{
|
|
|
+ argSpec{types: []jpType{jpAny}, variadic: true},
|
|
|
+ },
|
|
|
+ handler: jpfNotNull,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ return caller
|
|
|
+}
|
|
|
+
|
|
|
+func (e *functionEntry) resolveArgs(arguments []interface{}) ([]interface{}, error) {
|
|
|
+ if len(e.arguments) == 0 {
|
|
|
+ return arguments, nil
|
|
|
+ }
|
|
|
+ if !e.arguments[len(e.arguments)-1].variadic {
|
|
|
+ if len(e.arguments) != len(arguments) {
|
|
|
+ return nil, errors.New("incorrect number of args")
|
|
|
+ }
|
|
|
+ for i, spec := range e.arguments {
|
|
|
+ userArg := arguments[i]
|
|
|
+ err := spec.typeCheck(userArg)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return arguments, nil
|
|
|
+ }
|
|
|
+ if len(arguments) < len(e.arguments) {
|
|
|
+ return nil, errors.New("Invalid arity.")
|
|
|
+ }
|
|
|
+ return arguments, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (a *argSpec) typeCheck(arg interface{}) error {
|
|
|
+ for _, t := range a.types {
|
|
|
+ switch t {
|
|
|
+ case jpNumber:
|
|
|
+ if _, ok := arg.(float64); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpString:
|
|
|
+ if _, ok := arg.(string); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpArray:
|
|
|
+ if _, ok := arg.([]interface{}); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpObject:
|
|
|
+ if _, ok := arg.(map[string]interface{}); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpArrayNumber:
|
|
|
+ if _, ok := toArrayNum(arg); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpArrayString:
|
|
|
+ if _, ok := toArrayStr(arg); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case jpAny:
|
|
|
+ return nil
|
|
|
+ case jpExpref:
|
|
|
+ if _, ok := arg.(expRef); ok {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fmt.Errorf("Invalid type for: %v, expected: %#v", arg, a.types)
|
|
|
+}
|
|
|
+
|
|
|
+func (f *functionCaller) CallFunction(name string, arguments []interface{}, intr *treeInterpreter) (interface{}, error) {
|
|
|
+ entry, ok := f.functionTable[name]
|
|
|
+ if !ok {
|
|
|
+ return nil, errors.New("unknown function: " + name)
|
|
|
+ }
|
|
|
+ resolvedArgs, err := entry.resolveArgs(arguments)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if entry.hasExpRef {
|
|
|
+ var extra []interface{}
|
|
|
+ extra = append(extra, intr)
|
|
|
+ resolvedArgs = append(extra, resolvedArgs...)
|
|
|
+ }
|
|
|
+ return entry.handler(resolvedArgs)
|
|
|
+}
|
|
|
+
|
|
|
+func jpfAbs(arguments []interface{}) (interface{}, error) {
|
|
|
+ num := arguments[0].(float64)
|
|
|
+ return math.Abs(num), nil
|
|
|
+}
|
|
|
+
|
|
|
+func jpfLength(arguments []interface{}) (interface{}, error) {
|
|
|
+ arg := arguments[0]
|
|
|
+ if c, ok := arg.(string); ok {
|
|
|
+ return float64(utf8.RuneCountInString(c)), nil
|
|
|
+ } else if c, ok := arg.([]interface{}); ok {
|
|
|
+ return float64(len(c)), nil
|
|
|
+ } else if c, ok := arg.(map[string]interface{}); ok {
|
|
|
+ return float64(len(c)), nil
|
|
|
+ }
|
|
|
+ return nil, errors.New("could not compute length()")
|
|
|
+}
|
|
|
+
|
|
|
+func jpfStartsWith(arguments []interface{}) (interface{}, error) {
|
|
|
+ search := arguments[0].(string)
|
|
|
+ prefix := arguments[1].(string)
|
|
|
+ return strings.HasPrefix(search, prefix), nil
|
|
|
+}
|
|
|
+
|
|
|
+func jpfAvg(arguments []interface{}) (interface{}, error) {
|
|
|
+ // We've already type checked the value so we can safely use
|
|
|
+ // type assertions.
|
|
|
+ args := arguments[0].([]interface{})
|
|
|
+ length := float64(len(args))
|
|
|
+ numerator := 0.0
|
|
|
+ for _, n := range args {
|
|
|
+ numerator += n.(float64)
|
|
|
+ }
|
|
|
+ return numerator / length, nil
|
|
|
+}
|
|
|
+func jpfCeil(arguments []interface{}) (interface{}, error) {
|
|
|
+ val := arguments[0].(float64)
|
|
|
+ return math.Ceil(val), nil
|
|
|
+}
|
|
|
+func jpfContains(arguments []interface{}) (interface{}, error) {
|
|
|
+ search := arguments[0]
|
|
|
+ el := arguments[1]
|
|
|
+ if searchStr, ok := search.(string); ok {
|
|
|
+ if elStr, ok := el.(string); ok {
|
|
|
+ return strings.Index(searchStr, elStr) != -1, nil
|
|
|
+ }
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ // Otherwise this is a generic contains for []interface{}
|
|
|
+ general := search.([]interface{})
|
|
|
+ for _, item := range general {
|
|
|
+ if item == el {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false, nil
|
|
|
+}
|
|
|
+func jpfEndsWith(arguments []interface{}) (interface{}, error) {
|
|
|
+ search := arguments[0].(string)
|
|
|
+ suffix := arguments[1].(string)
|
|
|
+ return strings.HasSuffix(search, suffix), nil
|
|
|
+}
|
|
|
+func jpfFloor(arguments []interface{}) (interface{}, error) {
|
|
|
+ val := arguments[0].(float64)
|
|
|
+ return math.Floor(val), nil
|
|
|
+}
|
|
|
+func jpfMap(arguments []interface{}) (interface{}, error) {
|
|
|
+ intr := arguments[0].(*treeInterpreter)
|
|
|
+ exp := arguments[1].(expRef)
|
|
|
+ node := exp.ref
|
|
|
+ arr := arguments[2].([]interface{})
|
|
|
+ mapped := make([]interface{}, 0, len(arr))
|
|
|
+ for _, value := range arr {
|
|
|
+ current, err := intr.Execute(node, value)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ mapped = append(mapped, current)
|
|
|
+ }
|
|
|
+ return mapped, nil
|
|
|
+}
|
|
|
+func jpfMax(arguments []interface{}) (interface{}, error) {
|
|
|
+ if items, ok := toArrayNum(arguments[0]); ok {
|
|
|
+ if len(items) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if len(items) == 1 {
|
|
|
+ return items[0], nil
|
|
|
+ }
|
|
|
+ best := items[0]
|
|
|
+ for _, item := range items[1:] {
|
|
|
+ if item > best {
|
|
|
+ best = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return best, nil
|
|
|
+ }
|
|
|
+ // Otherwise we're dealing with a max() of strings.
|
|
|
+ items, _ := toArrayStr(arguments[0])
|
|
|
+ if len(items) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if len(items) == 1 {
|
|
|
+ return items[0], nil
|
|
|
+ }
|
|
|
+ best := items[0]
|
|
|
+ for _, item := range items[1:] {
|
|
|
+ if item > best {
|
|
|
+ best = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return best, nil
|
|
|
+}
|
|
|
+func jpfMerge(arguments []interface{}) (interface{}, error) {
|
|
|
+ final := make(map[string]interface{})
|
|
|
+ for _, m := range arguments {
|
|
|
+ mapped := m.(map[string]interface{})
|
|
|
+ for key, value := range mapped {
|
|
|
+ final[key] = value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return final, nil
|
|
|
+}
|
|
|
+func jpfMaxBy(arguments []interface{}) (interface{}, error) {
|
|
|
+ intr := arguments[0].(*treeInterpreter)
|
|
|
+ arr := arguments[1].([]interface{})
|
|
|
+ exp := arguments[2].(expRef)
|
|
|
+ node := exp.ref
|
|
|
+ if len(arr) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ } else if len(arr) == 1 {
|
|
|
+ return arr[0], nil
|
|
|
+ }
|
|
|
+ start, err := intr.Execute(node, arr[0])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ switch t := start.(type) {
|
|
|
+ case float64:
|
|
|
+ bestVal := t
|
|
|
+ bestItem := arr[0]
|
|
|
+ for _, item := range arr[1:] {
|
|
|
+ result, err := intr.Execute(node, item)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ current, ok := result.(float64)
|
|
|
+ if !ok {
|
|
|
+ return nil, errors.New("invalid type, must be number")
|
|
|
+ }
|
|
|
+ if current > bestVal {
|
|
|
+ bestVal = current
|
|
|
+ bestItem = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return bestItem, nil
|
|
|
+ case string:
|
|
|
+ bestVal := t
|
|
|
+ bestItem := arr[0]
|
|
|
+ for _, item := range arr[1:] {
|
|
|
+ result, err := intr.Execute(node, item)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ current, ok := result.(string)
|
|
|
+ if !ok {
|
|
|
+ return nil, errors.New("invalid type, must be string")
|
|
|
+ }
|
|
|
+ if current > bestVal {
|
|
|
+ bestVal = current
|
|
|
+ bestItem = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return bestItem, nil
|
|
|
+ default:
|
|
|
+ return nil, errors.New("invalid type, must be number of string")
|
|
|
+ }
|
|
|
+}
|
|
|
+func jpfSum(arguments []interface{}) (interface{}, error) {
|
|
|
+ items, _ := toArrayNum(arguments[0])
|
|
|
+ sum := 0.0
|
|
|
+ for _, item := range items {
|
|
|
+ sum += item
|
|
|
+ }
|
|
|
+ return sum, nil
|
|
|
+}
|
|
|
+
|
|
|
+func jpfMin(arguments []interface{}) (interface{}, error) {
|
|
|
+ if items, ok := toArrayNum(arguments[0]); ok {
|
|
|
+ if len(items) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if len(items) == 1 {
|
|
|
+ return items[0], nil
|
|
|
+ }
|
|
|
+ best := items[0]
|
|
|
+ for _, item := range items[1:] {
|
|
|
+ if item < best {
|
|
|
+ best = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return best, nil
|
|
|
+ }
|
|
|
+ items, _ := toArrayStr(arguments[0])
|
|
|
+ if len(items) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if len(items) == 1 {
|
|
|
+ return items[0], nil
|
|
|
+ }
|
|
|
+ best := items[0]
|
|
|
+ for _, item := range items[1:] {
|
|
|
+ if item < best {
|
|
|
+ best = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return best, nil
|
|
|
+}
|
|
|
+
|
|
|
+func jpfMinBy(arguments []interface{}) (interface{}, error) {
|
|
|
+ intr := arguments[0].(*treeInterpreter)
|
|
|
+ arr := arguments[1].([]interface{})
|
|
|
+ exp := arguments[2].(expRef)
|
|
|
+ node := exp.ref
|
|
|
+ if len(arr) == 0 {
|
|
|
+ return nil, nil
|
|
|
+ } else if len(arr) == 1 {
|
|
|
+ return arr[0], nil
|
|
|
+ }
|
|
|
+ start, err := intr.Execute(node, arr[0])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if t, ok := start.(float64); ok {
|
|
|
+ bestVal := t
|
|
|
+ bestItem := arr[0]
|
|
|
+ for _, item := range arr[1:] {
|
|
|
+ result, err := intr.Execute(node, item)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ current, ok := result.(float64)
|
|
|
+ if !ok {
|
|
|
+ return nil, errors.New("invalid type, must be number")
|
|
|
+ }
|
|
|
+ if current < bestVal {
|
|
|
+ bestVal = current
|
|
|
+ bestItem = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return bestItem, nil
|
|
|
+ } else if t, ok := start.(string); ok {
|
|
|
+ bestVal := t
|
|
|
+ bestItem := arr[0]
|
|
|
+ for _, item := range arr[1:] {
|
|
|
+ result, err := intr.Execute(node, item)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ current, ok := result.(string)
|
|
|
+ if !ok {
|
|
|
+ return nil, errors.New("invalid type, must be string")
|
|
|
+ }
|
|
|
+ if current < bestVal {
|
|
|
+ bestVal = current
|
|
|
+ bestItem = item
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return bestItem, nil
|
|
|
+ } else {
|
|
|
+ return nil, errors.New("invalid type, must be number of string")
|
|
|
+ }
|
|
|
+}
|
|
|
+func jpfType(arguments []interface{}) (interface{}, error) {
|
|
|
+ arg := arguments[0]
|
|
|
+ if _, ok := arg.(float64); ok {
|
|
|
+ return "number", nil
|
|
|
+ }
|
|
|
+ if _, ok := arg.(string); ok {
|
|
|
+ return "string", nil
|
|
|
+ }
|
|
|
+ if _, ok := arg.([]interface{}); ok {
|
|
|
+ return "array", nil
|
|
|
+ }
|
|
|
+ if _, ok := arg.(map[string]interface{}); ok {
|
|
|
+ return "object", nil
|
|
|
+ }
|
|
|
+ if arg == nil {
|
|
|
+ return "null", nil
|
|
|
+ }
|
|
|
+ if arg == true || arg == false {
|
|
|
+ return "boolean", nil
|
|
|
+ }
|
|
|
+ return nil, errors.New("unknown type")
|
|
|
+}
|
|
|
+func jpfKeys(arguments []interface{}) (interface{}, error) {
|
|
|
+ arg := arguments[0].(map[string]interface{})
|
|
|
+ collected := make([]interface{}, 0, len(arg))
|
|
|
+ for key := range arg {
|
|
|
+ collected = append(collected, key)
|
|
|
+ }
|
|
|
+ return collected, nil
|
|
|
+}
|
|
|
+func jpfValues(arguments []interface{}) (interface{}, error) {
|
|
|
+ arg := arguments[0].(map[string]interface{})
|
|
|
+ collected := make([]interface{}, 0, len(arg))
|
|
|
+ for _, value := range arg {
|
|
|
+ collected = append(collected, value)
|
|
|
+ }
|
|
|
+ return collected, nil
|
|
|
+}
|
|
|
+func jpfSort(arguments []interface{}) (interface{}, error) {
|
|
|
+ if items, ok := toArrayNum(arguments[0]); ok {
|
|
|
+ d := sort.Float64Slice(items)
|
|
|
+ sort.Stable(d)
|
|
|
+ final := make([]interface{}, len(d))
|
|
|
+ for i, val := range d {
|
|
|
+ final[i] = val
|
|
|
+ }
|
|
|
+ return final, nil
|
|
|
+ }
|
|
|
+ // Otherwise we're dealing with sort()'ing strings.
|
|
|
+ items, _ := toArrayStr(arguments[0])
|
|
|
+ d := sort.StringSlice(items)
|
|
|
+ sort.Stable(d)
|
|
|
+ final := make([]interface{}, len(d))
|
|
|
+ for i, val := range d {
|
|
|
+ final[i] = val
|
|
|
+ }
|
|
|
+ return final, nil
|
|
|
+}
|
|
|
+func jpfSortBy(arguments []interface{}) (interface{}, error) {
|
|
|
+ intr := arguments[0].(*treeInterpreter)
|
|
|
+ arr := arguments[1].([]interface{})
|
|
|
+ exp := arguments[2].(expRef)
|
|
|
+ node := exp.ref
|
|
|
+ if len(arr) == 0 {
|
|
|
+ return arr, nil
|
|
|
+ } else if len(arr) == 1 {
|
|
|
+ return arr, nil
|
|
|
+ }
|
|
|
+ start, err := intr.Execute(node, arr[0])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if _, ok := start.(float64); ok {
|
|
|
+ sortable := &byExprFloat{intr, node, arr, false}
|
|
|
+ sort.Stable(sortable)
|
|
|
+ if sortable.hasError {
|
|
|
+ return nil, errors.New("error in sort_by comparison")
|
|
|
+ }
|
|
|
+ return arr, nil
|
|
|
+ } else if _, ok := start.(string); ok {
|
|
|
+ sortable := &byExprString{intr, node, arr, false}
|
|
|
+ sort.Stable(sortable)
|
|
|
+ if sortable.hasError {
|
|
|
+ return nil, errors.New("error in sort_by comparison")
|
|
|
+ }
|
|
|
+ return arr, nil
|
|
|
+ } else {
|
|
|
+ return nil, errors.New("invalid type, must be number of string")
|
|
|
+ }
|
|
|
+}
|
|
|
+func jpfJoin(arguments []interface{}) (interface{}, error) {
|
|
|
+ sep := arguments[0].(string)
|
|
|
+ // We can't just do arguments[1].([]string), we have to
|
|
|
+ // manually convert each item to a string.
|
|
|
+ arrayStr := []string{}
|
|
|
+ for _, item := range arguments[1].([]interface{}) {
|
|
|
+ arrayStr = append(arrayStr, item.(string))
|
|
|
+ }
|
|
|
+ return strings.Join(arrayStr, sep), nil
|
|
|
+}
|
|
|
+func jpfReverse(arguments []interface{}) (interface{}, error) {
|
|
|
+ if s, ok := arguments[0].(string); ok {
|
|
|
+ r := []rune(s)
|
|
|
+ for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
|
|
|
+ r[i], r[j] = r[j], r[i]
|
|
|
+ }
|
|
|
+ return string(r), nil
|
|
|
+ }
|
|
|
+ items := arguments[0].([]interface{})
|
|
|
+ length := len(items)
|
|
|
+ reversed := make([]interface{}, length)
|
|
|
+ for i, item := range items {
|
|
|
+ reversed[length-(i+1)] = item
|
|
|
+ }
|
|
|
+ return reversed, nil
|
|
|
+}
|
|
|
+func jpfToArray(arguments []interface{}) (interface{}, error) {
|
|
|
+ if _, ok := arguments[0].([]interface{}); ok {
|
|
|
+ return arguments[0], nil
|
|
|
+ }
|
|
|
+ return arguments[:1:1], nil
|
|
|
+}
|
|
|
+func jpfToString(arguments []interface{}) (interface{}, error) {
|
|
|
+ if v, ok := arguments[0].(string); ok {
|
|
|
+ return v, nil
|
|
|
+ }
|
|
|
+ result, err := json.Marshal(arguments[0])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return string(result), nil
|
|
|
+}
|
|
|
+func jpfToNumber(arguments []interface{}) (interface{}, error) {
|
|
|
+ arg := arguments[0]
|
|
|
+ if v, ok := arg.(float64); ok {
|
|
|
+ return v, nil
|
|
|
+ }
|
|
|
+ if v, ok := arg.(string); ok {
|
|
|
+ conv, err := strconv.ParseFloat(v, 64)
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ return conv, nil
|
|
|
+ }
|
|
|
+ if _, ok := arg.([]interface{}); ok {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if _, ok := arg.(map[string]interface{}); ok {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if arg == nil {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ if arg == true || arg == false {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ return nil, errors.New("unknown type")
|
|
|
+}
|
|
|
+func jpfNotNull(arguments []interface{}) (interface{}, error) {
|
|
|
+ for _, arg := range arguments {
|
|
|
+ if arg != nil {
|
|
|
+ return arg, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil, nil
|
|
|
+}
|