Skip to content

Commit

Permalink
Encode strings with YAML 1.1 boolean values with quotes
Browse files Browse the repository at this point in the history
To allow better compatibility with YAML 1.1 parsers, when outputting a
string that is equal to one of the YAML 1.1 boolean values ("yes", "no",
"on", "off", etc), enclose the string in quotes. The string will then
parse correctly by either YAML 1.1 or YAML 1.2 parsers.

This change does not affect decoding: Bare `yes` and `no` will still be
decoded as strings.

See: go-yaml#214
  • Loading branch information
dsharp-pivotal committed Feb 5, 2020
1 parent a6ecf24 commit 20d531d
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
22 changes: 21 additions & 1 deletion encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,23 @@ func isBase60Float(s string) (result bool) {
// 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_]*)?$`)

// isYaml11Bool returns whether s is a boolean value as defined in YAML 1.1.
//
// The extra boolean values in YAML 1.1 are 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 isYaml11Bool(s string) (result bool) {
// Fast path.
if s == "" {
return false
}
return yaml11Bool.MatchString(s)
}

// From https://yaml.org/type/bool.html, except "true" and "false" are removed
// since those are supported in YAML 1.2
var yaml11Bool = regexp.MustCompile(`^(y|Y|yes|Yes|YES|n|N|no|No|NO|True|TRUE|False|FALSE|on|On|ON|off|Off|OFF)$`)

func (e *encoder) stringv(tag string, in reflect.Value) {
var style yaml_scalar_style_t
s := in.String()
Expand All @@ -319,7 +336,7 @@ func (e *encoder) stringv(tag string, in reflect.Value) {
// tag when encoded unquoted. If it doesn't,
// there's no need to quote it.
rtag, _ := resolve("", s)
canUsePlain = rtag == strTag && !isBase60Float(s)
canUsePlain = rtag == strTag && !isBase60Float(s) && !isYaml11Bool(s)
}
// Note: it's possible for user code to emit invalid YAML
// if they explicitly specify a tag and a string containing
Expand Down Expand Up @@ -425,6 +442,9 @@ func (e *encoder) node(node *Node, tail string) {
tag = ""
forceQuoting = true
}
if stag == strTag && isYaml11Bool(node.Value) {
forceQuoting = true
}
}
} else {
switch node.Kind {
Expand Down
39 changes: 36 additions & 3 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ import (
"bytes"
"fmt"
"math"
"net"
"os"
"strconv"
"strings"
"time"

"net"
"os"

. "gopkg.in/check.v1"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -422,6 +421,40 @@ var marshalTests = []struct {
map[string]string{"a": "\tB\n\tC\n"},
"a: |\n \tB\n \tC\n",
},

// Strings that are equal to the YAML 1.1 boolean values should be quoted
// so that YAML 1.1 parsers will parse them as booleans.
{
map[string]string{"v": "yes"},
"v: \"yes\"\n",
}, {
map[string]interface{}{"v": "no"},
"v: \"no\"\n",
}, {
&yaml.Node{Value: `yes`, Tag: "!!str", Kind: yaml.ScalarNode},
"\"yes\"\n",
}, {
&yaml.Node{Value: `no`, Tag: "!!str", Kind: yaml.ScalarNode},
"\"no\"\n",
}, {
&yaml.Node{Value: `true`, Tag: "!!str", Kind: yaml.ScalarNode},
"\"true\"\n",
}, {
&yaml.Node{Value: `false`, Tag: "!!str", Kind: yaml.ScalarNode},
"\"false\"\n",
}, {
&yaml.Node{Value: `yes`, Tag: "", Kind: yaml.ScalarNode},
"yes\n",
}, {
&yaml.Node{Value: `no`, Tag: "", Kind: yaml.ScalarNode},
"no\n",
}, {
&yaml.Node{Value: `true`, Tag: "", Kind: yaml.ScalarNode},
"true\n",
}, {
&yaml.Node{Value: `false`, Tag: "", Kind: yaml.ScalarNode},
"false\n",
},
}

func (s *S) TestMarshal(c *C) {
Expand Down

0 comments on commit 20d531d

Please sign in to comment.