a685e3fc98
Vndr has a simpler configuration and allows pointing to forked packages. Additionally other docker projects are now using vndr making vendoring in distribution more consistent. Updates letsencrypt to use fork. No longer uses sub-vendored packages. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
843 lines
18 KiB
Go
843 lines
18 KiB
Go
package jmespath
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"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": {
|
|
name: "length",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpString, jpArray, jpObject}},
|
|
},
|
|
handler: jpfLength,
|
|
},
|
|
"starts_with": {
|
|
name: "starts_with",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpString}},
|
|
{types: []jpType{jpString}},
|
|
},
|
|
handler: jpfStartsWith,
|
|
},
|
|
"abs": {
|
|
name: "abs",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpNumber}},
|
|
},
|
|
handler: jpfAbs,
|
|
},
|
|
"avg": {
|
|
name: "avg",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArrayNumber}},
|
|
},
|
|
handler: jpfAvg,
|
|
},
|
|
"ceil": {
|
|
name: "ceil",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpNumber}},
|
|
},
|
|
handler: jpfCeil,
|
|
},
|
|
"contains": {
|
|
name: "contains",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArray, jpString}},
|
|
{types: []jpType{jpAny}},
|
|
},
|
|
handler: jpfContains,
|
|
},
|
|
"ends_with": {
|
|
name: "ends_with",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpString}},
|
|
{types: []jpType{jpString}},
|
|
},
|
|
handler: jpfEndsWith,
|
|
},
|
|
"floor": {
|
|
name: "floor",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpNumber}},
|
|
},
|
|
handler: jpfFloor,
|
|
},
|
|
"map": {
|
|
name: "amp",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpExpref}},
|
|
{types: []jpType{jpArray}},
|
|
},
|
|
handler: jpfMap,
|
|
hasExpRef: true,
|
|
},
|
|
"max": {
|
|
name: "max",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArrayNumber, jpArrayString}},
|
|
},
|
|
handler: jpfMax,
|
|
},
|
|
"merge": {
|
|
name: "merge",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpObject}, variadic: true},
|
|
},
|
|
handler: jpfMerge,
|
|
},
|
|
"max_by": {
|
|
name: "max_by",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArray}},
|
|
{types: []jpType{jpExpref}},
|
|
},
|
|
handler: jpfMaxBy,
|
|
hasExpRef: true,
|
|
},
|
|
"sum": {
|
|
name: "sum",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArrayNumber}},
|
|
},
|
|
handler: jpfSum,
|
|
},
|
|
"min": {
|
|
name: "min",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArrayNumber, jpArrayString}},
|
|
},
|
|
handler: jpfMin,
|
|
},
|
|
"min_by": {
|
|
name: "min_by",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArray}},
|
|
{types: []jpType{jpExpref}},
|
|
},
|
|
handler: jpfMinBy,
|
|
hasExpRef: true,
|
|
},
|
|
"type": {
|
|
name: "type",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpAny}},
|
|
},
|
|
handler: jpfType,
|
|
},
|
|
"keys": {
|
|
name: "keys",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpObject}},
|
|
},
|
|
handler: jpfKeys,
|
|
},
|
|
"values": {
|
|
name: "values",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpObject}},
|
|
},
|
|
handler: jpfValues,
|
|
},
|
|
"sort": {
|
|
name: "sort",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArrayString, jpArrayNumber}},
|
|
},
|
|
handler: jpfSort,
|
|
},
|
|
"sort_by": {
|
|
name: "sort_by",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArray}},
|
|
{types: []jpType{jpExpref}},
|
|
},
|
|
handler: jpfSortBy,
|
|
hasExpRef: true,
|
|
},
|
|
"join": {
|
|
name: "join",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpString}},
|
|
{types: []jpType{jpArrayString}},
|
|
},
|
|
handler: jpfJoin,
|
|
},
|
|
"reverse": {
|
|
name: "reverse",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpArray, jpString}},
|
|
},
|
|
handler: jpfReverse,
|
|
},
|
|
"to_array": {
|
|
name: "to_array",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpAny}},
|
|
},
|
|
handler: jpfToArray,
|
|
},
|
|
"to_string": {
|
|
name: "to_string",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpAny}},
|
|
},
|
|
handler: jpfToString,
|
|
},
|
|
"to_number": {
|
|
name: "to_number",
|
|
arguments: []argSpec{
|
|
{types: []jpType{jpAny}},
|
|
},
|
|
handler: jpfToNumber,
|
|
},
|
|
"not_null": {
|
|
name: "not_null",
|
|
arguments: []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 isSliceType(arg) {
|
|
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 isSliceType(arg) {
|
|
v := reflect.ValueOf(arg)
|
|
return float64(v.Len()), 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
|
|
}
|