github.com/colincross/blueprint@v0.0.0-20150626231830-9c067caf2eb5/unpack.go (about)

     1  // Copyright 2014 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package blueprint
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/google/blueprint/parser"
    24  	"github.com/google/blueprint/proptools"
    25  )
    26  
    27  type packedProperty struct {
    28  	property *parser.Property
    29  	unpacked bool
    30  }
    31  
    32  func unpackProperties(propertyDefs []*parser.Property,
    33  	propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
    34  
    35  	propertyMap := make(map[string]*packedProperty)
    36  	errs := buildPropertyMap("", propertyDefs, propertyMap)
    37  	if len(errs) > 0 {
    38  		return nil, errs
    39  	}
    40  
    41  	for _, properties := range propertiesStructs {
    42  		propertiesValue := reflect.ValueOf(properties)
    43  		if propertiesValue.Kind() != reflect.Ptr {
    44  			panic("properties must be a pointer to a struct")
    45  		}
    46  
    47  		propertiesValue = propertiesValue.Elem()
    48  		if propertiesValue.Kind() != reflect.Struct {
    49  			panic("properties must be a pointer to a struct")
    50  		}
    51  
    52  		newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "")
    53  		errs = append(errs, newErrs...)
    54  
    55  		if len(errs) >= maxErrors {
    56  			return nil, errs
    57  		}
    58  	}
    59  
    60  	// Report any properties that didn't have corresponding struct fields as
    61  	// errors.
    62  	result := make(map[string]*parser.Property)
    63  	for name, packedProperty := range propertyMap {
    64  		result[name] = packedProperty.property
    65  		if !packedProperty.unpacked {
    66  			err := &Error{
    67  				Err: fmt.Errorf("unrecognized property %q", name),
    68  				Pos: packedProperty.property.Pos,
    69  			}
    70  			errs = append(errs, err)
    71  		}
    72  	}
    73  
    74  	if len(errs) > 0 {
    75  		return nil, errs
    76  	}
    77  
    78  	return result, nil
    79  }
    80  
    81  func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
    82  	propertyMap map[string]*packedProperty) (errs []error) {
    83  
    84  	for _, propertyDef := range propertyDefs {
    85  		name := namePrefix + propertyDef.Name.Name
    86  		if first, present := propertyMap[name]; present {
    87  			if first.property == propertyDef {
    88  				// We've already added this property.
    89  				continue
    90  			}
    91  
    92  			errs = append(errs, &Error{
    93  				Err: fmt.Errorf("property %q already defined", name),
    94  				Pos: propertyDef.Pos,
    95  			})
    96  			errs = append(errs, &Error{
    97  				Err: fmt.Errorf("<-- previous definition here"),
    98  				Pos: first.property.Pos,
    99  			})
   100  			if len(errs) >= maxErrors {
   101  				return errs
   102  			}
   103  			continue
   104  		}
   105  
   106  		propertyMap[name] = &packedProperty{
   107  			property: propertyDef,
   108  			unpacked: false,
   109  		}
   110  
   111  		// We intentionally do not rescursively add MapValue properties to the
   112  		// property map here.  Instead we add them when we encounter a struct
   113  		// into which they can be unpacked.  We do this so that if we never
   114  		// encounter such a struct then the "unrecognized property" error will
   115  		// be reported only once for the map property and not for each of its
   116  		// sub-properties.
   117  	}
   118  
   119  	return
   120  }
   121  
   122  func unpackStructValue(namePrefix string, structValue reflect.Value,
   123  	propertyMap map[string]*packedProperty, filterKey, filterValue string) []error {
   124  
   125  	structType := structValue.Type()
   126  
   127  	var errs []error
   128  	for i := 0; i < structValue.NumField(); i++ {
   129  		fieldValue := structValue.Field(i)
   130  		field := structType.Field(i)
   131  
   132  		if field.PkgPath != "" {
   133  			// This is an unexported field, so just skip it.
   134  			continue
   135  		}
   136  
   137  		if !fieldValue.CanSet() {
   138  			panic(fmt.Errorf("field %s is not settable", field.Name))
   139  		}
   140  
   141  		// To make testing easier we validate the struct field's type regardless
   142  		// of whether or not the property was specified in the parsed string.
   143  		switch kind := fieldValue.Kind(); kind {
   144  		case reflect.Bool, reflect.String, reflect.Struct:
   145  			// Do nothing
   146  		case reflect.Slice:
   147  			elemType := field.Type.Elem()
   148  			if elemType.Kind() != reflect.String {
   149  				panic(fmt.Errorf("field %s is a non-string slice", field.Name))
   150  			}
   151  		case reflect.Interface:
   152  			if fieldValue.IsNil() {
   153  				panic(fmt.Errorf("field %s contains a nil interface",
   154  					field.Name))
   155  			}
   156  			fieldValue = fieldValue.Elem()
   157  			elemType := fieldValue.Type()
   158  			if elemType.Kind() != reflect.Ptr {
   159  				panic(fmt.Errorf("field %s contains a non-pointer interface",
   160  					field.Name))
   161  			}
   162  			fallthrough
   163  		case reflect.Ptr:
   164  			if fieldValue.IsNil() {
   165  				panic(fmt.Errorf("field %s contains a nil pointer",
   166  					field.Name))
   167  			}
   168  			fieldValue = fieldValue.Elem()
   169  			elemType := fieldValue.Type()
   170  			if elemType.Kind() != reflect.Struct {
   171  				panic(fmt.Errorf("field %s contains a non-struct pointer",
   172  					field.Name))
   173  			}
   174  
   175  		case reflect.Int, reflect.Uint:
   176  			if !hasTag(field, "blueprint", "mutated") {
   177  				panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name))
   178  			}
   179  
   180  		default:
   181  			panic(fmt.Errorf("unsupported kind for field %s: %s",
   182  				field.Name, kind))
   183  		}
   184  
   185  		// Get the property value if it was specified.
   186  		propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
   187  		packedProperty, ok := propertyMap[propertyName]
   188  		if !ok {
   189  			// This property wasn't specified.
   190  			continue
   191  		}
   192  
   193  		packedProperty.unpacked = true
   194  
   195  		if hasTag(field, "blueprint", "mutated") {
   196  			errs = append(errs,
   197  				&Error{
   198  					Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
   199  					Pos: packedProperty.property.Pos,
   200  				})
   201  			if len(errs) >= maxErrors {
   202  				return errs
   203  			}
   204  			continue
   205  		}
   206  
   207  		if filterKey != "" && !hasTag(field, filterKey, filterValue) {
   208  			errs = append(errs,
   209  				&Error{
   210  					Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName),
   211  					Pos: packedProperty.property.Pos,
   212  				})
   213  			if len(errs) >= maxErrors {
   214  				return errs
   215  			}
   216  			continue
   217  		}
   218  
   219  		var newErrs []error
   220  
   221  		switch kind := fieldValue.Kind(); kind {
   222  		case reflect.Bool:
   223  			newErrs = unpackBool(fieldValue, packedProperty.property)
   224  		case reflect.String:
   225  			newErrs = unpackString(fieldValue, packedProperty.property)
   226  		case reflect.Slice:
   227  			newErrs = unpackSlice(fieldValue, packedProperty.property)
   228  		case reflect.Ptr, reflect.Interface:
   229  			fieldValue = fieldValue.Elem()
   230  			fallthrough
   231  		case reflect.Struct:
   232  			localFilterKey, localFilterValue := filterKey, filterValue
   233  			if k, v, err := HasFilter(field.Tag); err != nil {
   234  				errs = append(errs, err)
   235  				if len(errs) >= maxErrors {
   236  					return errs
   237  				}
   238  			} else if k != "" {
   239  				if filterKey != "" {
   240  					errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q",
   241  						field.Name))
   242  					if len(errs) >= maxErrors {
   243  						return errs
   244  					}
   245  				} else {
   246  					localFilterKey, localFilterValue = k, v
   247  				}
   248  			}
   249  			newErrs = unpackStruct(propertyName+".", fieldValue,
   250  				packedProperty.property, propertyMap, localFilterKey, localFilterValue)
   251  		}
   252  		errs = append(errs, newErrs...)
   253  		if len(errs) >= maxErrors {
   254  			return errs
   255  		}
   256  	}
   257  
   258  	return errs
   259  }
   260  
   261  func unpackBool(boolValue reflect.Value, property *parser.Property) []error {
   262  	if property.Value.Type != parser.Bool {
   263  		return []error{
   264  			fmt.Errorf("%s: can't assign %s value to %s property %q",
   265  				property.Value.Pos, property.Value.Type, parser.Bool,
   266  				property.Name),
   267  		}
   268  	}
   269  	boolValue.SetBool(property.Value.BoolValue)
   270  	return nil
   271  }
   272  
   273  func unpackString(stringValue reflect.Value,
   274  	property *parser.Property) []error {
   275  
   276  	if property.Value.Type != parser.String {
   277  		return []error{
   278  			fmt.Errorf("%s: can't assign %s value to %s property %q",
   279  				property.Value.Pos, property.Value.Type, parser.String,
   280  				property.Name),
   281  		}
   282  	}
   283  	stringValue.SetString(property.Value.StringValue)
   284  	return nil
   285  }
   286  
   287  func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error {
   288  	if property.Value.Type != parser.List {
   289  		return []error{
   290  			fmt.Errorf("%s: can't assign %s value to %s property %q",
   291  				property.Value.Pos, property.Value.Type, parser.List,
   292  				property.Name),
   293  		}
   294  	}
   295  
   296  	var list []string
   297  	for _, value := range property.Value.ListValue {
   298  		if value.Type != parser.String {
   299  			// The parser should not produce this.
   300  			panic("non-string value found in list")
   301  		}
   302  		list = append(list, value.StringValue)
   303  	}
   304  
   305  	sliceValue.Set(reflect.ValueOf(list))
   306  	return nil
   307  }
   308  
   309  func unpackStruct(namePrefix string, structValue reflect.Value,
   310  	property *parser.Property, propertyMap map[string]*packedProperty,
   311  	filterKey, filterValue string) []error {
   312  
   313  	if property.Value.Type != parser.Map {
   314  		return []error{
   315  			fmt.Errorf("%s: can't assign %s value to %s property %q",
   316  				property.Value.Pos, property.Value.Type, parser.Map,
   317  				property.Name),
   318  		}
   319  	}
   320  
   321  	errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap)
   322  	if len(errs) > 0 {
   323  		return errs
   324  	}
   325  
   326  	return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue)
   327  }
   328  
   329  func hasTag(field reflect.StructField, name, value string) bool {
   330  	tag := field.Tag.Get(name)
   331  	for _, entry := range strings.Split(tag, ",") {
   332  		if entry == value {
   333  			return true
   334  		}
   335  	}
   336  
   337  	return false
   338  }
   339  
   340  func HasFilter(field reflect.StructTag) (k, v string, err error) {
   341  	tag := field.Get("blueprint")
   342  	for _, entry := range strings.Split(tag, ",") {
   343  		if strings.HasPrefix(entry, "filter") {
   344  			if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {
   345  				return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry)
   346  			}
   347  			entry = strings.TrimPrefix(entry, "filter(")
   348  			entry = strings.TrimSuffix(entry, ")")
   349  
   350  			s := strings.Split(entry, ":")
   351  			if len(s) != 2 {
   352  				return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry)
   353  			}
   354  			k = s[0]
   355  			v, err = strconv.Unquote(s[1])
   356  			if err != nil {
   357  				return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error())
   358  			}
   359  			return k, v, nil
   360  		}
   361  	}
   362  
   363  	return "", "", nil
   364  }