From ce6fbd7bc088f354cd4ff3ed482fb36c956ff210 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 20:57:38 -0400 Subject: [PATCH] Support literal multiline marshal (#485) Use struct tag `multiline:"true" literal:"true"` to enable it. --- marshal.go | 7 +++++++ marshal_test.go | 26 ++++++++++++++++++++++++++ toml.go | 4 ++++ tomltree_write.go | 10 +++++++++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/marshal.go b/marshal.go index 1d8932ed..6540e035 100644 --- a/marshal.go +++ b/marshal.go @@ -18,6 +18,7 @@ const ( tagFieldComment = "comment" tagCommented = "commented" tagMultiline = "multiline" + tagLiteral = "literal" tagDefault = "default" ) @@ -27,6 +28,7 @@ type tomlOpts struct { comment string commented bool multiline bool + literal bool include bool omitempty bool defaultValue string @@ -46,6 +48,7 @@ type annotation struct { comment string commented string multiline string + literal string defaultValue string } @@ -54,6 +57,7 @@ var annotationDefault = annotation{ comment: tagFieldComment, commented: tagCommented, multiline: tagMultiline, + literal: tagLiteral, defaultValue: tagDefault, } @@ -442,6 +446,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er Comment: opts.comment, Commented: opts.commented, Multiline: opts.multiline, + Literal: opts.literal, }, val) } } @@ -1168,6 +1173,7 @@ func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { } commented, _ := strconv.ParseBool(vf.Tag.Get(an.commented)) multiline, _ := strconv.ParseBool(vf.Tag.Get(an.multiline)) + literal, _ := strconv.ParseBool(vf.Tag.Get(an.literal)) defaultValue := vf.Tag.Get(tagDefault) result := tomlOpts{ name: vf.Name, @@ -1175,6 +1181,7 @@ func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { comment: comment, commented: commented, multiline: multiline, + literal: literal, include: true, omitempty: false, defaultValue: defaultValue, diff --git a/marshal_test.go b/marshal_test.go index f948de4b..48f7df76 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -1294,6 +1294,32 @@ NonCommented = "Not commented line" } } +func TestMarshalMultilineLiteral(t *testing.T) { + type Doc struct { + Value string `multiline:"true" literal:"true"` + } + + d := Doc{ + Value: "hello\nworld\ttest\nend", + } + + expected := []byte(`Value = ''' +hello +world test +end +''' +`) + + b, err := Marshal(d) + if err != nil { + t.Fatal("unexpected error") + } + + if !bytes.Equal(b, expected) { + t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) + } +} + func TestMarshalNonPrimitiveTypeCommented(t *testing.T) { expectedToml := []byte(` # [CommentedMapField] diff --git a/toml.go b/toml.go index cbb89a9a..6d82587c 100644 --- a/toml.go +++ b/toml.go @@ -15,6 +15,7 @@ type tomlValue struct { comment string commented bool multiline bool + literal bool position Position } @@ -314,6 +315,7 @@ type SetOptions struct { Comment string Commented bool Multiline bool + Literal bool } // SetWithOptions is the same as Set, but allows you to provide formatting @@ -362,12 +364,14 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac v.comment = opts.Comment v.commented = opts.Commented v.multiline = opts.Multiline + v.literal = opts.Literal toInsert = v default: toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline, + literal: opts.Literal, position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}} } diff --git a/tomltree_write.go b/tomltree_write.go index 1d13217b..8e821a01 100644 --- a/tomltree_write.go +++ b/tomltree_write.go @@ -158,7 +158,15 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil case string: if tv.multiline { - return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil + if tv.literal { + b := strings.Builder{} + b.WriteString("'''\n") + b.Write([]byte(value)) + b.WriteString("\n'''") + return b.String(), nil + } else { + return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil + } } return "\"" + encodeTomlString(value) + "\"", nil case []byte: