2015-01-23 23:15:10 +01:00
|
|
|
// Copyright 2014 Google Inc. 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.
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
package blueprint
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2015-06-22 22:38:45 +02:00
|
|
|
"strconv"
|
2014-12-18 01:46:09 +01:00
|
|
|
"strings"
|
2015-06-22 22:38:45 +02:00
|
|
|
|
|
|
|
"github.com/google/blueprint/parser"
|
|
|
|
"github.com/google/blueprint/proptools"
|
2014-05-28 01:34:41 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type packedProperty struct {
|
|
|
|
property *parser.Property
|
|
|
|
unpacked bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func unpackProperties(propertyDefs []*parser.Property,
|
2014-09-30 20:38:25 +02:00
|
|
|
propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
propertyMap := make(map[string]*packedProperty)
|
2014-09-30 20:38:25 +02:00
|
|
|
errs := buildPropertyMap("", propertyDefs, propertyMap)
|
|
|
|
if len(errs) > 0 {
|
|
|
|
return nil, errs
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, properties := range propertiesStructs {
|
|
|
|
propertiesValue := reflect.ValueOf(properties)
|
|
|
|
if propertiesValue.Kind() != reflect.Ptr {
|
|
|
|
panic("properties must be a pointer to a struct")
|
|
|
|
}
|
|
|
|
|
|
|
|
propertiesValue = propertiesValue.Elem()
|
|
|
|
if propertiesValue.Kind() != reflect.Struct {
|
|
|
|
panic("properties must be a pointer to a struct")
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:38:45 +02:00
|
|
|
newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "")
|
2014-05-28 01:34:41 +02:00
|
|
|
errs = append(errs, newErrs...)
|
|
|
|
|
|
|
|
if len(errs) >= maxErrors {
|
2014-09-30 20:38:25 +02:00
|
|
|
return nil, errs
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report any properties that didn't have corresponding struct fields as
|
|
|
|
// errors.
|
2014-09-30 20:38:25 +02:00
|
|
|
result := make(map[string]*parser.Property)
|
2014-05-28 01:34:41 +02:00
|
|
|
for name, packedProperty := range propertyMap {
|
2014-09-30 20:38:25 +02:00
|
|
|
result[name] = packedProperty.property
|
2014-05-28 01:34:41 +02:00
|
|
|
if !packedProperty.unpacked {
|
2016-10-08 02:13:10 +02:00
|
|
|
err := &BlueprintError{
|
2014-05-28 01:34:41 +02:00
|
|
|
Err: fmt.Errorf("unrecognized property %q", name),
|
2016-06-10 02:03:57 +02:00
|
|
|
Pos: packedProperty.property.ColonPos,
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-30 20:38:25 +02:00
|
|
|
if len(errs) > 0 {
|
|
|
|
return nil, errs
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
|
|
|
|
propertyMap map[string]*packedProperty) (errs []error) {
|
|
|
|
|
|
|
|
for _, propertyDef := range propertyDefs {
|
2016-06-10 00:52:30 +02:00
|
|
|
name := namePrefix + propertyDef.Name
|
2014-09-30 20:38:25 +02:00
|
|
|
if first, present := propertyMap[name]; present {
|
|
|
|
if first.property == propertyDef {
|
|
|
|
// We've already added this property.
|
|
|
|
continue
|
|
|
|
}
|
2016-10-08 02:13:10 +02:00
|
|
|
errs = append(errs, &BlueprintError{
|
2014-09-30 20:38:25 +02:00
|
|
|
Err: fmt.Errorf("property %q already defined", name),
|
2016-06-10 02:03:57 +02:00
|
|
|
Pos: propertyDef.ColonPos,
|
2014-09-30 20:38:25 +02:00
|
|
|
})
|
2016-10-08 02:13:10 +02:00
|
|
|
errs = append(errs, &BlueprintError{
|
2014-09-30 20:38:25 +02:00
|
|
|
Err: fmt.Errorf("<-- previous definition here"),
|
2016-06-10 02:03:57 +02:00
|
|
|
Pos: first.property.ColonPos,
|
2014-09-30 20:38:25 +02:00
|
|
|
})
|
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
propertyMap[name] = &packedProperty{
|
|
|
|
property: propertyDef,
|
|
|
|
unpacked: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
// We intentionally do not rescursively add MapValue properties to the
|
|
|
|
// property map here. Instead we add them when we encounter a struct
|
|
|
|
// into which they can be unpacked. We do this so that if we never
|
|
|
|
// encounter such a struct then the "unrecognized property" error will
|
|
|
|
// be reported only once for the map property and not for each of its
|
|
|
|
// sub-properties.
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2014-09-30 20:38:25 +02:00
|
|
|
func unpackStructValue(namePrefix string, structValue reflect.Value,
|
2015-06-22 22:38:45 +02:00
|
|
|
propertyMap map[string]*packedProperty, filterKey, filterValue string) []error {
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
structType := structValue.Type()
|
|
|
|
|
|
|
|
var errs []error
|
|
|
|
for i := 0; i < structValue.NumField(); i++ {
|
|
|
|
fieldValue := structValue.Field(i)
|
|
|
|
field := structType.Field(i)
|
|
|
|
|
2016-09-07 02:20:57 +02:00
|
|
|
// In Go 1.7, runtime-created structs are unexported, so it's not
|
|
|
|
// possible to create an exported anonymous field with a generated
|
|
|
|
// type. So workaround this by special-casing "BlueprintEmbed" to
|
|
|
|
// behave like an anonymous field for structure unpacking.
|
|
|
|
if field.Name == "BlueprintEmbed" {
|
|
|
|
field.Name = ""
|
|
|
|
field.Anonymous = true
|
|
|
|
}
|
|
|
|
|
2014-06-23 02:02:55 +02:00
|
|
|
if field.PkgPath != "" {
|
|
|
|
// This is an unexported field, so just skip it.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-11-21 02:03:25 +01:00
|
|
|
propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
if !fieldValue.CanSet() {
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("field %s is not settable", propertyName))
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2016-08-09 02:24:03 +02:00
|
|
|
// Get the property value if it was specified.
|
|
|
|
packedProperty, propertyIsSet := propertyMap[propertyName]
|
|
|
|
|
2016-08-06 02:19:36 +02:00
|
|
|
origFieldValue := fieldValue
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
// To make testing easier we validate the struct field's type regardless
|
|
|
|
// of whether or not the property was specified in the parsed string.
|
2016-08-09 02:24:03 +02:00
|
|
|
// TODO(ccross): we don't validate types inside nil struct pointers
|
|
|
|
// Move type validation to a function that runs on each factory once
|
2014-05-28 01:34:41 +02:00
|
|
|
switch kind := fieldValue.Kind(); kind {
|
2014-09-30 20:38:25 +02:00
|
|
|
case reflect.Bool, reflect.String, reflect.Struct:
|
2014-05-28 01:34:41 +02:00
|
|
|
// Do nothing
|
|
|
|
case reflect.Slice:
|
|
|
|
elemType := field.Type.Elem()
|
|
|
|
if elemType.Kind() != reflect.String {
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("field %s is a non-string slice", propertyName))
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2014-09-30 20:38:25 +02:00
|
|
|
case reflect.Interface:
|
|
|
|
if fieldValue.IsNil() {
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("field %s contains a nil interface", propertyName))
|
2014-09-30 20:38:25 +02:00
|
|
|
}
|
|
|
|
fieldValue = fieldValue.Elem()
|
|
|
|
elemType := fieldValue.Type()
|
|
|
|
if elemType.Kind() != reflect.Ptr {
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
|
2014-09-30 20:38:25 +02:00
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case reflect.Ptr:
|
2015-10-30 23:53:55 +01:00
|
|
|
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
|
|
|
|
case reflect.Struct:
|
2016-08-09 02:24:03 +02:00
|
|
|
if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
|
|
|
|
// Instantiate nil struct pointers
|
|
|
|
// Set into origFieldValue in case it was an interface, in which case
|
|
|
|
// fieldValue points to the unsettable pointer inside the interface
|
2016-08-06 02:19:36 +02:00
|
|
|
fieldValue = reflect.New(fieldValue.Type().Elem())
|
|
|
|
origFieldValue.Set(fieldValue)
|
2015-10-30 23:53:55 +01:00
|
|
|
}
|
|
|
|
fieldValue = fieldValue.Elem()
|
|
|
|
case reflect.Bool, reflect.String:
|
|
|
|
// Nothing
|
|
|
|
default:
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2015-06-22 22:38:45 +02:00
|
|
|
|
2014-12-18 01:46:09 +01:00
|
|
|
case reflect.Int, reflect.Uint:
|
2015-10-28 02:15:15 +01:00
|
|
|
if !proptools.HasTag(field, "blueprint", "mutated") {
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
|
2014-12-18 01:46:09 +01:00
|
|
|
}
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
default:
|
2015-11-21 02:03:25 +01:00
|
|
|
panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
|
|
|
|
}
|
|
|
|
|
|
|
|
if field.Anonymous && fieldValue.Kind() == reflect.Struct {
|
|
|
|
newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue)
|
|
|
|
errs = append(errs, newErrs...)
|
|
|
|
continue
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2016-08-09 02:24:03 +02:00
|
|
|
if !propertyIsSet {
|
2014-05-28 01:34:41 +02:00
|
|
|
// This property wasn't specified.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:38:45 +02:00
|
|
|
packedProperty.unpacked = true
|
2014-12-18 01:46:09 +01:00
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
if proptools.HasTag(field, "blueprint", "mutated") {
|
2014-12-18 01:46:09 +01:00
|
|
|
errs = append(errs,
|
2016-10-08 02:13:10 +02:00
|
|
|
&BlueprintError{
|
2015-06-22 22:38:45 +02:00
|
|
|
Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
|
2016-06-10 02:03:57 +02:00
|
|
|
Pos: packedProperty.property.ColonPos,
|
2015-06-22 22:38:45 +02:00
|
|
|
})
|
2014-12-18 01:46:09 +01:00
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-10-28 02:15:15 +01:00
|
|
|
if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) {
|
2015-06-22 22:38:45 +02:00
|
|
|
errs = append(errs,
|
2016-10-08 02:13:10 +02:00
|
|
|
&BlueprintError{
|
2015-06-22 22:38:45 +02:00
|
|
|
Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName),
|
2016-06-10 02:03:57 +02:00
|
|
|
Pos: packedProperty.property.ColonPos,
|
2015-06-22 22:38:45 +02:00
|
|
|
})
|
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var newErrs []error
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
switch kind := fieldValue.Kind(); kind {
|
|
|
|
case reflect.Bool:
|
2014-10-05 16:41:44 +02:00
|
|
|
newErrs = unpackBool(fieldValue, packedProperty.property)
|
2014-05-28 01:34:41 +02:00
|
|
|
case reflect.String:
|
2014-10-05 16:41:44 +02:00
|
|
|
newErrs = unpackString(fieldValue, packedProperty.property)
|
2014-05-28 01:34:41 +02:00
|
|
|
case reflect.Slice:
|
2014-10-05 16:41:44 +02:00
|
|
|
newErrs = unpackSlice(fieldValue, packedProperty.property)
|
2015-10-30 23:53:55 +01:00
|
|
|
case reflect.Ptr:
|
|
|
|
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
|
|
|
|
case reflect.Bool:
|
|
|
|
newValue := reflect.New(fieldValue.Type().Elem())
|
|
|
|
newErrs = unpackBool(newValue.Elem(), packedProperty.property)
|
|
|
|
fieldValue.Set(newValue)
|
|
|
|
case reflect.String:
|
|
|
|
newValue := reflect.New(fieldValue.Type().Elem())
|
|
|
|
newErrs = unpackString(newValue.Elem(), packedProperty.property)
|
|
|
|
fieldValue.Set(newValue)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unexpected pointer kind %s", ptrKind))
|
|
|
|
}
|
2014-09-30 20:38:25 +02:00
|
|
|
case reflect.Struct:
|
2015-06-22 22:38:45 +02:00
|
|
|
localFilterKey, localFilterValue := filterKey, filterValue
|
2015-05-13 23:36:24 +02:00
|
|
|
if k, v, err := HasFilter(field.Tag); err != nil {
|
2015-06-22 22:38:45 +02:00
|
|
|
errs = append(errs, err)
|
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
} else if k != "" {
|
|
|
|
if filterKey != "" {
|
|
|
|
errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q",
|
|
|
|
field.Name))
|
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
localFilterKey, localFilterValue = k, v
|
|
|
|
}
|
|
|
|
}
|
2014-09-30 20:38:25 +02:00
|
|
|
newErrs = unpackStruct(propertyName+".", fieldValue,
|
2015-06-22 22:38:45 +02:00
|
|
|
packedProperty.property, propertyMap, localFilterKey, localFilterValue)
|
2015-10-30 23:53:55 +01:00
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unexpected kind %s", kind))
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
errs = append(errs, newErrs...)
|
|
|
|
if len(errs) >= maxErrors {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
|
2014-10-05 16:41:44 +02:00
|
|
|
func unpackBool(boolValue reflect.Value, property *parser.Property) []error {
|
2016-06-07 21:28:16 +02:00
|
|
|
b, ok := property.Value.Eval().(*parser.Bool)
|
|
|
|
if !ok {
|
2014-05-28 01:34:41 +02:00
|
|
|
return []error{
|
2016-06-07 21:28:16 +02:00
|
|
|
fmt.Errorf("%s: can't assign %s value to bool property %q",
|
2016-07-16 01:40:37 +02:00
|
|
|
property.Value.Pos(), property.Value.Type(), property.Name),
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-07 21:28:16 +02:00
|
|
|
boolValue.SetBool(b.Value)
|
2014-05-28 01:34:41 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unpackString(stringValue reflect.Value,
|
2014-10-05 16:41:44 +02:00
|
|
|
property *parser.Property) []error {
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
s, ok := property.Value.Eval().(*parser.String)
|
|
|
|
if !ok {
|
2014-05-28 01:34:41 +02:00
|
|
|
return []error{
|
2016-06-07 21:28:16 +02:00
|
|
|
fmt.Errorf("%s: can't assign %s value to string property %q",
|
2016-07-16 01:40:37 +02:00
|
|
|
property.Value.Pos(), property.Value.Type(), property.Name),
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-07 21:28:16 +02:00
|
|
|
stringValue.SetString(s.Value)
|
2014-05-28 01:34:41 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-05 16:41:44 +02:00
|
|
|
func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error {
|
2016-06-07 21:28:16 +02:00
|
|
|
|
|
|
|
l, ok := property.Value.Eval().(*parser.List)
|
|
|
|
if !ok {
|
2014-05-28 01:34:41 +02:00
|
|
|
return []error{
|
2016-06-07 21:28:16 +02:00
|
|
|
fmt.Errorf("%s: can't assign %s value to list property %q",
|
2016-07-16 01:40:37 +02:00
|
|
|
property.Value.Pos(), property.Value.Type(), property.Name),
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
list := make([]string, len(l.Values))
|
|
|
|
for i, value := range l.Values {
|
|
|
|
s, ok := value.Eval().(*parser.String)
|
|
|
|
if !ok {
|
2014-05-28 01:34:41 +02:00
|
|
|
// The parser should not produce this.
|
2016-06-07 21:28:16 +02:00
|
|
|
panic(fmt.Errorf("non-string value %q found in list", value))
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2016-06-07 21:28:16 +02:00
|
|
|
list[i] = s.Value
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2014-10-05 16:41:44 +02:00
|
|
|
sliceValue.Set(reflect.ValueOf(list))
|
2014-05-28 01:34:41 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-09-30 20:38:25 +02:00
|
|
|
func unpackStruct(namePrefix string, structValue reflect.Value,
|
2015-06-22 22:38:45 +02:00
|
|
|
property *parser.Property, propertyMap map[string]*packedProperty,
|
|
|
|
filterKey, filterValue string) []error {
|
2014-09-30 20:38:25 +02:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
m, ok := property.Value.Eval().(*parser.Map)
|
|
|
|
if !ok {
|
2014-09-30 20:38:25 +02:00
|
|
|
return []error{
|
2016-06-07 21:28:16 +02:00
|
|
|
fmt.Errorf("%s: can't assign %s value to map property %q",
|
2016-07-16 01:40:37 +02:00
|
|
|
property.Value.Pos(), property.Value.Type(), property.Name),
|
2014-09-30 20:38:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
|
2014-09-30 20:38:25 +02:00
|
|
|
if len(errs) > 0 {
|
|
|
|
return errs
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2014-09-30 20:38:25 +02:00
|
|
|
|
2015-06-22 22:38:45 +02:00
|
|
|
return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue)
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2014-12-18 01:46:09 +01:00
|
|
|
|
2015-05-13 23:36:24 +02:00
|
|
|
func HasFilter(field reflect.StructTag) (k, v string, err error) {
|
|
|
|
tag := field.Get("blueprint")
|
2015-06-22 22:38:45 +02:00
|
|
|
for _, entry := range strings.Split(tag, ",") {
|
|
|
|
if strings.HasPrefix(entry, "filter") {
|
|
|
|
if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {
|
|
|
|
return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry)
|
|
|
|
}
|
|
|
|
entry = strings.TrimPrefix(entry, "filter(")
|
|
|
|
entry = strings.TrimSuffix(entry, ")")
|
|
|
|
|
|
|
|
s := strings.Split(entry, ":")
|
|
|
|
if len(s) != 2 {
|
|
|
|
return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry)
|
|
|
|
}
|
|
|
|
k = s[0]
|
|
|
|
v, err = strconv.Unquote(s[1])
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error())
|
|
|
|
}
|
|
|
|
return k, v, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", "", nil
|
|
|
|
}
|