Merge pull request #313 from rogpeppe/020-quote-merge-key
quote "<<" keys on output
diff --git a/decode.go b/decode.go
index c8eac16..8a3ed78 100644
--- a/decode.go
+++ b/decode.go
@@ -234,6 +234,7 @@
ifaceType = defaultMapType.Elem()
timeType = reflect.TypeOf(time.Time{})
ptrTimeType = reflect.TypeOf(&time.Time{})
+ mergeTagType = reflect.TypeOf(MergeTag)
)
func newDecoder(strict bool) *decoder {
@@ -760,5 +761,11 @@
}
func isMerge(n *node) bool {
- return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
+ // Quick test so we avoid the overhead of calling resolve
+ // unless we're pretty sure it can be a merge node.
+ if n.kind != scalarNode || n.value != "<<" {
+ return false
+ }
+ _, v := resolve(n.tag, n.value)
+ return v == MergeTag
}
diff --git a/decode_test.go b/decode_test.go
index 7e1e8b3..40f60d5 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -412,6 +412,11 @@
"v: ! test",
map[string]interface{}{"v": "test"},
},
+ // Non-specific tag with resolvable item.
+ {
+ "v: ! 99",
+ map[string]interface{}{"v": "99"},
+ },
// Anchors and aliases.
{
@@ -1080,6 +1085,7 @@
# Inlined map in sequence
<< : [ *CENTER, {"r": 10} ]
label: center/big
+
`
func (s *S) TestMerge(c *C) {
diff --git a/encode.go b/encode.go
index 1e730ef..249aeee 100644
--- a/encode.go
+++ b/encode.go
@@ -126,9 +126,12 @@
e.marshal(tag, in.Elem())
}
case reflect.Struct:
- if in.Type() == timeType {
+ switch in.Type() {
+ case timeType:
e.timev(tag, in)
- } else {
+ case mergeTagType:
+ e.mergeTagv(tag)
+ default:
e.structv(tag, in)
}
case reflect.Slice:
@@ -334,6 +337,10 @@
e.emitScalar(t.Format(time.RFC3339Nano), "", tag, yaml_PLAIN_SCALAR_STYLE)
}
+func (e *encoder) mergeTagv(tag string) {
+ e.emitScalar("<<", "", tag, yaml_PLAIN_SCALAR_STYLE)
+}
+
func (e *encoder) floatv(tag string, in reflect.Value) {
s := strconv.FormatFloat(in.Float(), 'g', -1, 64)
switch s {
diff --git a/encode_test.go b/encode_test.go
index 2e78411..47012a2 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -356,6 +356,17 @@
map[string]string{"a": "你好 #comment"},
"a: '你好 #comment'\n",
},
+
+ // << should be quoted so that it's not treated as a merge key.
+ {
+ map[string]string{"<<": "x"},
+ "\"<<\": x\n",
+ },
+ // Explicit merge tag key is left unquoted.
+ {
+ map[interface{}]interface{}{yaml.MergeTag: map[string]string{"a": "b"}},
+ "<<:\n a: b\n",
+ },
}
func (s *S) TestMarshal(c *C) {
diff --git a/resolve.go b/resolve.go
index ea90bd5..086c9f4 100644
--- a/resolve.go
+++ b/resolve.go
@@ -14,20 +14,28 @@
tag string
}
+// MergeTag is the value used for a merge tag (!!merge "<<").
+// When marshaled as a map key, the << will not be quoted,
+// which makes it possible to include merge tags explicitly
+// when encoding YAML values.
+var MergeTag = mergeTag{}
+
+type mergeTag struct{}
+
var resolveTable = make([]byte, 256)
var resolveMap = make(map[string]resolveMapItem)
func init() {
t := resolveTable
- t[int('+')] = 'S' // Sign
- t[int('-')] = 'S'
+ t['+'] = 'S' // Sign
+ t['-'] = 'S'
for _, c := range "0123456789" {
- t[int(c)] = 'D' // Digit
+ t[c] = 'D' // Digit
}
- for _, c := range "yYnNtTfFoO~" {
- t[int(c)] = 'M' // In map
+ for _, c := range "yYnNtTfFoO~<" {
+ t[c] = 'M' // In resolveMap
}
- t[int('.')] = '.' // Float (potentially in map)
+ t['.'] = '.' // Float (potentially in map)
var resolveMapList = []struct {
v interface{}
@@ -45,7 +53,7 @@
{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{"<<"}},
+ {MergeTag, yaml_MERGE_TAG, []string{"<<"}},
}
m := resolveMap
@@ -75,7 +83,7 @@
func resolvableTag(tag string) bool {
switch tag {
- case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
+ case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG, yaml_MERGE_TAG:
return true
}
return false