Marshal top-level nils correctly.
Merge rogpeppe's branch '001-allow-nil-marshal'.
diff --git a/README.md b/README.md
index 896687b..af07056 100644
--- a/README.md
+++ b/README.md
@@ -12,10 +12,10 @@
Compatibility
-------------
-The yaml package is almost compatible with YAML 1.1, including support for
-anchors, tags, etc. There are still a few missing bits, such as document
-merging, base-60 floats (huh?), and multi-document unmarshalling. These
-features are not hard to add, and will be introduced as necessary.
+The yaml package supports most of YAML 1.1 and 1.2, including support for
+anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
+implemented, and base-60 floats from YAML 1.1 are purposefully not
+supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
diff --git a/decode.go b/decode.go
index ba926c0..a098626 100644
--- a/decode.go
+++ b/decode.go
@@ -1,6 +1,8 @@
package yaml
import (
+ "encoding/base64"
+ "fmt"
"reflect"
"strconv"
"time"
@@ -63,7 +65,7 @@
func (p *parser) skip() {
if p.event.typ != yaml_NO_EVENT {
if p.event.typ == yaml_STREAM_END_EVENT {
- panic("Attempted to go past the end of stream. Corrupted value?")
+ fail("Attempted to go past the end of stream. Corrupted value?")
}
yaml_event_delete(&p.event)
}
@@ -89,7 +91,7 @@
} else {
msg = "Unknown problem parsing YAML content"
}
- panic(where + msg)
+ fail(where + msg)
}
func (p *parser) anchor(n *node, anchor []byte) {
@@ -114,10 +116,9 @@
// Happens when attempting to decode an empty buffer.
return nil
default:
- panic("Attempted to parse unknown event: " +
- strconv.Itoa(int(p.event.typ)))
+ panic("Attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
}
- panic("Unreachable")
+ panic("unreachable")
}
func (p *parser) node(kind int) *node {
@@ -135,8 +136,7 @@
p.skip()
n.children = append(n.children, p.parse())
if p.event.typ != yaml_DOCUMENT_END_EVENT {
- panic("Expected end of document event but got " +
- strconv.Itoa(int(p.event.typ)))
+ panic("Expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
}
p.skip()
return n
@@ -218,7 +218,7 @@
var arg interface{}
*out = reflect.ValueOf(&arg).Elem()
return func() {
- *good = setter.SetYAML(tag, arg)
+ *good = setter.SetYAML(shortTag(tag), arg)
}
}
}
@@ -226,7 +226,7 @@
for again {
again = false
setter, _ := (*out).Interface().(Setter)
- if tag != "!!null" || setter != nil {
+ if tag != yaml_NULL_TAG || setter != nil {
if pv := (*out); pv.Kind() == reflect.Ptr {
if pv.IsNil() {
*out = reflect.New(pv.Type().Elem()).Elem()
@@ -242,7 +242,7 @@
var arg interface{}
*out = reflect.ValueOf(&arg).Elem()
return func() {
- *good = setter.SetYAML(tag, arg)
+ *good = setter.SetYAML(shortTag(tag), arg)
}
}
}
@@ -279,10 +279,10 @@
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
an, ok := d.doc.anchors[n.value]
if !ok {
- panic("Unknown anchor '" + n.value + "' referenced")
+ fail("Unknown anchor '" + n.value + "' referenced")
}
if d.aliases[n.value] {
- panic("Anchor '" + n.value + "' value contains itself")
+ fail("Anchor '" + n.value + "' value contains itself")
}
d.aliases[n.value] = true
good = d.unmarshal(an, out)
@@ -290,23 +290,50 @@
return good
}
+var zeroValue reflect.Value
+
+func resetMap(out reflect.Value) {
+ for _, k := range out.MapKeys() {
+ out.SetMapIndex(k, zeroValue)
+ }
+}
+
var durationType = reflect.TypeOf(time.Duration(0))
func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
var tag string
var resolved interface{}
if n.tag == "" && !n.implicit {
- tag = "!!str"
+ 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 {
+ fail("!!binary value contains invalid base64 data")
+ }
+ resolved = string(data)
+ }
}
if set := d.setter(tag, &out, &good); set != nil {
defer set()
}
+ if resolved == nil {
+ if out.Kind() == reflect.Map && !out.CanAddr() {
+ resetMap(out)
+ } else {
+ out.Set(reflect.Zero(out.Type()))
+ }
+ good = true
+ return
+ }
switch out.Kind() {
case reflect.String:
- if resolved != nil {
+ if tag == yaml_BINARY_TAG {
+ out.SetString(resolved.(string))
+ good = true
+ } else if resolved != nil {
out.SetString(n.value)
good = true
}
@@ -380,17 +407,11 @@
good = true
}
case reflect.Ptr:
- switch resolved.(type) {
- case nil:
- out.Set(reflect.Zero(out.Type()))
+ if out.Type().Elem() == reflect.TypeOf(resolved) {
+ elem := reflect.New(out.Type().Elem())
+ elem.Elem().Set(reflect.ValueOf(resolved))
+ out.Set(elem)
good = true
- default:
- if out.Type().Elem() == reflect.TypeOf(resolved) {
- elem := reflect.New(out.Type().Elem())
- elem.Elem().Set(reflect.ValueOf(resolved))
- out.Set(elem)
- good = true
- }
}
}
return good
@@ -404,7 +425,7 @@
}
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
- if set := d.setter("!!seq", &out, &good); set != nil {
+ if set := d.setter(yaml_SEQ_TAG, &out, &good); set != nil {
defer set()
}
var iface reflect.Value
@@ -433,7 +454,7 @@
}
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
- if set := d.setter("!!map", &out, &good); set != nil {
+ if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
defer set()
}
if out.Kind() == reflect.Struct {
@@ -465,6 +486,13 @@
}
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 {
+ fail(fmt.Sprintf("invalid map key: %#v", k.Interface()))
+ }
e := reflect.New(et).Elem()
if d.unmarshal(n.children[i+1], e) {
out.SetMapIndex(k, e)
@@ -511,7 +539,7 @@
case aliasNode:
an, ok := d.doc.anchors[n.value]
if ok && an.kind != mappingNode {
- panic(wantMap)
+ fail(wantMap)
}
d.unmarshal(n, out)
case sequenceNode:
@@ -521,18 +549,18 @@
if ni.kind == aliasNode {
an, ok := d.doc.anchors[ni.value]
if ok && an.kind != mappingNode {
- panic(wantMap)
+ fail(wantMap)
}
} else if ni.kind != mappingNode {
- panic(wantMap)
+ fail(wantMap)
}
d.unmarshal(ni, out)
}
default:
- panic(wantMap)
+ fail(wantMap)
}
}
func isMerge(n *node) bool {
- return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == "!!merge" || n.tag == "tag:yaml.org,2002:merge")
+ return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
}
diff --git a/decode_test.go b/decode_test.go
index 88fa4c7..ef3d37f 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -5,6 +5,7 @@
"gopkg.in/yaml.v1"
"math"
"reflect"
+ "strings"
"time"
)
@@ -316,7 +317,10 @@
map[string]*string{"foo": new(string)},
}, {
"foo: null",
- map[string]string{},
+ map[string]string{"foo": ""},
+ }, {
+ "foo: null",
+ map[string]interface{}{"foo": nil},
},
// Ignored field
@@ -377,6 +381,24 @@
"a: <foo>",
map[string]string{"a": "<foo>"},
},
+
+ // Base 60 floats are obsolete and unsupported.
+ {
+ "a: 1:1\n",
+ map[string]string{"a": "1:1"},
+ },
+
+ // Binary data.
+ {
+ "a: !!binary gIGC\n",
+ map[string]string{"a": "\x80\x81\x82"},
+ }, {
+ "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
+ map[string]string{"a": strings.Repeat("\x90", 54)},
+ }, {
+ "a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n",
+ map[string]string{"a": strings.Repeat("\x00", 52)},
+ },
}
type inlineB struct {
@@ -424,12 +446,15 @@
var unmarshalErrorTests = []struct {
data, error string
}{
- {"v: !!float 'error'", "YAML error: Can't decode !!str 'error' as a !!float"},
+ {"v: !!float 'error'", "YAML error: cannot decode !!str `error` as a !!float"},
{"v: [A,", "YAML error: line 1: did not find expected node content"},
{"v:\n- [A,", "YAML error: line 2: did not find expected node content"},
{"a: *b\n", "YAML error: Unknown anchor 'b' referenced"},
{"a: &a\n b: *a\n", "YAML error: Anchor 'a' value contains itself"},
{"value: -", "YAML error: block sequence entries are not allowed in this context"},
+ {"a: !!binary ==", "YAML error: !!binary value contains invalid base64 data"},
+ {"{[.]}", `YAML error: invalid map key: \[\]interface \{\}\{"\."\}`},
+ {"{{.}}", `YAML error: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`},
}
func (s *S) TestUnmarshalErrors(c *C) {
@@ -624,6 +649,30 @@
}
}
+var unmarshalNullTests = []func() interface{}{
+ func() interface{} { var v interface{}; v = "v"; return &v },
+ func() interface{} { var s = "s"; return &s },
+ func() interface{} { var s = "s"; sptr := &s; return &sptr },
+ func() interface{} { var i = 1; return &i },
+ func() interface{} { var i = 1; iptr := &i; return &iptr },
+ func() interface{} { m := map[string]int{"s": 1}; return &m },
+ func() interface{} { m := map[string]int{"s": 1}; return m },
+}
+
+func (s *S) TestUnmarshalNull(c *C) {
+ for _, test := range unmarshalNullTests {
+ item := test()
+ zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface()
+ err := yaml.Unmarshal([]byte("null"), item)
+ c.Assert(err, IsNil)
+ if reflect.TypeOf(item).Kind() == reflect.Map {
+ c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface())
+ } else {
+ c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero)
+ }
+ }
+}
+
//var data []byte
//func init() {
// var err error
diff --git a/emitterc.go b/emitterc.go
index 542ffd2..9b3dc4a 100644
--- a/emitterc.go
+++ b/emitterc.go
@@ -973,8 +973,8 @@
if bytes.HasPrefix(tag, tag_directive.prefix) {
emitter.tag_data.handle = tag_directive.handle
emitter.tag_data.suffix = tag[len(tag_directive.prefix):]
+ return true
}
- return true
}
emitter.tag_data.suffix = tag
return true
@@ -1279,6 +1279,9 @@
for k := 0; k < w; k++ {
octet := value[i]
i++
+ if !put(emitter, '%') {
+ return false
+ }
c := octet >> 4
if c < 10 {
diff --git a/encode.go b/encode.go
index fb88bb3..0b9048d 100644
--- a/encode.go
+++ b/encode.go
@@ -2,8 +2,10 @@
import (
"reflect"
+ "regexp"
"sort"
"strconv"
+ "strings"
"time"
)
@@ -50,18 +52,19 @@
if msg == "" {
msg = "Unknown problem generating YAML content"
}
- panic(msg)
+ fail(msg)
}
}
func (e *encoder) marshal(tag string, in reflect.Value) {
- var value interface{}
if !in.IsValid() {
e.nilv()
return
}
+ var value interface{}
if getter, ok := in.Interface().(Getter); ok {
tag, value = getter.GetYAML()
+ tag = longTag(tag)
if value == nil {
e.nilv()
return
@@ -102,7 +105,7 @@
case reflect.Bool:
e.boolv(tag, in)
default:
- panic("Can't marshal type yet: " + in.Type().String())
+ panic("Can't marshal type: " + in.Type().String())
}
}
@@ -171,11 +174,46 @@
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()
- if rtag, _ := resolve("", s); rtag != "!!str" {
+ 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 {
+ fail("explicitly tagged !!binary data must be base64-encoded")
+ } else {
+ fail("cannot marshal invalid UTF-8 data as " + 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
}
@@ -222,9 +260,6 @@
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
implicit := tag == ""
- if !implicit {
- style = yaml_PLAIN_SCALAR_STYLE
- }
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
e.emit()
}
diff --git a/encode_test.go b/encode_test.go
index 4b1480e..c9febc2 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -2,12 +2,13 @@
import (
"fmt"
- . "gopkg.in/check.v1"
- "gopkg.in/yaml.v1"
"math"
"strconv"
"strings"
"time"
+
+ . "gopkg.in/check.v1"
+ "gopkg.in/yaml.v1"
)
var marshalIntTest = 123
@@ -90,7 +91,7 @@
"v:\n- A\n- B\n",
}, {
map[string][]string{"v": []string{"A", "B\nC"}},
- "v:\n- A\n- 'B\n\n C'\n",
+ "v:\n- A\n- |-\n B\n C\n",
}, {
map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
@@ -223,11 +224,39 @@
"a: 3s\n",
},
- // Issue #24.
+ // Issue #24: bug in map merging logic.
{
map[string]string{"a": "<foo>"},
"a: <foo>\n",
},
+
+ // Issue #34: marshal unsupported base 60 floats quoted for compatibility
+ // with old YAML 1.1 parsers.
+ {
+ map[string]string{"a": "1:1"},
+ "a: \"1:1\"\n",
+ },
+
+ // Binary data.
+ {
+ map[string]string{"a": "\x00"},
+ "a: \"\\0\"\n",
+ }, {
+ map[string]string{"a": "\x80\x81\x82"},
+ "a: !!binary gIGC\n",
+ }, {
+ map[string]string{"a": strings.Repeat("\x90", 54)},
+ "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
+ }, {
+ map[string]interface{}{"a": typeWithGetter{"!!str", "\x80\x81\x82"}},
+ "a: !!binary gIGC\n",
+ },
+
+ // Escaping of tags.
+ {
+ map[string]interface{}{"a": typeWithGetter{"foo!bar", 1}},
+ "a: !<foo%21bar> 1\n",
+ },
}
func (s *S) TestMarshal(c *C) {
@@ -241,20 +270,29 @@
var marshalErrorTests = []struct {
value interface{}
error string
-}{
- {
- &struct {
- B int
- inlineB ",inline"
- }{1, inlineB{2, inlineC{3}}},
- `Duplicated key 'b' in struct struct \{ B int; .*`,
- },
-}
+ panic string
+}{{
+ value: &struct {
+ B int
+ inlineB ",inline"
+ }{1, inlineB{2, inlineC{3}}},
+ panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
+}, {
+ value: typeWithGetter{"!!binary", "\x80"},
+ error: "YAML error: explicitly tagged !!binary data must be base64-encoded",
+}, {
+ value: typeWithGetter{"!!float", "\x80"},
+ error: `YAML error: cannot marshal invalid UTF-8 data as !!float`,
+}}
func (s *S) TestMarshalErrors(c *C) {
for _, item := range marshalErrorTests {
- _, err := yaml.Marshal(item.value)
- c.Assert(err, ErrorMatches, item.error)
+ if item.panic != "" {
+ c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
+ } else {
+ _, err := yaml.Marshal(item.value)
+ c.Assert(err, ErrorMatches, item.error)
+ }
}
}
diff --git a/resolve.go b/resolve.go
index 863c314..06c698a 100644
--- a/resolve.go
+++ b/resolve.go
@@ -1,9 +1,12 @@
package yaml
import (
+ "encoding/base64"
+ "fmt"
"math"
"strconv"
"strings"
+ "unicode/utf8"
)
// TODO: merge, timestamps, base 60 floats, omap.
@@ -33,18 +36,18 @@
tag string
l []string
}{
- {true, "!!bool", []string{"y", "Y", "yes", "Yes", "YES"}},
- {true, "!!bool", []string{"true", "True", "TRUE"}},
- {true, "!!bool", []string{"on", "On", "ON"}},
- {false, "!!bool", []string{"n", "N", "no", "No", "NO"}},
- {false, "!!bool", []string{"false", "False", "FALSE"}},
- {false, "!!bool", []string{"off", "Off", "OFF"}},
- {nil, "!!null", []string{"~", "null", "Null", "NULL"}},
- {math.NaN(), "!!float", []string{".nan", ".NaN", ".NAN"}},
- {math.Inf(+1), "!!float", []string{".inf", ".Inf", ".INF"}},
- {math.Inf(+1), "!!float", []string{"+.inf", "+.Inf", "+.INF"}},
- {math.Inf(-1), "!!float", []string{"-.inf", "-.Inf", "-.INF"}},
- {"<<", "!!merge", []string{"<<"}},
+ {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
+ {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
+ {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
+ {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
+ {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
+ {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
+ {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
+ {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
+ {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
+ {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
+ {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
+ {"<<", yaml_MERGE_TAG, []string{"<<"}},
}
m := resolveMap
@@ -58,90 +61,130 @@
const longTagPrefix = "tag:yaml.org,2002:"
func shortTag(tag string) string {
+ // TODO This can easily be made faster and produce less garbage.
if strings.HasPrefix(tag, longTagPrefix) {
return "!!" + tag[len(longTagPrefix):]
}
return tag
}
+func longTag(tag string) string {
+ if strings.HasPrefix(tag, "!!") {
+ return longTagPrefix + tag[2:]
+ }
+ return tag
+}
+
func resolvableTag(tag string) bool {
switch tag {
- case "", "!!str", "!!bool", "!!int", "!!float", "!!null":
+ case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG:
return true
}
return false
}
func resolve(tag string, in string) (rtag string, out interface{}) {
- tag = shortTag(tag)
if !resolvableTag(tag) {
return tag, in
}
defer func() {
- if tag != "" && tag != rtag {
- panic("Can't decode " + rtag + " '" + in + "' as a " + tag)
+ switch tag {
+ case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
+ return
}
+ fail(fmt.Sprintf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)))
}()
- if in == "" {
- return "!!null", nil
+ // Any data is accepted as a !!str or !!binary.
+ // Otherwise, the prefix is enough of a hint about what it might be.
+ hint := byte('N')
+ if in != "" {
+ hint = resolveTable[in[0]]
}
-
- c := resolveTable[in[0]]
- if c == 0 {
- // It's a string for sure. Nothing to do.
- return "!!str", in
- }
-
- // Handle things we can lookup in a map.
- if item, ok := resolveMap[in]; ok {
- return item.tag, item.value
- }
-
- switch c {
- case 'M':
- // We've already checked the map above.
-
- case '.':
- // Not in the map, so maybe a normal float.
- floatv, err := strconv.ParseFloat(in, 64)
- if err == nil {
- return "!!float", floatv
+ if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
+ // Handle things we can lookup in a map.
+ if item, ok := resolveMap[in]; ok {
+ return item.tag, item.value
}
- // XXX Handle base 60 floats here (WTF!)
- case 'D', 'S':
- // Int, float, or timestamp.
- plain := strings.Replace(in, "_", "", -1)
- intv, err := strconv.ParseInt(plain, 0, 64)
- if err == nil {
- if intv == int64(int(intv)) {
- return "!!int", int(intv)
- } else {
- return "!!int", intv
- }
- }
- floatv, err := strconv.ParseFloat(plain, 64)
- if err == nil {
- return "!!float", floatv
- }
- if strings.HasPrefix(plain, "0b") {
- intv, err := strconv.ParseInt(plain[2:], 2, 64)
+ // Base 60 floats are a bad idea, were dropped in YAML 1.2, and
+ // are purposefully unsupported here. They're still quoted on
+ // the way out for compatibility with other parser, though.
+
+ switch hint {
+ case 'M':
+ // We've already checked the map above.
+
+ case '.':
+ // Not in the map, so maybe a normal float.
+ floatv, err := strconv.ParseFloat(in, 64)
if err == nil {
- return "!!int", int(intv)
+ return yaml_FLOAT_TAG, floatv
}
- } else if strings.HasPrefix(plain, "-0b") {
- intv, err := strconv.ParseInt(plain[3:], 2, 64)
- if err == nil {
- return "!!int", -int(intv)
- }
- }
- // XXX Handle timestamps here.
- default:
- panic("resolveTable item not yet handled: " +
- string([]byte{c}) + " (with " + in + ")")
+ case 'D', 'S':
+ // Int, float, or timestamp.
+ plain := strings.Replace(in, "_", "", -1)
+ intv, err := strconv.ParseInt(plain, 0, 64)
+ if err == nil {
+ if intv == int64(int(intv)) {
+ return yaml_INT_TAG, int(intv)
+ } else {
+ return yaml_INT_TAG, intv
+ }
+ }
+ floatv, err := strconv.ParseFloat(plain, 64)
+ if err == nil {
+ return yaml_FLOAT_TAG, floatv
+ }
+ if strings.HasPrefix(plain, "0b") {
+ intv, err := strconv.ParseInt(plain[2:], 2, 64)
+ if err == nil {
+ return yaml_INT_TAG, int(intv)
+ }
+ } else if strings.HasPrefix(plain, "-0b") {
+ intv, err := strconv.ParseInt(plain[3:], 2, 64)
+ if err == nil {
+ return yaml_INT_TAG, -int(intv)
+ }
+ }
+ // XXX Handle timestamps here.
+
+ default:
+ panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
+ }
}
- return "!!str", in
+ if tag == yaml_BINARY_TAG {
+ return yaml_BINARY_TAG, in
+ }
+ if utf8.ValidString(in) {
+ return yaml_STR_TAG, in
+ }
+ return yaml_BINARY_TAG, encodeBase64(in)
+}
+
+// encodeBase64 encodes s as base64 that is broken up into multiple lines
+// as appropriate for the resulting length.
+func encodeBase64(s string) string {
+ const lineLen = 70
+ encLen := base64.StdEncoding.EncodedLen(len(s))
+ lines := encLen/lineLen + 1
+ buf := make([]byte, encLen*2+lines)
+ in := buf[0:encLen]
+ out := buf[encLen:]
+ base64.StdEncoding.Encode(in, []byte(s))
+ k := 0
+ for i := 0; i < len(in); i += lineLen {
+ j := i + lineLen
+ if j > len(in) {
+ j = len(in)
+ }
+ k += copy(out[k:], in[i:j])
+ if lines > 1 {
+ out[k] = '\n'
+ k++
+ }
+ }
+ return string(out[:k])
}
diff --git a/yaml.go b/yaml.go
index 44b0cc6..903b13b 100644
--- a/yaml.go
+++ b/yaml.go
@@ -10,23 +10,20 @@
"errors"
"fmt"
"reflect"
- "runtime"
"strings"
"sync"
)
+type yamlError string
+
+func fail(msg string) {
+ panic(yamlError(msg))
+}
+
func handleErr(err *error) {
if r := recover(); r != nil {
- if _, ok := r.(runtime.Error); ok {
- panic(r)
- } else if _, ok := r.(*reflect.ValueError); ok {
- panic(r)
- } else if _, ok := r.(externalPanic); ok {
- panic(r)
- } else if s, ok := r.(string); ok {
- *err = errors.New("YAML error: " + s)
- } else if e, ok := r.(error); ok {
- *err = e
+ if e, ok := r.(yamlError); ok {
+ *err = errors.New("YAML error: " + string(e))
} else {
panic(r)
}
@@ -91,7 +88,11 @@
defer p.destroy()
node := p.parse()
if node != nil {
- d.unmarshal(node, reflect.ValueOf(out))
+ v := reflect.ValueOf(out)
+ if v.Kind() == reflect.Ptr && !v.IsNil() {
+ v = v.Elem()
+ }
+ d.unmarshal(node, v)
}
return nil
}
@@ -174,12 +175,6 @@
var structMap = make(map[reflect.Type]*structInfo)
var fieldMapMutex sync.RWMutex
-type externalPanic string
-
-func (e externalPanic) String() string {
- return string(e)
-}
-
func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldMapMutex.RLock()
sinfo, found := structMap[st]
@@ -220,8 +215,7 @@
case "inline":
inline = true
default:
- msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
- panic(externalPanic(msg))
+ return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
}
}
tag = fields[0]
@@ -229,6 +223,7 @@
if inline {
switch field.Type.Kind() {
+ // TODO: Implement support for inline maps.
//case reflect.Map:
// if inlineMap >= 0 {
// return nil, errors.New("Multiple ,inline maps in struct " + st.String())
@@ -256,8 +251,8 @@
fieldsList = append(fieldsList, finfo)
}
default:
- //panic("Option ,inline needs a struct value or map field")
- panic("Option ,inline needs a struct value field")
+ //return nil, errors.New("Option ,inline needs a struct value or map field")
+ return nil, errors.New("Option ,inline needs a struct value field")
}
continue
}
diff --git a/yamlh.go b/yamlh.go
index 6624d6c..4b020b1 100644
--- a/yamlh.go
+++ b/yamlh.go
@@ -294,6 +294,10 @@
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
+ // Not in original libyaml.
+ yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
+ yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
+
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.