Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support and tests for multiline string encoding closes BurntSushi/toml#64 #65

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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{}) {
Expand Down
12 changes: 12 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down