For 3rd parties building binary packages, and for build consistency in general, it is very helpful to have the same set of dependencies at any time the product is built. See [tools/godep](https://github.com/tools/godep) for further details.
307 lines
7.3 KiB
Go
307 lines
7.3 KiB
Go
package yaml
|
|
|
|
import (
|
|
"encoding"
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type encoder struct {
|
|
emitter yaml_emitter_t
|
|
event yaml_event_t
|
|
out []byte
|
|
flow bool
|
|
}
|
|
|
|
func newEncoder() (e *encoder) {
|
|
e = &encoder{}
|
|
e.must(yaml_emitter_initialize(&e.emitter))
|
|
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
|
yaml_emitter_set_unicode(&e.emitter, true)
|
|
e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING))
|
|
e.emit()
|
|
e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true))
|
|
e.emit()
|
|
return e
|
|
}
|
|
|
|
func (e *encoder) finish() {
|
|
e.must(yaml_document_end_event_initialize(&e.event, true))
|
|
e.emit()
|
|
e.emitter.open_ended = false
|
|
e.must(yaml_stream_end_event_initialize(&e.event))
|
|
e.emit()
|
|
}
|
|
|
|
func (e *encoder) destroy() {
|
|
yaml_emitter_delete(&e.emitter)
|
|
}
|
|
|
|
func (e *encoder) emit() {
|
|
// This will internally delete the e.event value.
|
|
if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT {
|
|
e.must(false)
|
|
}
|
|
}
|
|
|
|
func (e *encoder) must(ok bool) {
|
|
if !ok {
|
|
msg := e.emitter.problem
|
|
if msg == "" {
|
|
msg = "unknown problem generating YAML content"
|
|
}
|
|
failf("%s", msg)
|
|
}
|
|
}
|
|
|
|
func (e *encoder) marshal(tag string, in reflect.Value) {
|
|
if !in.IsValid() {
|
|
e.nilv()
|
|
return
|
|
}
|
|
iface := in.Interface()
|
|
if m, ok := iface.(Marshaler); ok {
|
|
v, err := m.MarshalYAML()
|
|
if err != nil {
|
|
fail(err)
|
|
}
|
|
if v == nil {
|
|
e.nilv()
|
|
return
|
|
}
|
|
in = reflect.ValueOf(v)
|
|
} else if m, ok := iface.(encoding.TextMarshaler); ok {
|
|
text, err := m.MarshalText()
|
|
if err != nil {
|
|
fail(err)
|
|
}
|
|
in = reflect.ValueOf(string(text))
|
|
}
|
|
switch in.Kind() {
|
|
case reflect.Interface:
|
|
if in.IsNil() {
|
|
e.nilv()
|
|
} else {
|
|
e.marshal(tag, in.Elem())
|
|
}
|
|
case reflect.Map:
|
|
e.mapv(tag, in)
|
|
case reflect.Ptr:
|
|
if in.IsNil() {
|
|
e.nilv()
|
|
} else {
|
|
e.marshal(tag, in.Elem())
|
|
}
|
|
case reflect.Struct:
|
|
e.structv(tag, in)
|
|
case reflect.Slice:
|
|
if in.Type().Elem() == mapItemType {
|
|
e.itemsv(tag, in)
|
|
} else {
|
|
e.slicev(tag, in)
|
|
}
|
|
case reflect.String:
|
|
e.stringv(tag, in)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if in.Type() == durationType {
|
|
e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
|
|
} else {
|
|
e.intv(tag, in)
|
|
}
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
e.uintv(tag, in)
|
|
case reflect.Float32, reflect.Float64:
|
|
e.floatv(tag, in)
|
|
case reflect.Bool:
|
|
e.boolv(tag, in)
|
|
default:
|
|
panic("cannot marshal type: " + in.Type().String())
|
|
}
|
|
}
|
|
|
|
func (e *encoder) mapv(tag string, in reflect.Value) {
|
|
e.mappingv(tag, func() {
|
|
keys := keyList(in.MapKeys())
|
|
sort.Sort(keys)
|
|
for _, k := range keys {
|
|
e.marshal("", k)
|
|
e.marshal("", in.MapIndex(k))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (e *encoder) itemsv(tag string, in reflect.Value) {
|
|
e.mappingv(tag, func() {
|
|
slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
|
|
for _, item := range slice {
|
|
e.marshal("", reflect.ValueOf(item.Key))
|
|
e.marshal("", reflect.ValueOf(item.Value))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (e *encoder) structv(tag string, in reflect.Value) {
|
|
sinfo, err := getStructInfo(in.Type())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
e.mappingv(tag, func() {
|
|
for _, info := range sinfo.FieldsList {
|
|
var value reflect.Value
|
|
if info.Inline == nil {
|
|
value = in.Field(info.Num)
|
|
} else {
|
|
value = in.FieldByIndex(info.Inline)
|
|
}
|
|
if info.OmitEmpty && isZero(value) {
|
|
continue
|
|
}
|
|
e.marshal("", reflect.ValueOf(info.Key))
|
|
e.flow = info.Flow
|
|
e.marshal("", value)
|
|
}
|
|
if sinfo.InlineMap >= 0 {
|
|
m := in.Field(sinfo.InlineMap)
|
|
if m.Len() > 0 {
|
|
e.flow = false
|
|
keys := keyList(m.MapKeys())
|
|
sort.Sort(keys)
|
|
for _, k := range keys {
|
|
if _, found := sinfo.FieldsMap[k.String()]; found {
|
|
panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
|
|
}
|
|
e.marshal("", k)
|
|
e.flow = false
|
|
e.marshal("", m.MapIndex(k))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (e *encoder) mappingv(tag string, f func()) {
|
|
implicit := tag == ""
|
|
style := yaml_BLOCK_MAPPING_STYLE
|
|
if e.flow {
|
|
e.flow = false
|
|
style = yaml_FLOW_MAPPING_STYLE
|
|
}
|
|
e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
|
e.emit()
|
|
f()
|
|
e.must(yaml_mapping_end_event_initialize(&e.event))
|
|
e.emit()
|
|
}
|
|
|
|
func (e *encoder) slicev(tag string, in reflect.Value) {
|
|
implicit := tag == ""
|
|
style := yaml_BLOCK_SEQUENCE_STYLE
|
|
if e.flow {
|
|
e.flow = false
|
|
style = yaml_FLOW_SEQUENCE_STYLE
|
|
}
|
|
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
|
e.emit()
|
|
n := in.Len()
|
|
for i := 0; i < n; i++ {
|
|
e.marshal("", in.Index(i))
|
|
}
|
|
e.must(yaml_sequence_end_event_initialize(&e.event))
|
|
e.emit()
|
|
}
|
|
|
|
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
|
//
|
|
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
|
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
|
// the time being for compatibility with other parsers.
|
|
func isBase60Float(s string) (result bool) {
|
|
// Fast path.
|
|
if s == "" {
|
|
return false
|
|
}
|
|
c := s[0]
|
|
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
|
return false
|
|
}
|
|
// Do the full match.
|
|
return base60float.MatchString(s)
|
|
}
|
|
|
|
// From http://yaml.org/type/float.html, except the regular expression there
|
|
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
|
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
|
|
|
func (e *encoder) stringv(tag string, in reflect.Value) {
|
|
var style yaml_scalar_style_t
|
|
s := in.String()
|
|
rtag, rs := resolve("", s)
|
|
if rtag == yaml_BINARY_TAG {
|
|
if tag == "" || tag == yaml_STR_TAG {
|
|
tag = rtag
|
|
s = rs.(string)
|
|
} else if tag == yaml_BINARY_TAG {
|
|
failf("explicitly tagged !!binary data must be base64-encoded")
|
|
} else {
|
|
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
|
}
|
|
}
|
|
if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {
|
|
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
|
} else if strings.Contains(s, "\n") {
|
|
style = yaml_LITERAL_SCALAR_STYLE
|
|
} else {
|
|
style = yaml_PLAIN_SCALAR_STYLE
|
|
}
|
|
e.emitScalar(s, "", tag, style)
|
|
}
|
|
|
|
func (e *encoder) boolv(tag string, in reflect.Value) {
|
|
var s string
|
|
if in.Bool() {
|
|
s = "true"
|
|
} else {
|
|
s = "false"
|
|
}
|
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
|
}
|
|
|
|
func (e *encoder) intv(tag string, in reflect.Value) {
|
|
s := strconv.FormatInt(in.Int(), 10)
|
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
|
}
|
|
|
|
func (e *encoder) uintv(tag string, in reflect.Value) {
|
|
s := strconv.FormatUint(in.Uint(), 10)
|
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
|
}
|
|
|
|
func (e *encoder) floatv(tag string, in reflect.Value) {
|
|
// FIXME: Handle 64 bits here.
|
|
s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32)
|
|
switch s {
|
|
case "+Inf":
|
|
s = ".inf"
|
|
case "-Inf":
|
|
s = "-.inf"
|
|
case "NaN":
|
|
s = ".nan"
|
|
}
|
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
|
}
|
|
|
|
func (e *encoder) nilv() {
|
|
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
|
|
}
|
|
|
|
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
|
implicit := tag == ""
|
|
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
|
e.emit()
|
|
}
|