Repository: goccy/go-yaml
Pull request #82 (2020-02-16 07:27:17)

Add support for multi-line string encoding
Hi @goccy !

I added support for multi-line string encoding similar to go-yaml/yaml.

**go-yaml/yaml encoding result:**

https://play.golang.org/p/mEGR5B3JpNY

**github.com/goccy/go-yaml v1.3.0 encoding result:**

https://play.golang.org/p/EDXASMpmbiV

Thanks.



Patch:
diff --git a/ast/ast.go b/ast/ast.go
index d472d14..6d6fd8d 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -420,7 +420,7 @@ func (n *StringNode) GetValue() interface{} {
 	return n.Value
 }
 
-// String string value to text with quote if required
+// String string value to text with quote or literal header if required
 func (n *StringNode) String() string {
 	switch n.Token.Type {
 	case token.SingleQuoteType:
@@ -428,6 +428,21 @@ func (n *StringNode) String() string {
 	case token.DoubleQuoteType:
 		return fmt.Sprintf(`"%s"`, n.Value)
 	}
+
+	lbc := token.DetectLineBreakCharacter(n.Value)
+	if strings.Contains(n.Value, lbc) {
+		// This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same.
+		// It works mostly, but inconsistencies occur if line break characters are mixed.
+		header := token.LiteralBlockHeader(n.Value)
+		space := strings.Repeat(" ", n.Token.Position.Column-1)
+		values := []string{}
+		for _, v := range strings.Split(n.Value, lbc) {
+			values = append(values, fmt.Sprintf("%s  %s", space, v))
+		}
+		block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s  %s", lbc, space)), fmt.Sprintf("  %s", space))
+		return fmt.Sprintf("%s%s%s", header, lbc, block)
+	}
+
 	return n.Value
 }
 
diff --git a/token/token.go b/token/token.go
index bb37dee..fa5a97c 100644
--- a/token/token.go
+++ b/token/token.go
@@ -585,6 +585,22 @@ func IsNeedQuoted(value string) bool {
 	return false
 }
 
+// LiteralBlockHeader detect literal block scalar header
+func LiteralBlockHeader(value string) string {
+	lbc := DetectLineBreakCharacter(value)
+
+	switch {
+	case !strings.Contains(value, lbc):
+		return ""
+	case strings.HasSuffix(value, fmt.Sprintf("%s%s", lbc, lbc)):
+		return "|+"
+	case strings.HasSuffix(value, lbc):
+		return "|"
+	default:
+		return "|-"
+	}
+}
+
 // New create reserved keyword token or number token and other string token
 func New(value string, org string, pos *Position) *Token {
 	fn := reservedKeywordMap[value]
@@ -957,3 +973,18 @@ func DocumentEnd(pos *Position) *Token {
 		Position:      pos,
 	}
 }
+
+// DetectLineBreakCharacter detect line break character in only one inside scalar content scope.
+func DetectLineBreakCharacter(src string) string {
+	nc := strings.Count(src, "\n")
+	rc := strings.Count(src, "\r")
+	rnc := strings.Count(src, "\r\n")
+	switch {
+	case nc == rnc && rc == rnc:
+		return "\r\n"
+	case rc > nc:
+		return "\r"
+	default:
+		return "\n"
+	}
+}


Test patch:
diff --git a/encode_test.go b/encode_test.go
index 5a40d26..727b31d 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -105,6 +105,30 @@ func TestEncoder(t *testing.T) {
 			"hello: world\n",
 			map[string]string{"hello": "world"},
 		},
+		{
+			"hello: |\n  hello\n  world\n",
+			map[string]string{"hello": "hello\nworld\n"},
+		},
+		{
+			"hello: |-\n  hello\n  world\n",
+			map[string]string{"hello": "hello\nworld"},
+		},
+		{
+			"hello: |+\n  hello\n  world\n\n",
+			map[string]string{"hello": "hello\nworld\n\n"},
+		},
+		{
+			"hello:\n  hello: |\n    hello\n    world\n",
+			map[string]map[string]string{"hello": {"hello": "hello\nworld\n"}},
+		},
+		{
+			"hello: |\r  hello\r  world\n",
+			map[string]string{"hello": "hello\rworld\r"},
+		},
+		{
+			"hello: |\r\n  hello\r\n  world\n",
+			map[string]string{"hello": "hello\r\nworld\r\n"},
+		},
 		{
 			"v:\n- A\n- 1\n- B:\n  - 2\n  - 3\n",
 			map[string]interface{}{
@@ -774,7 +798,8 @@ type FastMarshaler struct {
 type TextMarshaler int64
 type TextMarshalerContainer struct {
 	Field TextMarshaler `yaml:"field"`
-	}
+}
+
 func (v SlowMarshaler) MarshalYAML() ([]byte, error) {
 	var buf bytes.Buffer
 	buf.WriteString("tags:\n")