435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
816 lines
20 KiB
Go
816 lines
20 KiB
Go
package yaml
|
|
|
|
import (
|
|
"encoding"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
documentNode = 1 << iota
|
|
mappingNode
|
|
sequenceNode
|
|
scalarNode
|
|
aliasNode
|
|
)
|
|
|
|
type node struct {
|
|
kind int
|
|
line, column int
|
|
tag string
|
|
// For an alias node, alias holds the resolved alias.
|
|
alias *node
|
|
value string
|
|
implicit bool
|
|
children []*node
|
|
anchors map[string]*node
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Parser, produces a node tree out of a libyaml event stream.
|
|
|
|
type parser struct {
|
|
parser yaml_parser_t
|
|
event yaml_event_t
|
|
doc *node
|
|
doneInit bool
|
|
}
|
|
|
|
func newParser(b []byte) *parser {
|
|
p := parser{}
|
|
if !yaml_parser_initialize(&p.parser) {
|
|
panic("failed to initialize YAML emitter")
|
|
}
|
|
if len(b) == 0 {
|
|
b = []byte{'\n'}
|
|
}
|
|
yaml_parser_set_input_string(&p.parser, b)
|
|
return &p
|
|
}
|
|
|
|
func newParserFromReader(r io.Reader) *parser {
|
|
p := parser{}
|
|
if !yaml_parser_initialize(&p.parser) {
|
|
panic("failed to initialize YAML emitter")
|
|
}
|
|
yaml_parser_set_input_reader(&p.parser, r)
|
|
return &p
|
|
}
|
|
|
|
func (p *parser) init() {
|
|
if p.doneInit {
|
|
return
|
|
}
|
|
p.expect(yaml_STREAM_START_EVENT)
|
|
p.doneInit = true
|
|
}
|
|
|
|
func (p *parser) destroy() {
|
|
if p.event.typ != yaml_NO_EVENT {
|
|
yaml_event_delete(&p.event)
|
|
}
|
|
yaml_parser_delete(&p.parser)
|
|
}
|
|
|
|
// expect consumes an event from the event stream and
|
|
// checks that it's of the expected type.
|
|
func (p *parser) expect(e yaml_event_type_t) {
|
|
if p.event.typ == yaml_NO_EVENT {
|
|
if !yaml_parser_parse(&p.parser, &p.event) {
|
|
p.fail()
|
|
}
|
|
}
|
|
if p.event.typ == yaml_STREAM_END_EVENT {
|
|
failf("attempted to go past the end of stream; corrupted value?")
|
|
}
|
|
if p.event.typ != e {
|
|
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
|
|
p.fail()
|
|
}
|
|
yaml_event_delete(&p.event)
|
|
p.event.typ = yaml_NO_EVENT
|
|
}
|
|
|
|
// peek peeks at the next event in the event stream,
|
|
// puts the results into p.event and returns the event type.
|
|
func (p *parser) peek() yaml_event_type_t {
|
|
if p.event.typ != yaml_NO_EVENT {
|
|
return p.event.typ
|
|
}
|
|
if !yaml_parser_parse(&p.parser, &p.event) {
|
|
p.fail()
|
|
}
|
|
return p.event.typ
|
|
}
|
|
|
|
func (p *parser) fail() {
|
|
var where string
|
|
var line int
|
|
if p.parser.problem_mark.line != 0 {
|
|
line = p.parser.problem_mark.line
|
|
// Scanner errors don't iterate line before returning error
|
|
if p.parser.error == yaml_SCANNER_ERROR {
|
|
line++
|
|
}
|
|
} else if p.parser.context_mark.line != 0 {
|
|
line = p.parser.context_mark.line
|
|
}
|
|
if line != 0 {
|
|
where = "line " + strconv.Itoa(line) + ": "
|
|
}
|
|
var msg string
|
|
if len(p.parser.problem) > 0 {
|
|
msg = p.parser.problem
|
|
} else {
|
|
msg = "unknown problem parsing YAML content"
|
|
}
|
|
failf("%s%s", where, msg)
|
|
}
|
|
|
|
func (p *parser) anchor(n *node, anchor []byte) {
|
|
if anchor != nil {
|
|
p.doc.anchors[string(anchor)] = n
|
|
}
|
|
}
|
|
|
|
func (p *parser) parse() *node {
|
|
p.init()
|
|
switch p.peek() {
|
|
case yaml_SCALAR_EVENT:
|
|
return p.scalar()
|
|
case yaml_ALIAS_EVENT:
|
|
return p.alias()
|
|
case yaml_MAPPING_START_EVENT:
|
|
return p.mapping()
|
|
case yaml_SEQUENCE_START_EVENT:
|
|
return p.sequence()
|
|
case yaml_DOCUMENT_START_EVENT:
|
|
return p.document()
|
|
case yaml_STREAM_END_EVENT:
|
|
// Happens when attempting to decode an empty buffer.
|
|
return nil
|
|
default:
|
|
panic("attempted to parse unknown event: " + p.event.typ.String())
|
|
}
|
|
}
|
|
|
|
func (p *parser) node(kind int) *node {
|
|
return &node{
|
|
kind: kind,
|
|
line: p.event.start_mark.line,
|
|
column: p.event.start_mark.column,
|
|
}
|
|
}
|
|
|
|
func (p *parser) document() *node {
|
|
n := p.node(documentNode)
|
|
n.anchors = make(map[string]*node)
|
|
p.doc = n
|
|
p.expect(yaml_DOCUMENT_START_EVENT)
|
|
n.children = append(n.children, p.parse())
|
|
p.expect(yaml_DOCUMENT_END_EVENT)
|
|
return n
|
|
}
|
|
|
|
func (p *parser) alias() *node {
|
|
n := p.node(aliasNode)
|
|
n.value = string(p.event.anchor)
|
|
n.alias = p.doc.anchors[n.value]
|
|
if n.alias == nil {
|
|
failf("unknown anchor '%s' referenced", n.value)
|
|
}
|
|
p.expect(yaml_ALIAS_EVENT)
|
|
return n
|
|
}
|
|
|
|
func (p *parser) scalar() *node {
|
|
n := p.node(scalarNode)
|
|
n.value = string(p.event.value)
|
|
n.tag = string(p.event.tag)
|
|
n.implicit = p.event.implicit
|
|
p.anchor(n, p.event.anchor)
|
|
p.expect(yaml_SCALAR_EVENT)
|
|
return n
|
|
}
|
|
|
|
func (p *parser) sequence() *node {
|
|
n := p.node(sequenceNode)
|
|
p.anchor(n, p.event.anchor)
|
|
p.expect(yaml_SEQUENCE_START_EVENT)
|
|
for p.peek() != yaml_SEQUENCE_END_EVENT {
|
|
n.children = append(n.children, p.parse())
|
|
}
|
|
p.expect(yaml_SEQUENCE_END_EVENT)
|
|
return n
|
|
}
|
|
|
|
func (p *parser) mapping() *node {
|
|
n := p.node(mappingNode)
|
|
p.anchor(n, p.event.anchor)
|
|
p.expect(yaml_MAPPING_START_EVENT)
|
|
for p.peek() != yaml_MAPPING_END_EVENT {
|
|
n.children = append(n.children, p.parse(), p.parse())
|
|
}
|
|
p.expect(yaml_MAPPING_END_EVENT)
|
|
return n
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Decoder, unmarshals a node into a provided value.
|
|
|
|
type decoder struct {
|
|
doc *node
|
|
aliases map[*node]bool
|
|
mapType reflect.Type
|
|
terrors []string
|
|
strict bool
|
|
|
|
decodeCount int
|
|
aliasCount int
|
|
aliasDepth int
|
|
}
|
|
|
|
var (
|
|
mapItemType = reflect.TypeOf(MapItem{})
|
|
durationType = reflect.TypeOf(time.Duration(0))
|
|
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
|
|
ifaceType = defaultMapType.Elem()
|
|
timeType = reflect.TypeOf(time.Time{})
|
|
ptrTimeType = reflect.TypeOf(&time.Time{})
|
|
)
|
|
|
|
func newDecoder(strict bool) *decoder {
|
|
d := &decoder{mapType: defaultMapType, strict: strict}
|
|
d.aliases = make(map[*node]bool)
|
|
return d
|
|
}
|
|
|
|
func (d *decoder) terror(n *node, tag string, out reflect.Value) {
|
|
if n.tag != "" {
|
|
tag = n.tag
|
|
}
|
|
value := n.value
|
|
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
|
|
if len(value) > 10 {
|
|
value = " `" + value[:7] + "...`"
|
|
} else {
|
|
value = " `" + value + "`"
|
|
}
|
|
}
|
|
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
|
|
}
|
|
|
|
func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
|
|
terrlen := len(d.terrors)
|
|
err := u.UnmarshalYAML(func(v interface{}) (err error) {
|
|
defer handleErr(&err)
|
|
d.unmarshal(n, reflect.ValueOf(v))
|
|
if len(d.terrors) > terrlen {
|
|
issues := d.terrors[terrlen:]
|
|
d.terrors = d.terrors[:terrlen]
|
|
return &TypeError{issues}
|
|
}
|
|
return nil
|
|
})
|
|
if e, ok := err.(*TypeError); ok {
|
|
d.terrors = append(d.terrors, e.Errors...)
|
|
return false
|
|
}
|
|
if err != nil {
|
|
fail(err)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
|
|
// if a value is found to implement it.
|
|
// It returns the initialized and dereferenced out value, whether
|
|
// unmarshalling was already done by UnmarshalYAML, and if so whether
|
|
// its types unmarshalled appropriately.
|
|
//
|
|
// If n holds a null value, prepare returns before doing anything.
|
|
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
|
|
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
|
|
return out, false, false
|
|
}
|
|
again := true
|
|
for again {
|
|
again = false
|
|
if out.Kind() == reflect.Ptr {
|
|
if out.IsNil() {
|
|
out.Set(reflect.New(out.Type().Elem()))
|
|
}
|
|
out = out.Elem()
|
|
again = true
|
|
}
|
|
if out.CanAddr() {
|
|
if u, ok := out.Addr().Interface().(Unmarshaler); ok {
|
|
good = d.callUnmarshaler(n, u)
|
|
return out, true, good
|
|
}
|
|
}
|
|
}
|
|
return out, false, false
|
|
}
|
|
|
|
const (
|
|
// 400,000 decode operations is ~500kb of dense object declarations, or
|
|
// ~5kb of dense object declarations with 10000% alias expansion
|
|
alias_ratio_range_low = 400000
|
|
|
|
// 4,000,000 decode operations is ~5MB of dense object declarations, or
|
|
// ~4.5MB of dense object declarations with 10% alias expansion
|
|
alias_ratio_range_high = 4000000
|
|
|
|
// alias_ratio_range is the range over which we scale allowed alias ratios
|
|
alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low)
|
|
)
|
|
|
|
func allowedAliasRatio(decodeCount int) float64 {
|
|
switch {
|
|
case decodeCount <= alias_ratio_range_low:
|
|
// allow 99% to come from alias expansion for small-to-medium documents
|
|
return 0.99
|
|
case decodeCount >= alias_ratio_range_high:
|
|
// allow 10% to come from alias expansion for very large documents
|
|
return 0.10
|
|
default:
|
|
// scale smoothly from 99% down to 10% over the range.
|
|
// this maps to 396,000 - 400,000 allowed alias-driven decodes over the range.
|
|
// 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps).
|
|
return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range)
|
|
}
|
|
}
|
|
|
|
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
|
d.decodeCount++
|
|
if d.aliasDepth > 0 {
|
|
d.aliasCount++
|
|
}
|
|
if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) {
|
|
failf("document contains excessive aliasing")
|
|
}
|
|
switch n.kind {
|
|
case documentNode:
|
|
return d.document(n, out)
|
|
case aliasNode:
|
|
return d.alias(n, out)
|
|
}
|
|
out, unmarshaled, good := d.prepare(n, out)
|
|
if unmarshaled {
|
|
return good
|
|
}
|
|
switch n.kind {
|
|
case scalarNode:
|
|
good = d.scalar(n, out)
|
|
case mappingNode:
|
|
good = d.mapping(n, out)
|
|
case sequenceNode:
|
|
good = d.sequence(n, out)
|
|
default:
|
|
panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
|
|
}
|
|
return good
|
|
}
|
|
|
|
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
|
if len(n.children) == 1 {
|
|
d.doc = n
|
|
d.unmarshal(n.children[0], out)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
|
if d.aliases[n] {
|
|
// TODO this could actually be allowed in some circumstances.
|
|
failf("anchor '%s' value contains itself", n.value)
|
|
}
|
|
d.aliases[n] = true
|
|
d.aliasDepth++
|
|
good = d.unmarshal(n.alias, out)
|
|
d.aliasDepth--
|
|
delete(d.aliases, n)
|
|
return good
|
|
}
|
|
|
|
var zeroValue reflect.Value
|
|
|
|
func resetMap(out reflect.Value) {
|
|
for _, k := range out.MapKeys() {
|
|
out.SetMapIndex(k, zeroValue)
|
|
}
|
|
}
|
|
|
|
func (d *decoder) scalar(n *node, out reflect.Value) bool {
|
|
var tag string
|
|
var resolved interface{}
|
|
if n.tag == "" && !n.implicit {
|
|
tag = yaml_STR_TAG
|
|
resolved = n.value
|
|
} else {
|
|
tag, resolved = resolve(n.tag, n.value)
|
|
if tag == yaml_BINARY_TAG {
|
|
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
|
if err != nil {
|
|
failf("!!binary value contains invalid base64 data")
|
|
}
|
|
resolved = string(data)
|
|
}
|
|
}
|
|
if resolved == nil {
|
|
if out.Kind() == reflect.Map && !out.CanAddr() {
|
|
resetMap(out)
|
|
} else {
|
|
out.Set(reflect.Zero(out.Type()))
|
|
}
|
|
return true
|
|
}
|
|
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
|
// We've resolved to exactly the type we want, so use that.
|
|
out.Set(resolvedv)
|
|
return true
|
|
}
|
|
// Perhaps we can use the value as a TextUnmarshaler to
|
|
// set its value.
|
|
if out.CanAddr() {
|
|
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
|
|
if ok {
|
|
var text []byte
|
|
if tag == yaml_BINARY_TAG {
|
|
text = []byte(resolved.(string))
|
|
} else {
|
|
// We let any value be unmarshaled into TextUnmarshaler.
|
|
// That might be more lax than we'd like, but the
|
|
// TextUnmarshaler itself should bowl out any dubious values.
|
|
text = []byte(n.value)
|
|
}
|
|
err := u.UnmarshalText(text)
|
|
if err != nil {
|
|
fail(err)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
switch out.Kind() {
|
|
case reflect.String:
|
|
if tag == yaml_BINARY_TAG {
|
|
out.SetString(resolved.(string))
|
|
return true
|
|
}
|
|
if resolved != nil {
|
|
out.SetString(n.value)
|
|
return true
|
|
}
|
|
case reflect.Interface:
|
|
if resolved == nil {
|
|
out.Set(reflect.Zero(out.Type()))
|
|
} else if tag == yaml_TIMESTAMP_TAG {
|
|
// It looks like a timestamp but for backward compatibility
|
|
// reasons we set it as a string, so that code that unmarshals
|
|
// timestamp-like values into interface{} will continue to
|
|
// see a string and not a time.Time.
|
|
// TODO(v3) Drop this.
|
|
out.Set(reflect.ValueOf(n.value))
|
|
} else {
|
|
out.Set(reflect.ValueOf(resolved))
|
|
}
|
|
return true
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
switch resolved := resolved.(type) {
|
|
case int:
|
|
if !out.OverflowInt(int64(resolved)) {
|
|
out.SetInt(int64(resolved))
|
|
return true
|
|
}
|
|
case int64:
|
|
if !out.OverflowInt(resolved) {
|
|
out.SetInt(resolved)
|
|
return true
|
|
}
|
|
case uint64:
|
|
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
|
out.SetInt(int64(resolved))
|
|
return true
|
|
}
|
|
case float64:
|
|
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
|
out.SetInt(int64(resolved))
|
|
return true
|
|
}
|
|
case string:
|
|
if out.Type() == durationType {
|
|
d, err := time.ParseDuration(resolved)
|
|
if err == nil {
|
|
out.SetInt(int64(d))
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
switch resolved := resolved.(type) {
|
|
case int:
|
|
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
|
out.SetUint(uint64(resolved))
|
|
return true
|
|
}
|
|
case int64:
|
|
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
|
out.SetUint(uint64(resolved))
|
|
return true
|
|
}
|
|
case uint64:
|
|
if !out.OverflowUint(uint64(resolved)) {
|
|
out.SetUint(uint64(resolved))
|
|
return true
|
|
}
|
|
case float64:
|
|
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
|
|
out.SetUint(uint64(resolved))
|
|
return true
|
|
}
|
|
}
|
|
case reflect.Bool:
|
|
switch resolved := resolved.(type) {
|
|
case bool:
|
|
out.SetBool(resolved)
|
|
return true
|
|
}
|
|
case reflect.Float32, reflect.Float64:
|
|
switch resolved := resolved.(type) {
|
|
case int:
|
|
out.SetFloat(float64(resolved))
|
|
return true
|
|
case int64:
|
|
out.SetFloat(float64(resolved))
|
|
return true
|
|
case uint64:
|
|
out.SetFloat(float64(resolved))
|
|
return true
|
|
case float64:
|
|
out.SetFloat(resolved)
|
|
return true
|
|
}
|
|
case reflect.Struct:
|
|
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
|
out.Set(resolvedv)
|
|
return true
|
|
}
|
|
case reflect.Ptr:
|
|
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
|
// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
|
|
elem := reflect.New(out.Type().Elem())
|
|
elem.Elem().Set(reflect.ValueOf(resolved))
|
|
out.Set(elem)
|
|
return true
|
|
}
|
|
}
|
|
d.terror(n, tag, out)
|
|
return false
|
|
}
|
|
|
|
func settableValueOf(i interface{}) reflect.Value {
|
|
v := reflect.ValueOf(i)
|
|
sv := reflect.New(v.Type()).Elem()
|
|
sv.Set(v)
|
|
return sv
|
|
}
|
|
|
|
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
|
l := len(n.children)
|
|
|
|
var iface reflect.Value
|
|
switch out.Kind() {
|
|
case reflect.Slice:
|
|
out.Set(reflect.MakeSlice(out.Type(), l, l))
|
|
case reflect.Array:
|
|
if l != out.Len() {
|
|
failf("invalid array: want %d elements but got %d", out.Len(), l)
|
|
}
|
|
case reflect.Interface:
|
|
// No type hints. Will have to use a generic sequence.
|
|
iface = out
|
|
out = settableValueOf(make([]interface{}, l))
|
|
default:
|
|
d.terror(n, yaml_SEQ_TAG, out)
|
|
return false
|
|
}
|
|
et := out.Type().Elem()
|
|
|
|
j := 0
|
|
for i := 0; i < l; i++ {
|
|
e := reflect.New(et).Elem()
|
|
if ok := d.unmarshal(n.children[i], e); ok {
|
|
out.Index(j).Set(e)
|
|
j++
|
|
}
|
|
}
|
|
if out.Kind() != reflect.Array {
|
|
out.Set(out.Slice(0, j))
|
|
}
|
|
if iface.IsValid() {
|
|
iface.Set(out)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
|
switch out.Kind() {
|
|
case reflect.Struct:
|
|
return d.mappingStruct(n, out)
|
|
case reflect.Slice:
|
|
return d.mappingSlice(n, out)
|
|
case reflect.Map:
|
|
// okay
|
|
case reflect.Interface:
|
|
if d.mapType.Kind() == reflect.Map {
|
|
iface := out
|
|
out = reflect.MakeMap(d.mapType)
|
|
iface.Set(out)
|
|
} else {
|
|
slicev := reflect.New(d.mapType).Elem()
|
|
if !d.mappingSlice(n, slicev) {
|
|
return false
|
|
}
|
|
out.Set(slicev)
|
|
return true
|
|
}
|
|
default:
|
|
d.terror(n, yaml_MAP_TAG, out)
|
|
return false
|
|
}
|
|
outt := out.Type()
|
|
kt := outt.Key()
|
|
et := outt.Elem()
|
|
|
|
mapType := d.mapType
|
|
if outt.Key() == ifaceType && outt.Elem() == ifaceType {
|
|
d.mapType = outt
|
|
}
|
|
|
|
if out.IsNil() {
|
|
out.Set(reflect.MakeMap(outt))
|
|
}
|
|
l := len(n.children)
|
|
for i := 0; i < l; i += 2 {
|
|
if isMerge(n.children[i]) {
|
|
d.merge(n.children[i+1], out)
|
|
continue
|
|
}
|
|
k := reflect.New(kt).Elem()
|
|
if d.unmarshal(n.children[i], k) {
|
|
kkind := k.Kind()
|
|
if kkind == reflect.Interface {
|
|
kkind = k.Elem().Kind()
|
|
}
|
|
if kkind == reflect.Map || kkind == reflect.Slice {
|
|
failf("invalid map key: %#v", k.Interface())
|
|
}
|
|
e := reflect.New(et).Elem()
|
|
if d.unmarshal(n.children[i+1], e) {
|
|
d.setMapIndex(n.children[i+1], out, k, e)
|
|
}
|
|
}
|
|
}
|
|
d.mapType = mapType
|
|
return true
|
|
}
|
|
|
|
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
|
|
if d.strict && out.MapIndex(k) != zeroValue {
|
|
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
|
|
return
|
|
}
|
|
out.SetMapIndex(k, v)
|
|
}
|
|
|
|
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
|
|
outt := out.Type()
|
|
if outt.Elem() != mapItemType {
|
|
d.terror(n, yaml_MAP_TAG, out)
|
|
return false
|
|
}
|
|
|
|
mapType := d.mapType
|
|
d.mapType = outt
|
|
|
|
var slice []MapItem
|
|
var l = len(n.children)
|
|
for i := 0; i < l; i += 2 {
|
|
if isMerge(n.children[i]) {
|
|
d.merge(n.children[i+1], out)
|
|
continue
|
|
}
|
|
item := MapItem{}
|
|
k := reflect.ValueOf(&item.Key).Elem()
|
|
if d.unmarshal(n.children[i], k) {
|
|
v := reflect.ValueOf(&item.Value).Elem()
|
|
if d.unmarshal(n.children[i+1], v) {
|
|
slice = append(slice, item)
|
|
}
|
|
}
|
|
}
|
|
out.Set(reflect.ValueOf(slice))
|
|
d.mapType = mapType
|
|
return true
|
|
}
|
|
|
|
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
|
sinfo, err := getStructInfo(out.Type())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
name := settableValueOf("")
|
|
l := len(n.children)
|
|
|
|
var inlineMap reflect.Value
|
|
var elemType reflect.Type
|
|
if sinfo.InlineMap != -1 {
|
|
inlineMap = out.Field(sinfo.InlineMap)
|
|
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
|
elemType = inlineMap.Type().Elem()
|
|
}
|
|
|
|
var doneFields []bool
|
|
if d.strict {
|
|
doneFields = make([]bool, len(sinfo.FieldsList))
|
|
}
|
|
for i := 0; i < l; i += 2 {
|
|
ni := n.children[i]
|
|
if isMerge(ni) {
|
|
d.merge(n.children[i+1], out)
|
|
continue
|
|
}
|
|
if !d.unmarshal(ni, name) {
|
|
continue
|
|
}
|
|
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
|
if d.strict {
|
|
if doneFields[info.Id] {
|
|
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
|
|
continue
|
|
}
|
|
doneFields[info.Id] = true
|
|
}
|
|
var field reflect.Value
|
|
if info.Inline == nil {
|
|
field = out.Field(info.Num)
|
|
} else {
|
|
field = out.FieldByIndex(info.Inline)
|
|
}
|
|
d.unmarshal(n.children[i+1], field)
|
|
} else if sinfo.InlineMap != -1 {
|
|
if inlineMap.IsNil() {
|
|
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
|
|
}
|
|
value := reflect.New(elemType).Elem()
|
|
d.unmarshal(n.children[i+1], value)
|
|
d.setMapIndex(n.children[i+1], inlineMap, name, value)
|
|
} else if d.strict {
|
|
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func failWantMap() {
|
|
failf("map merge requires map or sequence of maps as the value")
|
|
}
|
|
|
|
func (d *decoder) merge(n *node, out reflect.Value) {
|
|
switch n.kind {
|
|
case mappingNode:
|
|
d.unmarshal(n, out)
|
|
case aliasNode:
|
|
if n.alias != nil && n.alias.kind != mappingNode {
|
|
failWantMap()
|
|
}
|
|
d.unmarshal(n, out)
|
|
case sequenceNode:
|
|
// Step backwards as earlier nodes take precedence.
|
|
for i := len(n.children) - 1; i >= 0; i-- {
|
|
ni := n.children[i]
|
|
if ni.kind == aliasNode {
|
|
if ni.alias != nil && ni.alias.kind != mappingNode {
|
|
failWantMap()
|
|
}
|
|
} else if ni.kind != mappingNode {
|
|
failWantMap()
|
|
}
|
|
d.unmarshal(ni, out)
|
|
}
|
|
default:
|
|
failWantMap()
|
|
}
|
|
}
|
|
|
|
func isMerge(n *node) bool {
|
|
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
|
}
|