mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	jsonpath template
This commit is contained in:
		
							
								
								
									
										20
									
								
								pkg/util/jsonpath/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								pkg/util/jsonpath/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// package jsonpath is a template engine using jsonpath syntax,
 | 
			
		||||
// which can be seen at http://goessner.net/articles/JsonPath/.
 | 
			
		||||
// In addition, it has {range} {end} function to iterate list and slice.
 | 
			
		||||
package jsonpath
 | 
			
		||||
							
								
								
									
										472
									
								
								pkg/util/jsonpath/jsonpath.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								pkg/util/jsonpath/jsonpath.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,472 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package jsonpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/third_party/golang/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type JSONPath struct {
 | 
			
		||||
	name       string
 | 
			
		||||
	parser     *Parser
 | 
			
		||||
	stack      [][]reflect.Value //push and pop values in different scopes
 | 
			
		||||
	cur        []reflect.Value   //current scope values
 | 
			
		||||
	beginRange int
 | 
			
		||||
	inRange    int
 | 
			
		||||
	endRange   int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(name string) *JSONPath {
 | 
			
		||||
	return &JSONPath{
 | 
			
		||||
		name:       name,
 | 
			
		||||
		beginRange: 0,
 | 
			
		||||
		inRange:    0,
 | 
			
		||||
		endRange:   0,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parse the given template, return error
 | 
			
		||||
func (j *JSONPath) Parse(text string) (err error) {
 | 
			
		||||
	j.parser, err = Parse(j.name, text)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute bounds data into template and write the result
 | 
			
		||||
func (j *JSONPath) Execute(wr io.Writer, data interface{}) error {
 | 
			
		||||
	if j.parser == nil {
 | 
			
		||||
		return fmt.Errorf("%s is an incomplete jsonpath template", j.name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	j.cur = []reflect.Value{reflect.ValueOf(data)}
 | 
			
		||||
	nodes := j.parser.Root.Nodes
 | 
			
		||||
	for i := 0; i < len(nodes); i++ {
 | 
			
		||||
		node := nodes[i]
 | 
			
		||||
		results, err := j.walk(j.cur, node)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//encounter an end node, break the current block
 | 
			
		||||
		if j.endRange > 0 && j.endRange <= j.inRange {
 | 
			
		||||
			j.endRange -= 1
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		//encounter a range node, start a range loop
 | 
			
		||||
		if j.beginRange > 0 {
 | 
			
		||||
			j.beginRange -= 1
 | 
			
		||||
			j.inRange += 1
 | 
			
		||||
			for k, value := range results {
 | 
			
		||||
				j.parser.Root.Nodes = nodes[i+1:]
 | 
			
		||||
				if k == len(results)-1 {
 | 
			
		||||
					j.inRange -= 1
 | 
			
		||||
				}
 | 
			
		||||
				err := j.Execute(wr, value.Interface())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		err = j.PrintResults(wr, results)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrintResults write the results into writer
 | 
			
		||||
func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error {
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		text, err := j.evalToText(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if i != len(results)-1 {
 | 
			
		||||
			text = append(text, ' ')
 | 
			
		||||
		}
 | 
			
		||||
		if _, err = wr.Write(text); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// walk visits tree rooted at the given node in DFS order
 | 
			
		||||
func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) {
 | 
			
		||||
	switch node := node.(type) {
 | 
			
		||||
	case *ListNode:
 | 
			
		||||
		return j.evalList(value, node)
 | 
			
		||||
	case *TextNode:
 | 
			
		||||
		return []reflect.Value{reflect.ValueOf(string(node.Text))}, nil
 | 
			
		||||
	case *FieldNode:
 | 
			
		||||
		return j.evalField(value, node)
 | 
			
		||||
	case *ArrayNode:
 | 
			
		||||
		return j.evalArray(value, node)
 | 
			
		||||
	case *FilterNode:
 | 
			
		||||
		return j.evalFilter(value, node)
 | 
			
		||||
	case *IntNode:
 | 
			
		||||
		return j.evalInt(value, node)
 | 
			
		||||
	case *FloatNode:
 | 
			
		||||
		return j.evalFloat(value, node)
 | 
			
		||||
	case *WildcardNode:
 | 
			
		||||
		return j.evalWildcard(value, node)
 | 
			
		||||
	case *RecursiveNode:
 | 
			
		||||
		return j.evalRecursive(value, node)
 | 
			
		||||
	case *UnionNode:
 | 
			
		||||
		return j.evalUnion(value, node)
 | 
			
		||||
	case *IdentifierNode:
 | 
			
		||||
		return j.evalIdentifier(value, node)
 | 
			
		||||
	default:
 | 
			
		||||
		return value, fmt.Errorf("unexpected Node %v", node)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalInt evaluates IntNode
 | 
			
		||||
func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) {
 | 
			
		||||
	result := make([]reflect.Value, len(input))
 | 
			
		||||
	for i := range input {
 | 
			
		||||
		result[i] = reflect.ValueOf(node.Value)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalFloat evaluates FloatNode
 | 
			
		||||
func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) {
 | 
			
		||||
	result := make([]reflect.Value, len(input))
 | 
			
		||||
	for i := range input {
 | 
			
		||||
		result[i] = reflect.ValueOf(node.Value)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalList evaluates ListNode
 | 
			
		||||
func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	curValue := value
 | 
			
		||||
	for _, node := range node.Nodes {
 | 
			
		||||
		curValue, err = j.walk(curValue, node)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return curValue, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return curValue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalIdentifier evaluates IdentifierNode
 | 
			
		||||
func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) {
 | 
			
		||||
	results := []reflect.Value{}
 | 
			
		||||
	switch node.Name {
 | 
			
		||||
	case "range":
 | 
			
		||||
		j.stack = append(j.stack, j.cur)
 | 
			
		||||
		j.beginRange += 1
 | 
			
		||||
		results = input
 | 
			
		||||
	case "end":
 | 
			
		||||
		if j.endRange < j.inRange { //inside a loop, break the current block
 | 
			
		||||
			j.endRange += 1
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		// the loop is about to end, pop value and continue the following execution
 | 
			
		||||
		if len(j.stack) > 0 {
 | 
			
		||||
			j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1]
 | 
			
		||||
		} else {
 | 
			
		||||
			return results, fmt.Errorf("not in range, nothing to end")
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return input, fmt.Errorf("unrecongnized identifier %v", node.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return results, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalArray evaluates ArrayNode
 | 
			
		||||
func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) {
 | 
			
		||||
	result := []reflect.Value{}
 | 
			
		||||
	for _, value := range input {
 | 
			
		||||
		if value.Kind() == reflect.Interface {
 | 
			
		||||
			value = reflect.ValueOf(value.Interface())
 | 
			
		||||
		}
 | 
			
		||||
		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
 | 
			
		||||
			return input, fmt.Errorf("%v is not array or slice", value)
 | 
			
		||||
		}
 | 
			
		||||
		params := node.Params
 | 
			
		||||
		if !params[0].Known {
 | 
			
		||||
			params[0].Value = 0
 | 
			
		||||
		}
 | 
			
		||||
		if params[0].Value < 0 {
 | 
			
		||||
			params[0].Value += value.Len()
 | 
			
		||||
		}
 | 
			
		||||
		if !params[1].Known {
 | 
			
		||||
			params[1].Value = value.Len()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if params[1].Value < 0 {
 | 
			
		||||
			params[1].Value += value.Len()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !params[2].Known {
 | 
			
		||||
			value = value.Slice(params[0].Value, params[1].Value)
 | 
			
		||||
		} else {
 | 
			
		||||
			value = value.Slice3(params[0].Value, params[1].Value, params[2].Value)
 | 
			
		||||
		}
 | 
			
		||||
		for i := 0; i < value.Len(); i++ {
 | 
			
		||||
			result = append(result, value.Index(i))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalUnion evaluates UnionNode
 | 
			
		||||
func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) {
 | 
			
		||||
	result := []reflect.Value{}
 | 
			
		||||
	for _, listNode := range node.Nodes {
 | 
			
		||||
		temp, err := j.evalList(input, listNode)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return input, err
 | 
			
		||||
		}
 | 
			
		||||
		result = append(result, temp...)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalField evaluates filed of struct or key of map.
 | 
			
		||||
func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
 | 
			
		||||
	results := []reflect.Value{}
 | 
			
		||||
	for _, value := range input {
 | 
			
		||||
		var result reflect.Value
 | 
			
		||||
		if value.Kind() == reflect.Interface {
 | 
			
		||||
			value = reflect.ValueOf(value.Interface())
 | 
			
		||||
		}
 | 
			
		||||
		if value.Kind() == reflect.Struct {
 | 
			
		||||
			result = value.FieldByName(node.Value)
 | 
			
		||||
		} else if value.Kind() == reflect.Map {
 | 
			
		||||
			result = value.MapIndex(reflect.ValueOf(node.Value))
 | 
			
		||||
		}
 | 
			
		||||
		if result.IsValid() {
 | 
			
		||||
			results = append(results, result)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		return results, fmt.Errorf("%s is not found", node.Value)
 | 
			
		||||
	}
 | 
			
		||||
	return results, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalWildcard extract all contents of the given value
 | 
			
		||||
func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) {
 | 
			
		||||
	results := []reflect.Value{}
 | 
			
		||||
	for _, value := range input {
 | 
			
		||||
		kind := value.Kind()
 | 
			
		||||
		if kind == reflect.Struct {
 | 
			
		||||
			for i := 0; i < value.NumField(); i++ {
 | 
			
		||||
				results = append(results, value.Field(i))
 | 
			
		||||
			}
 | 
			
		||||
		} else if kind == reflect.Map {
 | 
			
		||||
			for _, key := range value.MapKeys() {
 | 
			
		||||
				results = append(results, value.MapIndex(key))
 | 
			
		||||
			}
 | 
			
		||||
		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
 | 
			
		||||
			for i := 0; i < value.Len(); i++ {
 | 
			
		||||
				results = append(results, value.Index(i))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return results, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalRecursive visit the given value recursively and push all of them to result
 | 
			
		||||
func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) {
 | 
			
		||||
	result := []reflect.Value{}
 | 
			
		||||
	for _, value := range input {
 | 
			
		||||
		results := []reflect.Value{}
 | 
			
		||||
		kind := value.Kind()
 | 
			
		||||
		if kind == reflect.Struct {
 | 
			
		||||
			for i := 0; i < value.NumField(); i++ {
 | 
			
		||||
				results = append(results, value.Field(i))
 | 
			
		||||
			}
 | 
			
		||||
		} else if kind == reflect.Map {
 | 
			
		||||
			for _, key := range value.MapKeys() {
 | 
			
		||||
				results = append(results, value.MapIndex(key))
 | 
			
		||||
			}
 | 
			
		||||
		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
 | 
			
		||||
			for i := 0; i < value.Len(); i++ {
 | 
			
		||||
				results = append(results, value.Index(i))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(results) != 0 {
 | 
			
		||||
			result = append(result, value)
 | 
			
		||||
			output, err := j.evalRecursive(results, node)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return result, err
 | 
			
		||||
			}
 | 
			
		||||
			result = append(result, output...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalFilter filter array according to FilterNode
 | 
			
		||||
func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) {
 | 
			
		||||
	results := []reflect.Value{}
 | 
			
		||||
	for _, value := range input {
 | 
			
		||||
		if value.Kind() == reflect.Interface {
 | 
			
		||||
			value = reflect.ValueOf(value.Interface())
 | 
			
		||||
		}
 | 
			
		||||
		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
 | 
			
		||||
			return input, fmt.Errorf("%v is not array or slice", value)
 | 
			
		||||
		}
 | 
			
		||||
		for i := 0; i < value.Len(); i++ {
 | 
			
		||||
			temp := []reflect.Value{value.Index(i)}
 | 
			
		||||
			lefts, err := j.evalList(temp, node.Left)
 | 
			
		||||
 | 
			
		||||
			//case exists
 | 
			
		||||
			if node.Operator == "exists" {
 | 
			
		||||
				if len(lefts) > 0 {
 | 
			
		||||
					results = append(results, value.Index(i))
 | 
			
		||||
				}
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return input, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var left, right interface{}
 | 
			
		||||
			if len(lefts) != 1 {
 | 
			
		||||
				return input, fmt.Errorf("can only compare one element at a time")
 | 
			
		||||
			}
 | 
			
		||||
			left = lefts[0].Interface()
 | 
			
		||||
 | 
			
		||||
			rights, err := j.evalList(temp, node.Right)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return input, err
 | 
			
		||||
			}
 | 
			
		||||
			if len(rights) != 1 {
 | 
			
		||||
				return input, fmt.Errorf("can only compare one element at a time")
 | 
			
		||||
			}
 | 
			
		||||
			right = rights[0].Interface()
 | 
			
		||||
 | 
			
		||||
			pass := false
 | 
			
		||||
			switch node.Operator {
 | 
			
		||||
			case "<":
 | 
			
		||||
				pass, err = template.Less(left, right)
 | 
			
		||||
			case ">":
 | 
			
		||||
				pass, err = template.Greater(left, right)
 | 
			
		||||
			case "==":
 | 
			
		||||
				pass, err = template.Equal(left, right)
 | 
			
		||||
			case "!=":
 | 
			
		||||
				pass, err = template.NotEqual(left, right)
 | 
			
		||||
			case "<=":
 | 
			
		||||
				pass, err = template.LessEqual(left, right)
 | 
			
		||||
			case ">=":
 | 
			
		||||
				pass, err = template.GreaterEqual(left, right)
 | 
			
		||||
			default:
 | 
			
		||||
				return results, fmt.Errorf("unrecognized filter operator %s", node.Operator)
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return results, err
 | 
			
		||||
			}
 | 
			
		||||
			if pass {
 | 
			
		||||
				results = append(results, value.Index(i))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return results, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalToText translates reflect value to corresponding text
 | 
			
		||||
func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) {
 | 
			
		||||
	if v.Kind() == reflect.Interface {
 | 
			
		||||
		v = reflect.ValueOf(v.Interface())
 | 
			
		||||
	}
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	switch v.Kind() {
 | 
			
		||||
	case reflect.Invalid:
 | 
			
		||||
		//pass
 | 
			
		||||
	case reflect.Ptr:
 | 
			
		||||
		text, err := j.evalToText(reflect.Indirect(v))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		buffer.Write(text)
 | 
			
		||||
	case reflect.Bool:
 | 
			
		||||
		if variable := v.Bool(); variable {
 | 
			
		||||
			buffer.WriteString("True")
 | 
			
		||||
		} else {
 | 
			
		||||
			buffer.WriteString("False")
 | 
			
		||||
		}
 | 
			
		||||
	case reflect.Float32:
 | 
			
		||||
		buffer.WriteString(strconv.FormatFloat(v.Float(), 'f', -1, 32))
 | 
			
		||||
	case reflect.Float64:
 | 
			
		||||
		buffer.WriteString(strconv.FormatFloat(v.Float(), 'f', -1, 64))
 | 
			
		||||
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
		buffer.WriteString(strconv.FormatInt(v.Int(), 10))
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
		buffer.WriteString(v.String())
 | 
			
		||||
	case reflect.Array, reflect.Slice:
 | 
			
		||||
		buffer.WriteString("[")
 | 
			
		||||
		for i := 0; i < v.Len(); i++ {
 | 
			
		||||
			text, err := j.evalToText(v.Index(i))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			buffer.Write(text)
 | 
			
		||||
			if i != v.Len()-1 {
 | 
			
		||||
				buffer.WriteString(", ")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		buffer.WriteString("]")
 | 
			
		||||
	case reflect.Struct:
 | 
			
		||||
		buffer.WriteString("{")
 | 
			
		||||
		for i := 0; i < v.NumField(); i++ {
 | 
			
		||||
			text, err := j.evalToText(v.Field(i))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			pair := fmt.Sprintf("%s: %s", v.Type().Field(i).Name, text)
 | 
			
		||||
			buffer.WriteString(pair)
 | 
			
		||||
			if i != v.NumField()-1 {
 | 
			
		||||
				buffer.WriteString(", ")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		buffer.WriteString("}")
 | 
			
		||||
	case reflect.Map:
 | 
			
		||||
		buffer.WriteString("{")
 | 
			
		||||
		for i, key := range v.MapKeys() {
 | 
			
		||||
			text, err := j.evalToText(v.MapIndex(key))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			pair := fmt.Sprintf("%s: %s", key, text)
 | 
			
		||||
			buffer.WriteString(pair)
 | 
			
		||||
			if i != len(v.MapKeys())-1 {
 | 
			
		||||
				buffer.WriteString(", ")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		buffer.WriteString("}")
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("%v is not printable", v.Kind())
 | 
			
		||||
	}
 | 
			
		||||
	return buffer.Bytes(), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										214
									
								
								pkg/util/jsonpath/jsonpath_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								pkg/util/jsonpath/jsonpath_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package jsonpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type jsonpathTest struct {
 | 
			
		||||
	name     string
 | 
			
		||||
	template string
 | 
			
		||||
	input    interface{}
 | 
			
		||||
	expect   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testJSONPath(tests []jsonpathTest, t *testing.T) {
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		j := New(test.name)
 | 
			
		||||
		err := j.Parse(test.template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
 | 
			
		||||
		}
 | 
			
		||||
		buf := new(bytes.Buffer)
 | 
			
		||||
		err = j.Execute(buf, test.input)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("in %s, execute error %v", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		out := buf.String()
 | 
			
		||||
		if out != test.expect {
 | 
			
		||||
			t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testFailJSONPath(tests []jsonpathTest, t *testing.T) {
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		j := New(test.name)
 | 
			
		||||
		err := j.Parse(test.template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
 | 
			
		||||
		}
 | 
			
		||||
		buf := new(bytes.Buffer)
 | 
			
		||||
		err = j.Execute(buf, test.input)
 | 
			
		||||
		var out string
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			out = "nil"
 | 
			
		||||
		} else {
 | 
			
		||||
			out = err.Error()
 | 
			
		||||
		}
 | 
			
		||||
		if out != test.expect {
 | 
			
		||||
			t.Errorf("in %s, expect to get error %s, got %s", test.name, test.expect, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStructInput(t *testing.T) {
 | 
			
		||||
	type book struct {
 | 
			
		||||
		Category string
 | 
			
		||||
		Author   string
 | 
			
		||||
		Title    string
 | 
			
		||||
		Price    float32
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type bicycle struct {
 | 
			
		||||
		Color string
 | 
			
		||||
		Price float32
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type store struct {
 | 
			
		||||
		Book    []book
 | 
			
		||||
		Bicycle bicycle
 | 
			
		||||
		Name    string
 | 
			
		||||
		Labels  map[string]int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	storeData := store{
 | 
			
		||||
		Name: "jsonpath",
 | 
			
		||||
		Book: []book{
 | 
			
		||||
			{"reference", "Nigel Rees", "Sayings of the Centurey", 8.95},
 | 
			
		||||
			{"fiction", "Evelyn Waugh", "Sword of Honour", 12.99},
 | 
			
		||||
			{"fiction", "Herman Melville", "Moby Dick", 8.99},
 | 
			
		||||
		},
 | 
			
		||||
		Bicycle: bicycle{"red", 19.95},
 | 
			
		||||
		Labels: map[string]int{
 | 
			
		||||
			"engieer":  10,
 | 
			
		||||
			"web/html": 15,
 | 
			
		||||
			"k8s-app":  20,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	storeTests := []jsonpathTest{
 | 
			
		||||
		{"plain", "hello jsonpath", nil, "hello jsonpath"},
 | 
			
		||||
		{"recursive", "{..}", []int{1, 2, 3}, "[1, 2, 3]"},
 | 
			
		||||
		{"filter", "{[?(@<5)]}", []int{2, 6, 3, 7}, "2 3"},
 | 
			
		||||
		{"quote", `{"{"}`, nil, "{"},
 | 
			
		||||
		{"union", "{[1,3,4]}", []int{0, 1, 2, 3, 4}, "1 3 4"},
 | 
			
		||||
		{"array", "{[0:2]}", []string{"Monday", "Tudesday"}, "Monday Tudesday"},
 | 
			
		||||
		{"variable", "hello {.Name}", storeData, "hello jsonpath"},
 | 
			
		||||
		{"dict/", "{.Labels.web/html}", storeData, "15"},
 | 
			
		||||
		{"dict-", "{.Labels.k8s-app}", storeData, "20"},
 | 
			
		||||
		{"nest", "{.Bicycle.Color}", storeData, "red"},
 | 
			
		||||
		{"allarray", "{.Book[*].Author}", storeData, "Nigel Rees Evelyn Waugh Herman Melville"},
 | 
			
		||||
		{"allfileds", "{.Bicycle.*}", storeData, "red 19.95"},
 | 
			
		||||
		{"recurfileds", "{..Price}", storeData, "8.95 12.99 8.99 19.95"},
 | 
			
		||||
		{"lastarray", "{.Book[-1:]}", storeData,
 | 
			
		||||
			"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
 | 
			
		||||
		{"recurarray", "{..Book[2]}", storeData,
 | 
			
		||||
			"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
 | 
			
		||||
	}
 | 
			
		||||
	testJSONPath(storeTests, t)
 | 
			
		||||
 | 
			
		||||
	failStoreTests := []jsonpathTest{
 | 
			
		||||
		{"invalid identfier", "{hello}", storeData, "unrecongnized identifier hello"},
 | 
			
		||||
		{"nonexistent field", "{.hello}", storeData, "hello is not found"},
 | 
			
		||||
		{"invalid array", "{.Labels[0]}", storeData, "<map[string]int Value> is not array or slice"},
 | 
			
		||||
		{"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>"},
 | 
			
		||||
		{"redundent end", "{range .Labels.*}{@}{end}{end}", storeData, "not in range, nothing to end"},
 | 
			
		||||
	}
 | 
			
		||||
	testFailJSONPath(failStoreTests, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestJSONInput(t *testing.T) {
 | 
			
		||||
	var pointsJSON = []byte(`[
 | 
			
		||||
		{"id": "i1", "x":4, "y":-5},
 | 
			
		||||
		{"id": "i2", "x":-2, "y":-5, "z":1},
 | 
			
		||||
		{"id": "i3", "x":  8, "y":  3 },
 | 
			
		||||
		{"id": "i4", "x": -6, "y": -1 },
 | 
			
		||||
		{"id": "i5", "x":  0, "y":  2, "z": 1 },
 | 
			
		||||
		{"id": "i6", "x":  1, "y":  4 }
 | 
			
		||||
	]`)
 | 
			
		||||
	var pointsData interface{}
 | 
			
		||||
	err := json.Unmarshal(pointsJSON, &pointsData)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	pointsTests := []jsonpathTest{
 | 
			
		||||
		{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5"},
 | 
			
		||||
		{"bracket key", "{[0]['id']}", pointsData, "i1"},
 | 
			
		||||
	}
 | 
			
		||||
	testJSONPath(pointsTests, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestKubenates tests some use cases from kubenates
 | 
			
		||||
func TestKubenates(t *testing.T) {
 | 
			
		||||
	var input = []byte(`{
 | 
			
		||||
	  "kind": "List",
 | 
			
		||||
	  "items":[
 | 
			
		||||
		{
 | 
			
		||||
		  "kind":"None",
 | 
			
		||||
		  "metadata":{"name":"127.0.0.1"},
 | 
			
		||||
		  "status":{
 | 
			
		||||
			"capacity":{"cpu":"4"},
 | 
			
		||||
			"addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}]
 | 
			
		||||
		  }
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
		  "kind":"None",
 | 
			
		||||
		  "metadata":{"name":"127.0.0.2"},
 | 
			
		||||
		  "status":{
 | 
			
		||||
			"capacity":{"cpu":"8"},
 | 
			
		||||
			"addresses":[
 | 
			
		||||
			  {"type": "LegacyHostIP", "address":"127.0.0.2"},
 | 
			
		||||
			  {"type": "another", "address":"127.0.0.3"}
 | 
			
		||||
			]
 | 
			
		||||
		  }
 | 
			
		||||
		}
 | 
			
		||||
	  ],
 | 
			
		||||
	  "users":[
 | 
			
		||||
	    {
 | 
			
		||||
	      "name": "myself",
 | 
			
		||||
	      "user": {}
 | 
			
		||||
	    },
 | 
			
		||||
	    {
 | 
			
		||||
	      "name": "e2e",
 | 
			
		||||
	      "user": {"username": "admin", "password": "secret"}
 | 
			
		||||
	  	}
 | 
			
		||||
	  ]
 | 
			
		||||
	}`)
 | 
			
		||||
	var nodesData interface{}
 | 
			
		||||
	err := json.Unmarshal(input, &nodesData)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	nodesTests := []jsonpathTest{
 | 
			
		||||
		{"range item", "{range .items[*]}{.metadata.name}, {end}{.kind}", nodesData, `127.0.0.1, 127.0.0.2, List`},
 | 
			
		||||
		{"range addresss", "{.items[*].status.addresses[*].address}", nodesData,
 | 
			
		||||
			`127.0.0.1 127.0.0.2 127.0.0.3`},
 | 
			
		||||
		{"double range", "{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}", nodesData,
 | 
			
		||||
			`127.0.0.1, 127.0.0.2, 127.0.0.3, `},
 | 
			
		||||
		{"item name", "{.items[*].metadata.name}", nodesData, `127.0.0.1 127.0.0.2`},
 | 
			
		||||
		{"union nodes capacity", "{.items[*]['metadata.name', 'status.capacity']}", nodesData,
 | 
			
		||||
			`127.0.0.1 127.0.0.2 {cpu: 4} {cpu: 8}`},
 | 
			
		||||
		{"range nodes capacity", "{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}", nodesData,
 | 
			
		||||
			`[127.0.0.1, {cpu: 4}] [127.0.0.2, {cpu: 8}] `},
 | 
			
		||||
		{"user password", `{.users[?(@.name=="e2e")].user.password}`, nodesData, "secret"},
 | 
			
		||||
	}
 | 
			
		||||
	testJSONPath(nodesTests, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										239
									
								
								pkg/util/jsonpath/node.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								pkg/util/jsonpath/node.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package jsonpath
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// NodeType identifies the type of a parse tree node.
 | 
			
		||||
type NodeType int
 | 
			
		||||
 | 
			
		||||
// Type returns itself and provides an easy default implementation
 | 
			
		||||
func (t NodeType) Type() NodeType {
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t NodeType) String() string {
 | 
			
		||||
	return NodeTypeName[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	NodeText NodeType = iota
 | 
			
		||||
	NodeArray
 | 
			
		||||
	NodeList
 | 
			
		||||
	NodeField
 | 
			
		||||
	NodeIdentifier
 | 
			
		||||
	NodeFilter
 | 
			
		||||
	NodeInt
 | 
			
		||||
	NodeFloat
 | 
			
		||||
	NodeWildcard
 | 
			
		||||
	NodeRecursive
 | 
			
		||||
	NodeUnion
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var NodeTypeName = map[NodeType]string{
 | 
			
		||||
	NodeText:       "NodeText",
 | 
			
		||||
	NodeArray:      "NodeArray",
 | 
			
		||||
	NodeList:       "NodeList",
 | 
			
		||||
	NodeField:      "NodeField",
 | 
			
		||||
	NodeIdentifier: "NodeIdentifier",
 | 
			
		||||
	NodeFilter:     "NodeFilter",
 | 
			
		||||
	NodeInt:        "NodeInt",
 | 
			
		||||
	NodeFloat:      "NodeFloat",
 | 
			
		||||
	NodeWildcard:   "NodeWildcard",
 | 
			
		||||
	NodeRecursive:  "NodeRecursive",
 | 
			
		||||
	NodeUnion:      "NodeUnion",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Node interface {
 | 
			
		||||
	Type() NodeType
 | 
			
		||||
	String() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListNode holds a sequence of nodes.
 | 
			
		||||
type ListNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Nodes []Node // The element nodes in lexical order.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newList() *ListNode {
 | 
			
		||||
	return &ListNode{NodeType: NodeList}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *ListNode) append(n Node) {
 | 
			
		||||
	l.Nodes = append(l.Nodes, n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *ListNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s", l.Type())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TextNode holds plain text.
 | 
			
		||||
type TextNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Text []byte // The text; may span newlines.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newText(text string) *TextNode {
 | 
			
		||||
	return &TextNode{NodeType: NodeText, Text: []byte(text)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *TextNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s", t.Type(), t.Text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FieldNode holds filed of struct
 | 
			
		||||
type FieldNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Value string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newField(value string) *FieldNode {
 | 
			
		||||
	return &FieldNode{NodeType: NodeField, Value: value}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FieldNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s", f.Type(), f.Value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IdentifierNode holds an identifier
 | 
			
		||||
type IdentifierNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newIdentifier(value string) *IdentifierNode {
 | 
			
		||||
	return &IdentifierNode{
 | 
			
		||||
		NodeType: NodeIdentifier,
 | 
			
		||||
		Name:     value,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *IdentifierNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s", f.Type(), f.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParamsEntry holds param information for ArrayNode
 | 
			
		||||
type ParamsEntry struct {
 | 
			
		||||
	Value int
 | 
			
		||||
	Known bool //whether the value is known when parse it
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ArrayNode holds start, end, step information for array index selection
 | 
			
		||||
type ArrayNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Params [3]ParamsEntry //start, end, step
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newArray(params [3]ParamsEntry) *ArrayNode {
 | 
			
		||||
	return &ArrayNode{
 | 
			
		||||
		NodeType: NodeArray,
 | 
			
		||||
		Params:   params,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ArrayNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %v", a.Type(), a.Params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterNode holds operand and operator information for filter
 | 
			
		||||
type FilterNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Left     *ListNode
 | 
			
		||||
	Right    *ListNode
 | 
			
		||||
	Operator string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newFilter(left, right *ListNode, operator string) *FilterNode {
 | 
			
		||||
	return &FilterNode{
 | 
			
		||||
		NodeType: NodeFilter,
 | 
			
		||||
		Left:     left,
 | 
			
		||||
		Right:    right,
 | 
			
		||||
		Operator: operator,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FilterNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IntNode holds integer value
 | 
			
		||||
type IntNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Value int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newInt(num int) *IntNode {
 | 
			
		||||
	return &IntNode{NodeType: NodeInt, Value: num}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *IntNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %d", i.Type(), i.Value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FloatNode holds float value
 | 
			
		||||
type FloatNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Value float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newFloat(num float64) *FloatNode {
 | 
			
		||||
	return &FloatNode{NodeType: NodeFloat, Value: num}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *FloatNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s: %f", i.Type(), i.Value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WildcardNode means a wildcard
 | 
			
		||||
type WildcardNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newWildcard() *WildcardNode {
 | 
			
		||||
	return &WildcardNode{NodeType: NodeWildcard}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *WildcardNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s", i.Type())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RecursiveNode means a recursive descent operator
 | 
			
		||||
type RecursiveNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newRecursive() *RecursiveNode {
 | 
			
		||||
	return &RecursiveNode{NodeType: NodeRecursive}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RecursiveNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s", r.Type())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnionNode is union of ListNode
 | 
			
		||||
type UnionNode struct {
 | 
			
		||||
	NodeType
 | 
			
		||||
	Nodes []*ListNode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newUnion(nodes []*ListNode) *UnionNode {
 | 
			
		||||
	return &UnionNode{NodeType: NodeUnion, Nodes: nodes}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *UnionNode) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s", u.Type())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										419
									
								
								pkg/util/jsonpath/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								pkg/util/jsonpath/parser.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,419 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package jsonpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const eof = -1
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	leftDelim  = "{"
 | 
			
		||||
	rightDelim = "}"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Parser struct {
 | 
			
		||||
	Name  string
 | 
			
		||||
	Root  *ListNode
 | 
			
		||||
	input string
 | 
			
		||||
	cur   *ListNode
 | 
			
		||||
	pos   int
 | 
			
		||||
	start int
 | 
			
		||||
	width int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parsed the given text and return a node Parser.
 | 
			
		||||
// If an error is encountered, parsing stops and an empty
 | 
			
		||||
// Parser is returned with the error
 | 
			
		||||
func Parse(name, text string) (*Parser, error) {
 | 
			
		||||
	p := NewParser(name)
 | 
			
		||||
	err := p.Parse(text)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		p = nil
 | 
			
		||||
	}
 | 
			
		||||
	return p, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewParser(name string) *Parser {
 | 
			
		||||
	return &Parser{
 | 
			
		||||
		Name: name,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseAction parsed the expression inside delimiter
 | 
			
		||||
func parseAction(name, text string) (*Parser, error) {
 | 
			
		||||
	p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim))
 | 
			
		||||
	p.Root = p.Root.Nodes[0].(*ListNode)
 | 
			
		||||
	return p, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Parser) Parse(text string) error {
 | 
			
		||||
	p.input = text
 | 
			
		||||
	p.Root = newList()
 | 
			
		||||
	p.pos = 0
 | 
			
		||||
	return p.parseText(p.Root)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// consumeText return the parsed text since last cosumeText
 | 
			
		||||
func (p *Parser) consumeText() string {
 | 
			
		||||
	value := p.input[p.start:p.pos]
 | 
			
		||||
	p.start = p.pos
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// next returns the next rune in the input.
 | 
			
		||||
func (p *Parser) next() rune {
 | 
			
		||||
	if int(p.pos) >= len(p.input) {
 | 
			
		||||
		p.width = 0
 | 
			
		||||
		return eof
 | 
			
		||||
	}
 | 
			
		||||
	r, w := utf8.DecodeRuneInString(p.input[p.pos:])
 | 
			
		||||
	p.width = w
 | 
			
		||||
	p.pos += p.width
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// peek returns but does not consume the next rune in the input.
 | 
			
		||||
func (p *Parser) peek() rune {
 | 
			
		||||
	r := p.next()
 | 
			
		||||
	p.backup()
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// backup steps back one rune. Can only be called once per call of next.
 | 
			
		||||
func (p *Parser) backup() {
 | 
			
		||||
	p.pos -= p.width
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Parser) parseText(cur *ListNode) error {
 | 
			
		||||
	for {
 | 
			
		||||
		if strings.HasPrefix(p.input[p.pos:], leftDelim) {
 | 
			
		||||
			if p.pos > p.start {
 | 
			
		||||
				cur.append(newText(p.consumeText()))
 | 
			
		||||
			}
 | 
			
		||||
			return p.parseLeftDelim(cur)
 | 
			
		||||
		}
 | 
			
		||||
		if p.next() == eof {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Correctly reached EOF.
 | 
			
		||||
	if p.pos > p.start {
 | 
			
		||||
		cur.append(newText(p.consumeText()))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseLeftDelim scans the left delimiter, which is known to be present.
 | 
			
		||||
func (p *Parser) parseLeftDelim(cur *ListNode) error {
 | 
			
		||||
	p.pos += len(leftDelim)
 | 
			
		||||
	p.consumeText()
 | 
			
		||||
	newNode := newList()
 | 
			
		||||
	cur.append(newNode)
 | 
			
		||||
	cur = newNode
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Parser) parseInsideAction(cur *ListNode) error {
 | 
			
		||||
	prefixMap := map[string]func(*ListNode) error{
 | 
			
		||||
		rightDelim: p.parseRightDelim,
 | 
			
		||||
		"[?(":      p.parseFilter,
 | 
			
		||||
		"..":       p.parseRecursive,
 | 
			
		||||
	}
 | 
			
		||||
	for prefix, parseFunc := range prefixMap {
 | 
			
		||||
		if strings.HasPrefix(p.input[p.pos:], prefix) {
 | 
			
		||||
			return parseFunc(cur)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch r := p.next(); {
 | 
			
		||||
	case r == eof || isEndOfLine(r):
 | 
			
		||||
		return fmt.Errorf("unclosed action")
 | 
			
		||||
	case r == ' ':
 | 
			
		||||
		p.consumeText()
 | 
			
		||||
	case r == '@': //the current object, just pass it
 | 
			
		||||
		p.consumeText()
 | 
			
		||||
	case r == '[':
 | 
			
		||||
		return p.parseArray(cur)
 | 
			
		||||
	case r == '"':
 | 
			
		||||
		return p.parseQuote(cur)
 | 
			
		||||
	case r == '.':
 | 
			
		||||
		return p.parseField(cur)
 | 
			
		||||
	case r == '+' || r == '-' || unicode.IsDigit(r):
 | 
			
		||||
		p.backup()
 | 
			
		||||
		return p.parseNumber(cur)
 | 
			
		||||
	case isAlphaNumeric(r):
 | 
			
		||||
		p.backup()
 | 
			
		||||
		return p.parseIdentifier(cur)
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("unrecognized charactor in action: %#U", r)
 | 
			
		||||
	}
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseRightDelim scans the right delimiter, which is known to be present.
 | 
			
		||||
func (p *Parser) parseRightDelim(cur *ListNode) error {
 | 
			
		||||
	p.pos += len(rightDelim)
 | 
			
		||||
	p.consumeText()
 | 
			
		||||
	cur = p.Root
 | 
			
		||||
	return p.parseText(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseIdentifier scans build-in keywords, like "range" "end"
 | 
			
		||||
func (p *Parser) parseIdentifier(cur *ListNode) error {
 | 
			
		||||
	var r rune
 | 
			
		||||
	for {
 | 
			
		||||
		r = p.next()
 | 
			
		||||
		if isTerminator(r) {
 | 
			
		||||
			p.backup()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	value := p.consumeText()
 | 
			
		||||
	cur.append(newIdentifier(value))
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseRecursive scans the recursive desent operator ..
 | 
			
		||||
func (p *Parser) parseRecursive(cur *ListNode) error {
 | 
			
		||||
	p.pos += len("..")
 | 
			
		||||
	p.consumeText()
 | 
			
		||||
	cur.append(newRecursive())
 | 
			
		||||
	if r := p.peek(); isAlphaNumeric(r) {
 | 
			
		||||
		return p.parseField(cur)
 | 
			
		||||
	}
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseNumber scans number
 | 
			
		||||
func (p *Parser) parseNumber(cur *ListNode) error {
 | 
			
		||||
	r := p.peek()
 | 
			
		||||
	if r == '+' || r == '-' {
 | 
			
		||||
		r = p.next()
 | 
			
		||||
	}
 | 
			
		||||
	for {
 | 
			
		||||
		r = p.next()
 | 
			
		||||
		if r != '.' && !unicode.IsDigit(r) {
 | 
			
		||||
			p.backup()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	value := p.consumeText()
 | 
			
		||||
	i, err := strconv.Atoi(value)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		cur.append(newInt(i))
 | 
			
		||||
		return p.parseInsideAction(cur)
 | 
			
		||||
	}
 | 
			
		||||
	d, err := strconv.ParseFloat(value, 64)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		cur.append(newFloat(d))
 | 
			
		||||
		return p.parseInsideAction(cur)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("cannot parse number %s", value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseArray scans array index selection
 | 
			
		||||
func (p *Parser) parseArray(cur *ListNode) error {
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
		switch p.next() {
 | 
			
		||||
		case eof, '\n':
 | 
			
		||||
			return fmt.Errorf("unterminated array")
 | 
			
		||||
		case ']':
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	text := p.consumeText()
 | 
			
		||||
	text = string(text[1 : len(text)-1])
 | 
			
		||||
	if text == "*" {
 | 
			
		||||
		text = ":"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//union operator
 | 
			
		||||
	strs := strings.Split(text, ",")
 | 
			
		||||
	if len(strs) > 1 {
 | 
			
		||||
		union := []*ListNode{}
 | 
			
		||||
		for _, str := range strs {
 | 
			
		||||
			parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " ")))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			union = append(union, parser.Root)
 | 
			
		||||
		}
 | 
			
		||||
		cur.append(newUnion(union))
 | 
			
		||||
		return p.parseInsideAction(cur)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// dict key
 | 
			
		||||
	reg := regexp.MustCompile(`^'([^']*)'$`)
 | 
			
		||||
	value := reg.FindStringSubmatch(text)
 | 
			
		||||
	if value != nil {
 | 
			
		||||
		parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1]))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, node := range parser.Root.Nodes {
 | 
			
		||||
			cur.append(node)
 | 
			
		||||
		}
 | 
			
		||||
		return p.parseInsideAction(cur)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//slice operator
 | 
			
		||||
	reg = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:[\d]*)?$`)
 | 
			
		||||
	value = reg.FindStringSubmatch(text)
 | 
			
		||||
	if value == nil {
 | 
			
		||||
		return fmt.Errorf("invalid array index %s", text)
 | 
			
		||||
	}
 | 
			
		||||
	value = value[1:]
 | 
			
		||||
	params := [3]ParamsEntry{}
 | 
			
		||||
	for i := 0; i < 3; i++ {
 | 
			
		||||
		if value[i] != "" {
 | 
			
		||||
			if i > 0 {
 | 
			
		||||
				value[i] = value[i][1:]
 | 
			
		||||
			}
 | 
			
		||||
			if i > 0 && value[i] == "" {
 | 
			
		||||
				params[i].Known = false
 | 
			
		||||
			} else {
 | 
			
		||||
				var err error
 | 
			
		||||
				params[i].Known = true
 | 
			
		||||
				params[i].Value, err = strconv.Atoi(value[i])
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return fmt.Errorf("array index %s is not a number", params[i].Value)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if i == 1 {
 | 
			
		||||
				params[i].Known = true
 | 
			
		||||
				params[i].Value = params[0].Value + 1
 | 
			
		||||
			} else {
 | 
			
		||||
				params[i].Known = false
 | 
			
		||||
				params[i].Value = 0
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	cur.append(newArray(params))
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseFilter scans filter inside array selection
 | 
			
		||||
func (p *Parser) parseFilter(cur *ListNode) error {
 | 
			
		||||
	p.pos += len("[?(")
 | 
			
		||||
	p.consumeText()
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
		switch p.next() {
 | 
			
		||||
		case eof, '\n':
 | 
			
		||||
			return fmt.Errorf("unterminated filter")
 | 
			
		||||
		case ')':
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.next() != ']' {
 | 
			
		||||
		return fmt.Errorf("unclosed array expect ]")
 | 
			
		||||
	}
 | 
			
		||||
	reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`)
 | 
			
		||||
	text := p.consumeText()
 | 
			
		||||
	text = string(text[:len(text)-2])
 | 
			
		||||
	value := reg.FindStringSubmatch(text)
 | 
			
		||||
	if value == nil {
 | 
			
		||||
		parser, err := parseAction("text", text)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		cur.append(newFilter(parser.Root, newList(), "exists"))
 | 
			
		||||
	} else {
 | 
			
		||||
		leftParser, err := parseAction("left", value[1])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		rightParser, err := parseAction("right", value[3])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		cur.append(newFilter(leftParser.Root, rightParser.Root, value[2]))
 | 
			
		||||
	}
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseQuote scans array index selection
 | 
			
		||||
func (p *Parser) parseQuote(cur *ListNode) error {
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
		switch p.next() {
 | 
			
		||||
		case eof, '\n':
 | 
			
		||||
			return fmt.Errorf("unterminated quoted string")
 | 
			
		||||
		case '"':
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	value := p.consumeText()
 | 
			
		||||
	cur.append(newText(value[1 : len(value)-1]))
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseField scans a field until a terminator
 | 
			
		||||
func (p *Parser) parseField(cur *ListNode) error {
 | 
			
		||||
	p.consumeText()
 | 
			
		||||
	var r rune
 | 
			
		||||
	for {
 | 
			
		||||
		r = p.next()
 | 
			
		||||
		if isTerminator(r) {
 | 
			
		||||
			p.backup()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	value := p.consumeText()
 | 
			
		||||
	if value == "*" {
 | 
			
		||||
		cur.append(newWildcard())
 | 
			
		||||
	} else {
 | 
			
		||||
		cur.append(newField(value))
 | 
			
		||||
	}
 | 
			
		||||
	return p.parseInsideAction(cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isTerminator reports whether the input is at valid termination character to appear after an identifier.
 | 
			
		||||
func isTerminator(r rune) bool {
 | 
			
		||||
	if isSpace(r) || isEndOfLine(r) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	switch r {
 | 
			
		||||
	case eof, '.', ',', '[', ']', '$', '@', '{', '}':
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isSpace reports whether r is a space character.
 | 
			
		||||
func isSpace(r rune) bool {
 | 
			
		||||
	return r == ' ' || r == '\t'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isEndOfLine reports whether r is an end-of-line character.
 | 
			
		||||
func isEndOfLine(r rune) bool {
 | 
			
		||||
	return r == '\r' || r == '\n'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
 | 
			
		||||
func isAlphaNumeric(r rune) bool {
 | 
			
		||||
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								pkg/util/jsonpath/parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								pkg/util/jsonpath/parser_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package jsonpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type parserTest struct {
 | 
			
		||||
	name  string
 | 
			
		||||
	text  string
 | 
			
		||||
	nodes []Node
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var parserTests = []parserTest{
 | 
			
		||||
	{"plain", `hello jsonpath`, []Node{newText("hello jsonpath")}},
 | 
			
		||||
	{"variable", `hello {.jsonpath}`,
 | 
			
		||||
		[]Node{newText("hello "), newList(), newField("jsonpath")}},
 | 
			
		||||
	{"arrayfiled", `hello {['jsonpath']}`,
 | 
			
		||||
		[]Node{newText("hello "), newList(), newField("jsonpath")}},
 | 
			
		||||
	{"quote", `{"{"}`, []Node{newList(), newText("{")}},
 | 
			
		||||
	{"array", `{[1:3]}`, []Node{newList(),
 | 
			
		||||
		newArray([3]ParamsEntry{{1, true}, {3, true}, {0, false}})}},
 | 
			
		||||
	{"allarray", `{.book[*].author}`,
 | 
			
		||||
		[]Node{newList(), newField("book"),
 | 
			
		||||
			newArray([3]ParamsEntry{{0, false}, {0, false}, {0, false}}), newField("author")}},
 | 
			
		||||
	{"wildcard", `{.bicycle.*}`,
 | 
			
		||||
		[]Node{newList(), newField("bicycle"), newWildcard()}},
 | 
			
		||||
	{"filter", `{[?(@.price<3)]}`,
 | 
			
		||||
		[]Node{newList(), newFilter(newList(), newList(), "<"),
 | 
			
		||||
			newList(), newField("price"), newList(), newInt(3)}},
 | 
			
		||||
	{"recursive", `{..}`, []Node{newList(), newRecursive()}},
 | 
			
		||||
	{"recurField", `{..price}`,
 | 
			
		||||
		[]Node{newList(), newRecursive(), newField("price")}},
 | 
			
		||||
	{"arraydict", `{['book.price']}`, []Node{newList(),
 | 
			
		||||
		newField("book"), newField("price"),
 | 
			
		||||
	}},
 | 
			
		||||
	{"union", `{['bicycle.price', 3, 'book.price']}`, []Node{newList(), newUnion([]*ListNode{}),
 | 
			
		||||
		newList(), newField("bicycle"), newField("price"),
 | 
			
		||||
		newList(), newArray([3]ParamsEntry{{3, true}, {4, true}, {0, false}}),
 | 
			
		||||
		newList(), newField("book"), newField("price"),
 | 
			
		||||
	}},
 | 
			
		||||
	{"range", `{range .items}{.name},{end}`, []Node{
 | 
			
		||||
		newList(), newIdentifier("range"), newField("items"),
 | 
			
		||||
		newList(), newField("name"), newText(","),
 | 
			
		||||
		newList(), newIdentifier("end"),
 | 
			
		||||
	}},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func collectNode(nodes []Node, cur Node) []Node {
 | 
			
		||||
	nodes = append(nodes, cur)
 | 
			
		||||
	switch cur.Type() {
 | 
			
		||||
	case NodeList:
 | 
			
		||||
		for _, node := range cur.(*ListNode).Nodes {
 | 
			
		||||
			nodes = collectNode(nodes, node)
 | 
			
		||||
		}
 | 
			
		||||
	case NodeFilter:
 | 
			
		||||
		nodes = collectNode(nodes, cur.(*FilterNode).Left)
 | 
			
		||||
		nodes = collectNode(nodes, cur.(*FilterNode).Right)
 | 
			
		||||
	case NodeUnion:
 | 
			
		||||
		for _, node := range cur.(*UnionNode).Nodes {
 | 
			
		||||
			nodes = collectNode(nodes, node)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nodes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParser(t *testing.T) {
 | 
			
		||||
	for _, test := range parserTests {
 | 
			
		||||
		parser, err := Parse(test.name, test.text)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("parse %s error %v", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		result := collectNode([]Node{}, parser.Root)[1:]
 | 
			
		||||
		if len(result) != len(test.nodes) {
 | 
			
		||||
			t.Errorf("in %s, expect to get %d nodes, got %d nodes", test.name, len(test.nodes), len(result))
 | 
			
		||||
			t.Error(result)
 | 
			
		||||
		}
 | 
			
		||||
		for i, expect := range test.nodes {
 | 
			
		||||
			if result[i].String() != expect.String() {
 | 
			
		||||
				t.Errorf("in %s, %dth node, expect %v, got %v", test.name, i, expect, result[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type failParserTest struct {
 | 
			
		||||
	name string
 | 
			
		||||
	text string
 | 
			
		||||
	err  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFailParser(t *testing.T) {
 | 
			
		||||
	failParserTests := []failParserTest{
 | 
			
		||||
		{"unclosed action", "{.hello", "unclosed action"},
 | 
			
		||||
		{"unrecognized charactor", "{*}", "unrecognized charactor in action: U+002A '*'"},
 | 
			
		||||
		{"invalid number", "{+12.3.0}", "cannot parse number +12.3.0"},
 | 
			
		||||
		{"unterminated array", "{[1}", "unterminated array"},
 | 
			
		||||
		{"invalid index", "{[::-1]}", "invalid array index ::-1"},
 | 
			
		||||
		{"unterminated filter", "{[?(.price]}", "unterminated filter"},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range failParserTests {
 | 
			
		||||
		_, err := Parse(test.name, test.text)
 | 
			
		||||
		var out string
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			out = "nil"
 | 
			
		||||
		} else {
 | 
			
		||||
			out = err.Error()
 | 
			
		||||
		}
 | 
			
		||||
		if out != test.err {
 | 
			
		||||
			t.Errorf("in %s, expect to get error %v, got %v", test.name, test.err, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								third_party/golang/template/exec.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								third_party/golang/template/exec.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
package template
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errorType       = reflect.TypeOf((*error)(nil)).Elem()
 | 
			
		||||
	fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
 | 
			
		||||
// We indirect through pointers and empty interfaces (only) because
 | 
			
		||||
// non-empty interfaces have methods we might need.
 | 
			
		||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
 | 
			
		||||
	for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
 | 
			
		||||
		if v.IsNil() {
 | 
			
		||||
			return v, true
 | 
			
		||||
		}
 | 
			
		||||
		if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// printableValue returns the, possibly indirected, interface value inside v that
 | 
			
		||||
// is best for a call to formatted printer.
 | 
			
		||||
func printableValue(v reflect.Value) (interface{}, bool) {
 | 
			
		||||
	if v.Kind() == reflect.Ptr {
 | 
			
		||||
		v, _ = indirect(v) // fmt.Fprint handles nil.
 | 
			
		||||
	}
 | 
			
		||||
	if !v.IsValid() {
 | 
			
		||||
		return "<no value>", true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
 | 
			
		||||
		if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
 | 
			
		||||
			v = v.Addr()
 | 
			
		||||
		} else {
 | 
			
		||||
			switch v.Kind() {
 | 
			
		||||
			case reflect.Chan, reflect.Func:
 | 
			
		||||
				return nil, false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v.Interface(), true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
 | 
			
		||||
func canBeNil(typ reflect.Type) bool {
 | 
			
		||||
	switch typ.Kind() {
 | 
			
		||||
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
 | 
			
		||||
// and whether the value has a meaningful truth value.
 | 
			
		||||
func isTrue(val reflect.Value) (truth, ok bool) {
 | 
			
		||||
	if !val.IsValid() {
 | 
			
		||||
		// Something like var x interface{}, never set. It's a form of nil.
 | 
			
		||||
		return false, true
 | 
			
		||||
	}
 | 
			
		||||
	switch val.Kind() {
 | 
			
		||||
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
 | 
			
		||||
		truth = val.Len() > 0
 | 
			
		||||
	case reflect.Bool:
 | 
			
		||||
		truth = val.Bool()
 | 
			
		||||
	case reflect.Complex64, reflect.Complex128:
 | 
			
		||||
		truth = val.Complex() != 0
 | 
			
		||||
	case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
 | 
			
		||||
		truth = !val.IsNil()
 | 
			
		||||
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
		truth = val.Int() != 0
 | 
			
		||||
	case reflect.Float32, reflect.Float64:
 | 
			
		||||
		truth = val.Float() != 0
 | 
			
		||||
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
			
		||||
		truth = val.Uint() != 0
 | 
			
		||||
	case reflect.Struct:
 | 
			
		||||
		truth = true // Struct values are always true.
 | 
			
		||||
	default:
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return truth, true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										599
									
								
								third_party/golang/template/funcs.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										599
									
								
								third_party/golang/template/funcs.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,599 @@
 | 
			
		||||
//This package is copied from Go library text/template.
 | 
			
		||||
//The original private functions eq, ge, gt, le, lt, and ne
 | 
			
		||||
//are exported as public functions.
 | 
			
		||||
package template
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Equal = eq
 | 
			
		||||
var GreaterEqual = ge
 | 
			
		||||
var Greater = gt
 | 
			
		||||
var LessEqual = le
 | 
			
		||||
var Less = lt
 | 
			
		||||
var NotEqual = ne
 | 
			
		||||
 | 
			
		||||
// FuncMap is the type of the map defining the mapping from names to functions.
 | 
			
		||||
// Each function must have either a single return value, or two return values of
 | 
			
		||||
// which the second has type error. In that case, if the second (error)
 | 
			
		||||
// return value evaluates to non-nil during execution, execution terminates and
 | 
			
		||||
// Execute returns that error.
 | 
			
		||||
type FuncMap map[string]interface{}
 | 
			
		||||
 | 
			
		||||
var builtins = FuncMap{
 | 
			
		||||
	"and":      and,
 | 
			
		||||
	"call":     call,
 | 
			
		||||
	"html":     HTMLEscaper,
 | 
			
		||||
	"index":    index,
 | 
			
		||||
	"js":       JSEscaper,
 | 
			
		||||
	"len":      length,
 | 
			
		||||
	"not":      not,
 | 
			
		||||
	"or":       or,
 | 
			
		||||
	"print":    fmt.Sprint,
 | 
			
		||||
	"printf":   fmt.Sprintf,
 | 
			
		||||
	"println":  fmt.Sprintln,
 | 
			
		||||
	"urlquery": URLQueryEscaper,
 | 
			
		||||
 | 
			
		||||
	// Comparisons
 | 
			
		||||
	"eq": eq, // ==
 | 
			
		||||
	"ge": ge, // >=
 | 
			
		||||
	"gt": gt, // >
 | 
			
		||||
	"le": le, // <=
 | 
			
		||||
	"lt": lt, // <
 | 
			
		||||
	"ne": ne, // !=
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var builtinFuncs = createValueFuncs(builtins)
 | 
			
		||||
 | 
			
		||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
 | 
			
		||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
 | 
			
		||||
	m := make(map[string]reflect.Value)
 | 
			
		||||
	addValueFuncs(m, funcMap)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
 | 
			
		||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
 | 
			
		||||
	for name, fn := range in {
 | 
			
		||||
		v := reflect.ValueOf(fn)
 | 
			
		||||
		if v.Kind() != reflect.Func {
 | 
			
		||||
			panic("value for " + name + " not a function")
 | 
			
		||||
		}
 | 
			
		||||
		if !goodFunc(v.Type()) {
 | 
			
		||||
			panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
 | 
			
		||||
		}
 | 
			
		||||
		out[name] = v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddFuncs adds to values the functions in funcs. It does no checking of the input -
 | 
			
		||||
// call addValueFuncs first.
 | 
			
		||||
func addFuncs(out, in FuncMap) {
 | 
			
		||||
	for name, fn := range in {
 | 
			
		||||
		out[name] = fn
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// goodFunc checks that the function or method has the right result signature.
 | 
			
		||||
func goodFunc(typ reflect.Type) bool {
 | 
			
		||||
	// We allow functions with 1 result or 2 results where the second is an error.
 | 
			
		||||
	switch {
 | 
			
		||||
	case typ.NumOut() == 1:
 | 
			
		||||
		return true
 | 
			
		||||
	case typ.NumOut() == 2 && typ.Out(1) == errorType:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// findFunction looks for a function in the template, and global map.
 | 
			
		||||
func findFunction(name string) (reflect.Value, bool) {
 | 
			
		||||
	if fn := builtinFuncs[name]; fn.IsValid() {
 | 
			
		||||
		return fn, true
 | 
			
		||||
	}
 | 
			
		||||
	return reflect.Value{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Indexing.
 | 
			
		||||
 | 
			
		||||
// index returns the result of indexing its first argument by the following
 | 
			
		||||
// arguments.  Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
 | 
			
		||||
// indexed item must be a map, slice, or array.
 | 
			
		||||
func index(item interface{}, indices ...interface{}) (interface{}, error) {
 | 
			
		||||
	v := reflect.ValueOf(item)
 | 
			
		||||
	for _, i := range indices {
 | 
			
		||||
		index := reflect.ValueOf(i)
 | 
			
		||||
		var isNil bool
 | 
			
		||||
		if v, isNil = indirect(v); isNil {
 | 
			
		||||
			return nil, fmt.Errorf("index of nil pointer")
 | 
			
		||||
		}
 | 
			
		||||
		switch v.Kind() {
 | 
			
		||||
		case reflect.Array, reflect.Slice, reflect.String:
 | 
			
		||||
			var x int64
 | 
			
		||||
			switch index.Kind() {
 | 
			
		||||
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
				x = index.Int()
 | 
			
		||||
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
			
		||||
				x = int64(index.Uint())
 | 
			
		||||
			default:
 | 
			
		||||
				return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
 | 
			
		||||
			}
 | 
			
		||||
			if x < 0 || x >= int64(v.Len()) {
 | 
			
		||||
				return nil, fmt.Errorf("index out of range: %d", x)
 | 
			
		||||
			}
 | 
			
		||||
			v = v.Index(int(x))
 | 
			
		||||
		case reflect.Map:
 | 
			
		||||
			if !index.IsValid() {
 | 
			
		||||
				index = reflect.Zero(v.Type().Key())
 | 
			
		||||
			}
 | 
			
		||||
			if !index.Type().AssignableTo(v.Type().Key()) {
 | 
			
		||||
				return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
 | 
			
		||||
			}
 | 
			
		||||
			if x := v.MapIndex(index); x.IsValid() {
 | 
			
		||||
				v = x
 | 
			
		||||
			} else {
 | 
			
		||||
				v = reflect.Zero(v.Type().Elem())
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("can't index item of type %s", v.Type())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v.Interface(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Length
 | 
			
		||||
 | 
			
		||||
// length returns the length of the item, with an error if it has no defined length.
 | 
			
		||||
func length(item interface{}) (int, error) {
 | 
			
		||||
	v, isNil := indirect(reflect.ValueOf(item))
 | 
			
		||||
	if isNil {
 | 
			
		||||
		return 0, fmt.Errorf("len of nil pointer")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Kind() {
 | 
			
		||||
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
 | 
			
		||||
		return v.Len(), nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, fmt.Errorf("len of type %s", v.Type())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Function invocation
 | 
			
		||||
 | 
			
		||||
// call returns the result of evaluating the first argument as a function.
 | 
			
		||||
// The function must return 1 result, or 2 results, the second of which is an error.
 | 
			
		||||
func call(fn interface{}, args ...interface{}) (interface{}, error) {
 | 
			
		||||
	v := reflect.ValueOf(fn)
 | 
			
		||||
	typ := v.Type()
 | 
			
		||||
	if typ.Kind() != reflect.Func {
 | 
			
		||||
		return nil, fmt.Errorf("non-function of type %s", typ)
 | 
			
		||||
	}
 | 
			
		||||
	if !goodFunc(typ) {
 | 
			
		||||
		return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
 | 
			
		||||
	}
 | 
			
		||||
	numIn := typ.NumIn()
 | 
			
		||||
	var dddType reflect.Type
 | 
			
		||||
	if typ.IsVariadic() {
 | 
			
		||||
		if len(args) < numIn-1 {
 | 
			
		||||
			return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
 | 
			
		||||
		}
 | 
			
		||||
		dddType = typ.In(numIn - 1).Elem()
 | 
			
		||||
	} else {
 | 
			
		||||
		if len(args) != numIn {
 | 
			
		||||
			return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	argv := make([]reflect.Value, len(args))
 | 
			
		||||
	for i, arg := range args {
 | 
			
		||||
		value := reflect.ValueOf(arg)
 | 
			
		||||
		// Compute the expected type. Clumsy because of variadics.
 | 
			
		||||
		var argType reflect.Type
 | 
			
		||||
		if !typ.IsVariadic() || i < numIn-1 {
 | 
			
		||||
			argType = typ.In(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			argType = dddType
 | 
			
		||||
		}
 | 
			
		||||
		if !value.IsValid() && canBeNil(argType) {
 | 
			
		||||
			value = reflect.Zero(argType)
 | 
			
		||||
		}
 | 
			
		||||
		if !value.Type().AssignableTo(argType) {
 | 
			
		||||
			return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
 | 
			
		||||
		}
 | 
			
		||||
		argv[i] = value
 | 
			
		||||
	}
 | 
			
		||||
	result := v.Call(argv)
 | 
			
		||||
	if len(result) == 2 && !result[1].IsNil() {
 | 
			
		||||
		return result[0].Interface(), result[1].Interface().(error)
 | 
			
		||||
	}
 | 
			
		||||
	return result[0].Interface(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Boolean logic.
 | 
			
		||||
 | 
			
		||||
func truth(a interface{}) bool {
 | 
			
		||||
	t, _ := isTrue(reflect.ValueOf(a))
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// and computes the Boolean AND of its arguments, returning
 | 
			
		||||
// the first false argument it encounters, or the last argument.
 | 
			
		||||
func and(arg0 interface{}, args ...interface{}) interface{} {
 | 
			
		||||
	if !truth(arg0) {
 | 
			
		||||
		return arg0
 | 
			
		||||
	}
 | 
			
		||||
	for i := range args {
 | 
			
		||||
		arg0 = args[i]
 | 
			
		||||
		if !truth(arg0) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return arg0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// or computes the Boolean OR of its arguments, returning
 | 
			
		||||
// the first true argument it encounters, or the last argument.
 | 
			
		||||
func or(arg0 interface{}, args ...interface{}) interface{} {
 | 
			
		||||
	if truth(arg0) {
 | 
			
		||||
		return arg0
 | 
			
		||||
	}
 | 
			
		||||
	for i := range args {
 | 
			
		||||
		arg0 = args[i]
 | 
			
		||||
		if truth(arg0) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return arg0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// not returns the Boolean negation of its argument.
 | 
			
		||||
func not(arg interface{}) (truth bool) {
 | 
			
		||||
	truth, _ = isTrue(reflect.ValueOf(arg))
 | 
			
		||||
	return !truth
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Comparison.
 | 
			
		||||
 | 
			
		||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errBadComparisonType = errors.New("invalid type for comparison")
 | 
			
		||||
	errBadComparison     = errors.New("incompatible types for comparison")
 | 
			
		||||
	errNoComparison      = errors.New("missing argument for comparison")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type kind int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	invalidKind kind = iota
 | 
			
		||||
	boolKind
 | 
			
		||||
	complexKind
 | 
			
		||||
	intKind
 | 
			
		||||
	floatKind
 | 
			
		||||
	integerKind
 | 
			
		||||
	stringKind
 | 
			
		||||
	uintKind
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func basicKind(v reflect.Value) (kind, error) {
 | 
			
		||||
	switch v.Kind() {
 | 
			
		||||
	case reflect.Bool:
 | 
			
		||||
		return boolKind, nil
 | 
			
		||||
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
		return intKind, nil
 | 
			
		||||
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
			
		||||
		return uintKind, nil
 | 
			
		||||
	case reflect.Float32, reflect.Float64:
 | 
			
		||||
		return floatKind, nil
 | 
			
		||||
	case reflect.Complex64, reflect.Complex128:
 | 
			
		||||
		return complexKind, nil
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
		return stringKind, nil
 | 
			
		||||
	}
 | 
			
		||||
	return invalidKind, errBadComparisonType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// eq evaluates the comparison a == b || a == c || ...
 | 
			
		||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
 | 
			
		||||
	v1 := reflect.ValueOf(arg1)
 | 
			
		||||
	k1, err := basicKind(v1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(arg2) == 0 {
 | 
			
		||||
		return false, errNoComparison
 | 
			
		||||
	}
 | 
			
		||||
	for _, arg := range arg2 {
 | 
			
		||||
		v2 := reflect.ValueOf(arg)
 | 
			
		||||
		k2, err := basicKind(v2)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		truth := false
 | 
			
		||||
		if k1 != k2 {
 | 
			
		||||
			// Special case: Can compare integer values regardless of type's sign.
 | 
			
		||||
			switch {
 | 
			
		||||
			case k1 == intKind && k2 == uintKind:
 | 
			
		||||
				truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
 | 
			
		||||
			case k1 == uintKind && k2 == intKind:
 | 
			
		||||
				truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
 | 
			
		||||
			default:
 | 
			
		||||
				return false, errBadComparison
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			switch k1 {
 | 
			
		||||
			case boolKind:
 | 
			
		||||
				truth = v1.Bool() == v2.Bool()
 | 
			
		||||
			case complexKind:
 | 
			
		||||
				truth = v1.Complex() == v2.Complex()
 | 
			
		||||
			case floatKind:
 | 
			
		||||
				truth = v1.Float() == v2.Float()
 | 
			
		||||
			case intKind:
 | 
			
		||||
				truth = v1.Int() == v2.Int()
 | 
			
		||||
			case stringKind:
 | 
			
		||||
				truth = v1.String() == v2.String()
 | 
			
		||||
			case uintKind:
 | 
			
		||||
				truth = v1.Uint() == v2.Uint()
 | 
			
		||||
			default:
 | 
			
		||||
				panic("invalid kind")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if truth {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ne evaluates the comparison a != b.
 | 
			
		||||
func ne(arg1, arg2 interface{}) (bool, error) {
 | 
			
		||||
	// != is the inverse of ==.
 | 
			
		||||
	equal, err := eq(arg1, arg2)
 | 
			
		||||
	return !equal, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lt evaluates the comparison a < b.
 | 
			
		||||
func lt(arg1, arg2 interface{}) (bool, error) {
 | 
			
		||||
	v1 := reflect.ValueOf(arg1)
 | 
			
		||||
	k1, err := basicKind(v1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	v2 := reflect.ValueOf(arg2)
 | 
			
		||||
	k2, err := basicKind(v2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	truth := false
 | 
			
		||||
	if k1 != k2 {
 | 
			
		||||
		// Special case: Can compare integer values regardless of type's sign.
 | 
			
		||||
		switch {
 | 
			
		||||
		case k1 == intKind && k2 == uintKind:
 | 
			
		||||
			truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
 | 
			
		||||
		case k1 == uintKind && k2 == intKind:
 | 
			
		||||
			truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
 | 
			
		||||
		default:
 | 
			
		||||
			return false, errBadComparison
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		switch k1 {
 | 
			
		||||
		case boolKind, complexKind:
 | 
			
		||||
			return false, errBadComparisonType
 | 
			
		||||
		case floatKind:
 | 
			
		||||
			truth = v1.Float() < v2.Float()
 | 
			
		||||
		case intKind:
 | 
			
		||||
			truth = v1.Int() < v2.Int()
 | 
			
		||||
		case stringKind:
 | 
			
		||||
			truth = v1.String() < v2.String()
 | 
			
		||||
		case uintKind:
 | 
			
		||||
			truth = v1.Uint() < v2.Uint()
 | 
			
		||||
		default:
 | 
			
		||||
			panic("invalid kind")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return truth, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// le evaluates the comparison <= b.
 | 
			
		||||
func le(arg1, arg2 interface{}) (bool, error) {
 | 
			
		||||
	// <= is < or ==.
 | 
			
		||||
	lessThan, err := lt(arg1, arg2)
 | 
			
		||||
	if lessThan || err != nil {
 | 
			
		||||
		return lessThan, err
 | 
			
		||||
	}
 | 
			
		||||
	return eq(arg1, arg2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// gt evaluates the comparison a > b.
 | 
			
		||||
func gt(arg1, arg2 interface{}) (bool, error) {
 | 
			
		||||
	// > is the inverse of <=.
 | 
			
		||||
	lessOrEqual, err := le(arg1, arg2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return !lessOrEqual, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ge evaluates the comparison a >= b.
 | 
			
		||||
func ge(arg1, arg2 interface{}) (bool, error) {
 | 
			
		||||
	// >= is the inverse of <.
 | 
			
		||||
	lessThan, err := lt(arg1, arg2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return !lessThan, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTML escaping.
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	htmlQuot = []byte(""") // shorter than """
 | 
			
		||||
	htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
 | 
			
		||||
	htmlAmp  = []byte("&")
 | 
			
		||||
	htmlLt   = []byte("<")
 | 
			
		||||
	htmlGt   = []byte(">")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
 | 
			
		||||
func HTMLEscape(w io.Writer, b []byte) {
 | 
			
		||||
	last := 0
 | 
			
		||||
	for i, c := range b {
 | 
			
		||||
		var html []byte
 | 
			
		||||
		switch c {
 | 
			
		||||
		case '"':
 | 
			
		||||
			html = htmlQuot
 | 
			
		||||
		case '\'':
 | 
			
		||||
			html = htmlApos
 | 
			
		||||
		case '&':
 | 
			
		||||
			html = htmlAmp
 | 
			
		||||
		case '<':
 | 
			
		||||
			html = htmlLt
 | 
			
		||||
		case '>':
 | 
			
		||||
			html = htmlGt
 | 
			
		||||
		default:
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		w.Write(b[last:i])
 | 
			
		||||
		w.Write(html)
 | 
			
		||||
		last = i + 1
 | 
			
		||||
	}
 | 
			
		||||
	w.Write(b[last:])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
 | 
			
		||||
func HTMLEscapeString(s string) string {
 | 
			
		||||
	// Avoid allocation if we can.
 | 
			
		||||
	if strings.IndexAny(s, `'"&<>`) < 0 {
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	HTMLEscape(&b, []byte(s))
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
 | 
			
		||||
// representation of its arguments.
 | 
			
		||||
func HTMLEscaper(args ...interface{}) string {
 | 
			
		||||
	return HTMLEscapeString(evalArgs(args))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JavaScript escaping.
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	jsLowUni = []byte(`\u00`)
 | 
			
		||||
	hex      = []byte("0123456789ABCDEF")
 | 
			
		||||
 | 
			
		||||
	jsBackslash = []byte(`\\`)
 | 
			
		||||
	jsApos      = []byte(`\'`)
 | 
			
		||||
	jsQuot      = []byte(`\"`)
 | 
			
		||||
	jsLt        = []byte(`\x3C`)
 | 
			
		||||
	jsGt        = []byte(`\x3E`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
 | 
			
		||||
func JSEscape(w io.Writer, b []byte) {
 | 
			
		||||
	last := 0
 | 
			
		||||
	for i := 0; i < len(b); i++ {
 | 
			
		||||
		c := b[i]
 | 
			
		||||
 | 
			
		||||
		if !jsIsSpecial(rune(c)) {
 | 
			
		||||
			// fast path: nothing to do
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		w.Write(b[last:i])
 | 
			
		||||
 | 
			
		||||
		if c < utf8.RuneSelf {
 | 
			
		||||
			// Quotes, slashes and angle brackets get quoted.
 | 
			
		||||
			// Control characters get written as \u00XX.
 | 
			
		||||
			switch c {
 | 
			
		||||
			case '\\':
 | 
			
		||||
				w.Write(jsBackslash)
 | 
			
		||||
			case '\'':
 | 
			
		||||
				w.Write(jsApos)
 | 
			
		||||
			case '"':
 | 
			
		||||
				w.Write(jsQuot)
 | 
			
		||||
			case '<':
 | 
			
		||||
				w.Write(jsLt)
 | 
			
		||||
			case '>':
 | 
			
		||||
				w.Write(jsGt)
 | 
			
		||||
			default:
 | 
			
		||||
				w.Write(jsLowUni)
 | 
			
		||||
				t, b := c>>4, c&0x0f
 | 
			
		||||
				w.Write(hex[t : t+1])
 | 
			
		||||
				w.Write(hex[b : b+1])
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Unicode rune.
 | 
			
		||||
			r, size := utf8.DecodeRune(b[i:])
 | 
			
		||||
			if unicode.IsPrint(r) {
 | 
			
		||||
				w.Write(b[i : i+size])
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Fprintf(w, "\\u%04X", r)
 | 
			
		||||
			}
 | 
			
		||||
			i += size - 1
 | 
			
		||||
		}
 | 
			
		||||
		last = i + 1
 | 
			
		||||
	}
 | 
			
		||||
	w.Write(b[last:])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
 | 
			
		||||
func JSEscapeString(s string) string {
 | 
			
		||||
	// Avoid allocation if we can.
 | 
			
		||||
	if strings.IndexFunc(s, jsIsSpecial) < 0 {
 | 
			
		||||
		return s
 | 
			
		||||
	}
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	JSEscape(&b, []byte(s))
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func jsIsSpecial(r rune) bool {
 | 
			
		||||
	switch r {
 | 
			
		||||
	case '\\', '\'', '"', '<', '>':
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return r < ' ' || utf8.RuneSelf <= r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
 | 
			
		||||
// representation of its arguments.
 | 
			
		||||
func JSEscaper(args ...interface{}) string {
 | 
			
		||||
	return JSEscapeString(evalArgs(args))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// URLQueryEscaper returns the escaped value of the textual representation of
 | 
			
		||||
// its arguments in a form suitable for embedding in a URL query.
 | 
			
		||||
func URLQueryEscaper(args ...interface{}) string {
 | 
			
		||||
	return url.QueryEscape(evalArgs(args))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
 | 
			
		||||
//	fmt.Sprint(args...)
 | 
			
		||||
// except that each argument is indirected (if a pointer), as required,
 | 
			
		||||
// using the same rules as the default string evaluation during template
 | 
			
		||||
// execution.
 | 
			
		||||
func evalArgs(args []interface{}) string {
 | 
			
		||||
	ok := false
 | 
			
		||||
	var s string
 | 
			
		||||
	// Fast path for simple common case.
 | 
			
		||||
	if len(args) == 1 {
 | 
			
		||||
		s, ok = args[0].(string)
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		for i, arg := range args {
 | 
			
		||||
			a, ok := printableValue(reflect.ValueOf(arg))
 | 
			
		||||
			if ok {
 | 
			
		||||
				args[i] = a
 | 
			
		||||
			} // else left fmt do its thing
 | 
			
		||||
		}
 | 
			
		||||
		s = fmt.Sprint(args...)
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user