Files
linuxkit/vendor/github.com/docker/infrakit/pkg/plugin/metadata/reflect.go
Rolf Neugebauer 6a29d153f5 infrakit: Move the hyperkit instance plugin into the source directory
- The tools directory ideally should not contain source code
- Removes double vendoring of packagages
- Makes it easer to hook the build into the top-level Makefile

Eventually, the plugin should be moved to the infrakit repo.

Signed-off-by: Rolf Neugebauer <rolf.neugebauer@docker.com>
2017-03-25 13:02:45 +01:00

231 lines
5.3 KiB
Go

package metadata
import (
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/docker/infrakit/pkg/types"
)
var (
indexRoot = "\\[(([+|-]*[0-9]+)|((.*)=(.*)))\\]$"
arrayIndexExp = regexp.MustCompile("(.*)" + indexRoot)
indexExp = regexp.MustCompile("^" + indexRoot)
)
// Put sets the attribute of an object at path to the given value
func Put(path []string, value interface{}, object map[string]interface{}) bool {
return put(path, value, object)
}
// Get returns the attribute of the object at path
func Get(path []string, object interface{}) interface{} {
return get(path, object)
}
// GetValue returns the attribute of the object at path, as serialized blob
func GetValue(path []string, object interface{}) (*types.Any, error) {
if any, is := object.(*types.Any); is {
return any, nil
}
return types.AnyValue(Get(path, object))
}
// List lists the members at the path
func List(path []string, object interface{}) []string {
list := []string{}
v := get(path, object)
if v == nil {
return list
}
val := reflect.Indirect(reflect.ValueOf(v))
if any, is := v.(*types.Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
val = reflect.ValueOf(temp)
}
}
switch val.Kind() {
case reflect.Slice:
// this is a slice, so return the name as '[%d]'
for i := 0; i < val.Len(); i++ {
list = append(list, fmt.Sprintf("[%d]", i))
}
case reflect.Map:
for _, k := range val.MapKeys() {
list = append(list, k.String())
}
case reflect.Struct:
vt := val.Type()
for i := 0; i < vt.NumField(); i++ {
if vt.Field(i).PkgPath == "" {
list = append(list, vt.Field(i).Name)
}
}
}
sort.Strings(list)
return list
}
func put(p []string, value interface{}, store map[string]interface{}) bool {
if len(p) == 0 {
return false
}
key := p[0]
if key == "" {
return put(p[1:], value, store)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
p = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, p[1:]...)
return put(p, value, store)
}
s := reflect.Indirect(reflect.ValueOf(store))
switch s.Kind() {
case reflect.Slice:
return false // not supported
case reflect.Map:
if reflect.TypeOf(p[0]).AssignableTo(s.Type().Key()) {
m := s.MapIndex(reflect.ValueOf(p[0]))
if !m.IsValid() {
m = reflect.ValueOf(map[string]interface{}{})
s.SetMapIndex(reflect.ValueOf(p[0]), m)
}
if len(p) > 1 {
return put(p[1:], value, m.Interface().(map[string]interface{}))
}
s.SetMapIndex(reflect.ValueOf(p[0]), reflect.ValueOf(value))
return true
}
}
return false
}
func get(path []string, object interface{}) (value interface{}) {
if f, is := object.(func() interface{}); is {
object = f()
}
if len(path) == 0 {
return object
}
if any, is := object.(*types.Any); is {
var temp interface{}
if err := any.Decode(&temp); err == nil {
return get(path, temp)
}
return nil
}
key := path[0]
switch key {
case ".":
return object
case "":
return get(path[1:], object)
}
// check if key is an array index of the form <1>[<2>]
matches := arrayIndexExp.FindStringSubmatch(key)
if len(matches) > 2 && matches[1] != "" {
key = matches[1]
path = append([]string{key, fmt.Sprintf("[%s]", matches[2])}, path[1:]...)
return get(path, object)
}
v := reflect.Indirect(reflect.ValueOf(object))
switch v.Kind() {
case reflect.Slice:
i := 0
matches = indexExp.FindStringSubmatch(key)
if len(matches) > 0 {
if matches[2] != "" {
// numeric index
if index, err := strconv.Atoi(matches[1]); err == nil {
switch {
case index >= 0 && v.Len() > index:
i = index
case index < 0 && v.Len() > -index: // negative index like python
i = v.Len() + index
}
}
return get(path[1:], v.Index(i).Interface())
} else if matches[3] != "" {
// equality search index for 'field=check'
lhs := matches[4] // supports another select expression for extracting deeply from the struct
rhs := matches[5]
// loop through the array looking for field that matches the check value
for j := 0; j < v.Len(); j++ {
if el := get(tokenize(lhs), v.Index(j).Interface()); el != nil {
if fmt.Sprintf("%v", el) == rhs {
return get(path[1:], v.Index(j).Interface())
}
}
}
}
}
case reflect.Map:
value := v.MapIndex(reflect.ValueOf(key))
if value.IsValid() {
return get(path[1:], value.Interface())
}
case reflect.Struct:
fv := v.FieldByName(key)
if !fv.IsValid() {
return nil
}
if !fv.CanInterface() {
return nil
}
return get(path[1:], fv.Interface())
}
return nil
}
// With quoting to support azure rm type names: e.g. Microsoft.Network/virtualNetworks
// This will split a sting like /Resources/'Microsoft.Network/virtualNetworks'/managerSubnet/Name" into
// [ , Resources, Microsoft.Network/virtualNetworks, managerSubnet, Name]
func tokenize(s string) []string {
if len(s) == 0 {
return []string{}
}
a := []string{}
start := 0
quoted := false
for i := 0; i < len(s); i++ {
switch s[i] {
case '/':
if !quoted {
a = append(a, strings.Replace(s[start:i], "'", "", -1))
start = i + 1
}
case '\'':
quoted = !quoted
}
}
if start < len(s)-1 {
a = append(a, strings.Replace(s[start:], "'", "", -1))
}
return a
}