From 008f9b5bff01a86b8d9a0f5475c185c5de2a7a5e Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 17:20:32 +0300 Subject: [PATCH 1/6] fix: emit break after folded scalar start --- emitterc.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/emitterc.go b/emitterc.go index 761aafb..1bee3b5 100644 --- a/emitterc.go +++ b/emitterc.go @@ -1926,10 +1926,15 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo if !yaml_emitter_write_block_scalar_hints(emitter, value) { return false } + no_comment := len(emitter.line_comment) == 0 if !yaml_emitter_process_line_comment(emitter) { return false } - + if no_comment { + if !put_break(emitter) { + return false + } + } // emitter.indention = true emitter.whitespace = true From b2afb60e85610bfb34d483bae09e88b725d0c9b9 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 17:48:51 +0300 Subject: [PATCH 2/6] fix: folded scalar newline logic Implement line breaking correctly. Put double newline, but only once and only if there is no additional indentation on next line. --- emitterc.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/emitterc.go b/emitterc.go index 1bee3b5..37eb11c 100644 --- a/emitterc.go +++ b/emitterc.go @@ -1942,18 +1942,12 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo leading_spaces := true for i := 0; i < len(value); { if is_break(value, i) { - if !breaks && !leading_spaces && value[i] == '\n' { - k := 0 - for is_break(value, k) { + if !breaks && !leading_spaces { + k := i + for k < len(value) && is_break(value, k) { k += width(value[k]) } - // FIXME(tdakkota): hacky, probably there is a better way to do this - // - // Do not break the line if the next line is additionally indented. - // - // It leads to double line breaking. - next_line_more_indent := bytes.HasPrefix(value[i+1:], []byte{' '}) - if !is_blankz(value, k) && !next_line_more_indent { + if k < len(value) && !is_blank(value, k) { if !put_break(emitter) { return false } From 69d363be3133edf4687e452834d4d57ff9b08632 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 17:49:18 +0300 Subject: [PATCH 3/6] feat: implement `fmt.Stringer` for `Style` --- node.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/node.go b/node.go index 4f284bc..1d7c019 100644 --- a/node.go +++ b/node.go @@ -1,6 +1,7 @@ package yaml import ( + "fmt" "strings" "unicode/utf8" ) @@ -28,6 +29,28 @@ const ( FlowStyle ) +// String implements fmt.Stringer. +func (s Style) String() string { + switch s { + case 0: + return "Undefined" + case TaggedStyle: + return "Tagged" + case DoubleQuotedStyle: + return "DoubleQuoted" + case SingleQuotedStyle: + return "SingleQuoted" + case LiteralStyle: + return "Literal" + case FoldedStyle: + return "Folded" + case FlowStyle: + return "Flow" + default: + return fmt.Sprintf("Style(%d)", s) + } +} + // Node represents an element in the YAML document hierarchy. While documents // are typically encoded and decoded into higher level types, such as structs // and maps, Node is an intermediate representation that allows detailed From 18cc3de96cbe723473396870534e85c173a74880 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 17:49:50 +0300 Subject: [PATCH 4/6] test: try to encode string in different styles --- encode_test.go | 149 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 45 deletions(-) diff --git a/encode_test.go b/encode_test.go index 3b3f360..36fc80d 100644 --- a/encode_test.go +++ b/encode_test.go @@ -20,6 +20,7 @@ import ( "fmt" "math" "net" + "reflect" "strconv" "strings" "testing" @@ -928,63 +929,121 @@ func newTime(t time.Time) *time.Time { } func testEncodeDecodeString(t *testing.T, input string) { - t.Run("Scalar", func(t *testing.T) { - defer func() { - t.Logf("Input: %q", input) - }() - a := require.New(t) - - data, err := yaml.Marshal(input) - a.NoError(err) + t.Run("String", func(t *testing.T) { + tests := []struct { + name string + input interface{} + }{ + { + "Scalar", + input, + }, + { + "Mapping", + map[string]string{"foo": input}, + }, + { + "Sequence", + []string{input}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + defer func() { + t.Logf("Input: %#v", tt.input) + }() + a := require.New(t) - defer func() { - t.Logf("Marshal: %q", data) - }() + data, err := yaml.Marshal(tt.input) + a.NoError(err) - var output string - a.NoError(yaml.Unmarshal(data, &output)) - a.Equal(input, output) - }) - t.Run("Mapping", func(t *testing.T) { - defer func() { - t.Logf("Input: %q", input) - }() - a := require.New(t) - - input := map[string]string{"foo": input} - data, err := yaml.Marshal(input) - a.NoError(err) + defer func() { + t.Logf("Marshal: %q", data) + }() - defer func() { - t.Logf("Marshal: %q", data) - }() + typ := reflect.TypeOf(tt.input) + target := reflect.New(typ) + a.NoError(yaml.Unmarshal(data, target.Interface())) - var output map[string]string - a.NoError(yaml.Unmarshal(data, &output)) - a.Equal(input, output) + output := target.Elem().Interface() + a.Equal(tt.input, output) + }) + } }) - t.Run("Sequence", func(t *testing.T) { - defer func() { - t.Logf("Input: %q", input) - }() - a := require.New(t) - - input := []string{input} - data, err := yaml.Marshal(input) - a.NoError(err) + t.Run("Node", func(t *testing.T) { + for _, style := range []yaml.Style{ + 0, + yaml.DoubleQuotedStyle, + yaml.SingleQuotedStyle, + yaml.LiteralStyle, + yaml.FoldedStyle, + } { + tt := struct { + input yaml.Node + }{ + input: yaml.Node{ + Kind: yaml.ScalarNode, + Style: style, + Value: input, + }, + } + t.Run(fmt.Sprintf("%sStyle", style), func(t *testing.T) { + defer func() { + t.Logf("Input: %#v", tt.input) + }() + a := require.New(t) - defer func() { - t.Logf("Marshal: %q", data) - }() + data, err := yaml.Marshal(tt.input) + a.NoError(err) - var output []string - a.NoError(yaml.Unmarshal(data, &output)) - a.Equal(input, output) + defer func() { + t.Logf("Marshal: %q", data) + }() + + var output yaml.Node + a.NoError(yaml.Unmarshal(data, &output)) + if output.Kind == yaml.DocumentNode { + output = *output.Content[0] + } + a.Equal(tt.input.Value, output.Value) + }) + } }) } func TestEncodeDecodeString(t *testing.T) { for i, tt := range []string{ + "", + " ", + "\t", + "\n", + "\r", + "\u0085", + "\u2028", + "\u2029", + "\"", + ":", + "?", + "#", + + "foo", + "foo\n", + "\nfoo", + "\n\nfoo", + "\n\tfoo", + "\tfoo", + " foo", + "# foo", + "\n# foo", + "foo\"", + "- foo\n - bar\n", + "\n- foo\n - bar\n", + + "0\n0", + "0\n\n0", + "0\n\n\n0", + "\t\ndetected\n", "\tB\n\tC\n", From f8f8270c81cc2795f68c79ada027aa3cecef35b4 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 20:59:12 +0300 Subject: [PATCH 5/6] fix: add guard buffer length check --- emitterc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emitterc.go b/emitterc.go index 37eb11c..5498e26 100644 --- a/emitterc.go +++ b/emitterc.go @@ -1835,7 +1835,7 @@ func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []by } func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { - if is_space(value, 0) || is_break(value, 0) { + if len(value) > 0 && (is_space(value, 0) || is_break(value, 0)) { indent_hint := []byte{'0' + byte(emitter.best_indent)} if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { return false From 12988942b3a75f5207a83e59f10ae2f672555c1a Mon Sep 17 00:00:00 2001 From: tdakkota Date: Thu, 11 Aug 2022 21:09:09 +0300 Subject: [PATCH 6/6] test: add `Style.String` test --- node_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/node_test.go b/node_test.go index 1a170ca..6502c3f 100644 --- a/node_test.go +++ b/node_test.go @@ -2884,3 +2884,22 @@ func fprintCommentSet(out io.Writer, node *yaml.Node) { fmt.Fprintf(out, "%q / %q / %q", node.HeadComment, node.LineComment, node.FootComment) } } + +func TestStyle_String(t *testing.T) { + a := require.New(t) + for _, tc := range []struct { + style yaml.Style + expect string + }{ + {0, "Undefined"}, + {yaml.TaggedStyle, "Tagged"}, + {yaml.DoubleQuotedStyle, "DoubleQuoted"}, + {yaml.SingleQuotedStyle, "SingleQuoted"}, + {yaml.LiteralStyle, "Literal"}, + {yaml.FoldedStyle, "Folded"}, + {yaml.FlowStyle, "Flow"}, + {4421412, "Style(4421412)"}, + } { + a.Equal(tc.expect, tc.style.String()) + } +}