diff --git a/encode.go b/encode.go index 36187134..3292a571 100644 --- a/encode.go +++ b/encode.go @@ -30,6 +30,19 @@ var ( errAnything = errors.New("") // used in testing ) +type Modifier string + +const ( + MOD_NONE Modifier = "" + MOD_MULTILINE_STRING Modifier = "multiline_string" + MOD_MULTILINE_RAWSTRING Modifier = "multiline_rawstring" +) + +var validmodifiers = map[Modifier]reflect.Kind{ + MOD_MULTILINE_STRING: reflect.String, + MOD_MULTILINE_RAWSTRING: reflect.String, +} + var quotedReplacer = strings.NewReplacer( "\t", "\\t", "\n", "\\n", @@ -49,14 +62,18 @@ type Encoder struct { // hasWritten is whether we have written any output to w yet. hasWritten bool w *bufio.Writer + + // modifiers contains a map of struct field keys with detected modifiers + modifier Modifier } // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer // given. By default, a single indentation level is 2 spaces. func NewEncoder(w io.Writer) *Encoder { return &Encoder{ - w: bufio.NewWriter(w), - Indent: " ", + w: bufio.NewWriter(w), + Indent: " ", + modifier: MOD_NONE, } } @@ -341,6 +358,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) { if keyName == "" { keyName = sft.Name } + + keyModifier := Modifier(sft.Tag.Get("modifier")) + if kind, ok := validmodifiers[keyModifier]; ok && sf.Kind() == kind { + enc.modifier = keyModifier + } else { + enc.modifier = MOD_NONE + } + enc.encode(key.add(keyName), sf) } } @@ -442,8 +467,42 @@ func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { } panicIfInvalidKey(key, false) enc.wf("%s%s = ", enc.indentStr(key), key[len(key)-1]) - enc.eElement(val) + + //a modifier exists on this element, handle it with the appropriate function + switch enc.modifier { + case MOD_MULTILINE_STRING: + enc.writeMultiLineString(val.String(), false) + case MOD_MULTILINE_RAWSTRING: + enc.writeMultiLineString(val.String(), true) + default: + enc.eElement(val) + } enc.newline() + enc.modifier = MOD_NONE //re-setting the flag for safety. shoud not strictly be necessary +} + +func (enc *Encoder) writeMultiLineString(s string, raw bool) { + //if there are any windows style CRLF terminations, replace them with newlines and then split + s = strings.Replace(s, "\r\n", "\n", -1) + lines := strings.Split(s, "\n") + + var marker string + if raw { + marker = `'''` + } else { + marker = `"""` + } + + enc.wf(marker) //triple quote to start multiline string + for _, line := range lines { + enc.newline() //spec: decoder must remove \n if after triple quote + if raw { + enc.wf(line) + } else { + enc.wf(quotedReplacer.Replace(line)) //quote the rest of the characters + } + } + enc.wf(marker) } func (enc *Encoder) wf(format string, v ...interface{}) { diff --git a/encode_test.go b/encode_test.go index 74a5ee5d..b7dc9ca6 100644 --- a/encode_test.go +++ b/encode_test.go @@ -387,6 +387,18 @@ ArrayOfMixedSlices = [[1, 2], ["a", "b"]] }, wantError: errAnything, }, + "multiline string": { + input: struct { + Text string `modifier:"multiline_string"` + }{"\"Roses\" are red\n\"Violets\" are blue"}, + wantOutput: "Text = \"\"\"\n\\\"Roses\\\" are red\n\\\"Violets\\\" are blue\"\"\"\n", + }, + "multiline raw string": { + input: struct { + Text string `modifier:"multiline_rawstring"` + }{"\"Roses\" are red\n\"Violets\" are blue"}, + wantOutput: "Text = '''\n\"Roses\" are red\n\"Violets\" are blue'''\n", + }, } for label, test := range tests { encodeExpected(t, label, test.input, test.wantOutput, test.wantError)