Option to keep fields ordered when marshal struct (#266)

Adds a new `Order()` option to preserve order of struct fields when
marshaling.
This commit is contained in:
Brent DeSpain
2019-04-02 10:47:51 -06:00
committed by Thomas Pelletier
parent f9070d3b40
commit 63909f0a90
5 changed files with 335 additions and 143 deletions
+37 -6
View File
@@ -54,10 +54,21 @@ var annotationDefault = annotation{
defaultValue: tagDefault, defaultValue: tagDefault,
} }
type marshalOrder int
// Orders the Encoder can write the fields to the output stream.
const (
// Sort fields alphabetically.
OrderAlphabetical marshalOrder = iota + 1
// Preserve the order the fields are encountered. For example, the order of fields in
// a struct.
OrderPreserve
)
var timeType = reflect.TypeOf(time.Time{}) var timeType = reflect.TypeOf(time.Time{})
var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() var marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
// Check if the given marshall type maps to a Tree primitive // Check if the given marshal type maps to a Tree primitive
func isPrimitive(mtype reflect.Type) bool { func isPrimitive(mtype reflect.Type) bool {
switch mtype.Kind() { switch mtype.Kind() {
case reflect.Ptr: case reflect.Ptr:
@@ -79,7 +90,7 @@ func isPrimitive(mtype reflect.Type) bool {
} }
} }
// Check if the given marshall type maps to a Tree slice // Check if the given marshal type maps to a Tree slice
func isTreeSlice(mtype reflect.Type) bool { func isTreeSlice(mtype reflect.Type) bool {
switch mtype.Kind() { switch mtype.Kind() {
case reflect.Slice: case reflect.Slice:
@@ -89,7 +100,7 @@ func isTreeSlice(mtype reflect.Type) bool {
} }
} }
// Check if the given marshall type maps to a non-Tree slice // Check if the given marshal type maps to a non-Tree slice
func isOtherSlice(mtype reflect.Type) bool { func isOtherSlice(mtype reflect.Type) bool {
switch mtype.Kind() { switch mtype.Kind() {
case reflect.Ptr: case reflect.Ptr:
@@ -101,7 +112,7 @@ func isOtherSlice(mtype reflect.Type) bool {
} }
} }
// Check if the given marshall type maps to a Tree // Check if the given marshal type maps to a Tree
func isTree(mtype reflect.Type) bool { func isTree(mtype reflect.Type) bool {
switch mtype.Kind() { switch mtype.Kind() {
case reflect.Map: case reflect.Map:
@@ -159,6 +170,8 @@ Tree primitive types and corresponding marshal types:
string string, pointers to same string string, pointers to same
bool bool, pointers to same bool bool, pointers to same
time.Time time.Time{}, pointers to same time.Time time.Time{}, pointers to same
For additional flexibility, use the Encoder API.
*/ */
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
return NewEncoder(nil).marshal(v) return NewEncoder(nil).marshal(v)
@@ -169,6 +182,9 @@ type Encoder struct {
w io.Writer w io.Writer
encOpts encOpts
annotation annotation
line int
col int
order marshalOrder
} }
// NewEncoder returns a new encoder that writes to w. // NewEncoder returns a new encoder that writes to w.
@@ -177,6 +193,9 @@ func NewEncoder(w io.Writer) *Encoder {
w: w, w: w,
encOpts: encOptsDefaults, encOpts: encOptsDefaults,
annotation: annotationDefault, annotation: annotationDefault,
line: 0,
col: 1,
order: OrderAlphabetical,
} }
} }
@@ -222,6 +241,12 @@ func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder {
return e return e
} }
// Order allows to change in which order fields will be written to the output stream.
func (e *Encoder) Order(ord marshalOrder) *Encoder {
e.order = ord
return e
}
// SetTagName allows changing default tag "toml" // SetTagName allows changing default tag "toml"
func (e *Encoder) SetTagName(v string) *Encoder { func (e *Encoder) SetTagName(v string) *Encoder {
e.tag = v e.tag = v
@@ -269,17 +294,22 @@ func (e *Encoder) marshal(v interface{}) ([]byte, error) {
} }
var buf bytes.Buffer var buf bytes.Buffer
_, err = t.writeTo(&buf, "", "", 0, e.arraysOneElementPerLine) _, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order)
return buf.Bytes(), err return buf.Bytes(), err
} }
// Create next tree with a position based on Encoder.line
func (e *Encoder) nextTree() *Tree {
return newTreeWithPosition(Position{Line: e.line, Col: 1})
}
// Convert given marshal struct or map value to toml tree // Convert given marshal struct or map value to toml tree
func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) {
if mtype.Kind() == reflect.Ptr { if mtype.Kind() == reflect.Ptr {
return e.valueToTree(mtype.Elem(), mval.Elem()) return e.valueToTree(mtype.Elem(), mval.Elem())
} }
tval := newTree() tval := e.nextTree()
switch mtype.Kind() { switch mtype.Kind() {
case reflect.Struct: case reflect.Struct:
for i := 0; i < mtype.NumField(); i++ { for i := 0; i < mtype.NumField(); i++ {
@@ -347,6 +377,7 @@ func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (int
// Convert given marshal value to toml value // Convert given marshal value to toml value
func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
e.line++
if mtype.Kind() == reflect.Ptr { if mtype.Kind() == reflect.Ptr {
return e.valueToToml(mtype.Elem(), mval.Elem()) return e.valueToToml(mtype.Elem(), mval.Elem())
} }
+38
View File
@@ -0,0 +1,38 @@
title = "TOML Marshal Testing"
[basic_lists]
floats = [12.3,45.6,78.9]
bools = [true,false,true]
dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z]
ints = [8001,8001,8002]
uints = [5002,5003]
strings = ["One","Two","Three"]
[[subdocptrs]]
name = "Second"
[basic_map]
one = "one"
two = "two"
[subdoc]
[subdoc.second]
name = "Second"
[subdoc.first]
name = "First"
[basic]
uint = 5001
bool = true
float = 123.4
int = 5000
string = "Bite me"
date = 1979-05-27T07:32:00Z
[[subdoclist]]
name = "List.First"
[[subdoclist]]
name = "List.Second"
+67 -18
View File
@@ -12,10 +12,10 @@ import (
) )
type basicMarshalTestStruct struct { type basicMarshalTestStruct struct {
String string `toml:"string"` String string `toml:"Zstring"`
StringList []string `toml:"strlist"` StringList []string `toml:"Ystrlist"`
Sub basicMarshalTestSubStruct `toml:"subdoc"` Sub basicMarshalTestSubStruct `toml:"Xsubdoc"`
SubList []basicMarshalTestSubStruct `toml:"sublist"` SubList []basicMarshalTestSubStruct `toml:"Wsublist"`
} }
type basicMarshalTestSubStruct struct { type basicMarshalTestSubStruct struct {
@@ -29,16 +29,29 @@ var basicTestData = basicMarshalTestStruct{
SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}},
} }
var basicTestToml = []byte(`string = "Hello" var basicTestToml = []byte(`Ystrlist = ["Howdy","Hey There"]
strlist = ["Howdy","Hey There"] Zstring = "Hello"
[subdoc] [[Wsublist]]
String2 = "One"
[[sublist]]
String2 = "Two" String2 = "Two"
[[sublist]] [[Wsublist]]
String2 = "Three"
[Xsubdoc]
String2 = "One"
`)
var basicTestTomlOrdered = []byte(`Zstring = "Hello"
Ystrlist = ["Howdy","Hey There"]
[Xsubdoc]
String2 = "One"
[[Wsublist]]
String2 = "Two"
[[Wsublist]]
String2 = "Three" String2 = "Three"
`) `)
@@ -53,6 +66,18 @@ func TestBasicMarshal(t *testing.T) {
} }
} }
func TestBasicMarshalOrdered(t *testing.T) {
var result bytes.Buffer
err := NewEncoder(&result).Order(OrderPreserve).Encode(basicTestData)
if err != nil {
t.Fatal(err)
}
expected := basicTestTomlOrdered
if !bytes.Equal(result.Bytes(), expected) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes())
}
}
func TestBasicMarshalWithPointer(t *testing.T) { func TestBasicMarshalWithPointer(t *testing.T) {
result, err := Marshal(&basicTestData) result, err := Marshal(&basicTestData)
if err != nil { if err != nil {
@@ -64,6 +89,18 @@ func TestBasicMarshalWithPointer(t *testing.T) {
} }
} }
func TestBasicMarshalOrderedWithPointer(t *testing.T) {
var result bytes.Buffer
err := NewEncoder(&result).Order(OrderPreserve).Encode(&basicTestData)
if err != nil {
t.Fatal(err)
}
expected := basicTestTomlOrdered
if !bytes.Equal(result.Bytes(), expected) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes())
}
}
func TestBasicUnmarshal(t *testing.T) { func TestBasicUnmarshal(t *testing.T) {
result := basicMarshalTestStruct{} result := basicMarshalTestStruct{}
err := Unmarshal(basicTestToml, &result) err := Unmarshal(basicTestToml, &result)
@@ -78,39 +115,39 @@ func TestBasicUnmarshal(t *testing.T) {
type testDoc struct { type testDoc struct {
Title string `toml:"title"` Title string `toml:"title"`
Basics testDocBasics `toml:"basic"`
BasicLists testDocBasicLists `toml:"basic_lists"` BasicLists testDocBasicLists `toml:"basic_lists"`
SubDocPtrs []*testSubDoc `toml:"subdocptrs"`
BasicMap map[string]string `toml:"basic_map"` BasicMap map[string]string `toml:"basic_map"`
Subdocs testDocSubs `toml:"subdoc"` Subdocs testDocSubs `toml:"subdoc"`
Basics testDocBasics `toml:"basic"`
SubDocList []testSubDoc `toml:"subdoclist"` SubDocList []testSubDoc `toml:"subdoclist"`
SubDocPtrs []*testSubDoc `toml:"subdocptrs"`
err int `toml:"shouldntBeHere"` err int `toml:"shouldntBeHere"`
unexported int `toml:"shouldntBeHere"` unexported int `toml:"shouldntBeHere"`
Unexported2 int `toml:"-"` Unexported2 int `toml:"-"`
} }
type testDocBasics struct { type testDocBasics struct {
Uint uint `toml:"uint"`
Bool bool `toml:"bool"` Bool bool `toml:"bool"`
Date time.Time `toml:"date"`
Float float32 `toml:"float"` Float float32 `toml:"float"`
Int int `toml:"int"` Int int `toml:"int"`
Uint uint `toml:"uint"`
String *string `toml:"string"` String *string `toml:"string"`
Date time.Time `toml:"date"`
unexported int `toml:"shouldntBeHere"` unexported int `toml:"shouldntBeHere"`
} }
type testDocBasicLists struct { type testDocBasicLists struct {
Floats []*float32 `toml:"floats"`
Bools []bool `toml:"bools"` Bools []bool `toml:"bools"`
Dates []time.Time `toml:"dates"` Dates []time.Time `toml:"dates"`
Floats []*float32 `toml:"floats"`
Ints []int `toml:"ints"` Ints []int `toml:"ints"`
Strings []string `toml:"strings"`
UInts []uint `toml:"uints"` UInts []uint `toml:"uints"`
Strings []string `toml:"strings"`
} }
type testDocSubs struct { type testDocSubs struct {
First testSubDoc `toml:"first"`
Second *testSubDoc `toml:"second"` Second *testSubDoc `toml:"second"`
First testSubDoc `toml:"first"`
} }
type testSubDoc struct { type testSubDoc struct {
@@ -174,6 +211,18 @@ func TestDocMarshal(t *testing.T) {
} }
} }
func TestDocMarshalOrdered(t *testing.T) {
var result bytes.Buffer
err := NewEncoder(&result).Order(OrderPreserve).Encode(docData)
if err != nil {
t.Fatal(err)
}
expected, _ := ioutil.ReadFile("marshal_OrderPreserve_test.toml")
if !bytes.Equal(result.Bytes(), expected) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes())
}
}
func TestDocMarshalPointer(t *testing.T) { func TestDocMarshalPointer(t *testing.T) {
result, err := Marshal(&docData) result, err := Marshal(&docData)
if err != nil { if err != nil {
+16 -43
View File
@@ -27,9 +27,13 @@ type Tree struct {
} }
func newTree() *Tree { func newTree() *Tree {
return newTreeWithPosition(Position{})
}
func newTreeWithPosition(pos Position) *Tree {
return &Tree{ return &Tree{
values: make(map[string]interface{}), values: make(map[string]interface{}),
position: Position{}, position: pos,
} }
} }
@@ -194,10 +198,10 @@ func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) {
// formatting instructions to the key, that will be reused by Marshal(). // formatting instructions to the key, that will be reused by Marshal().
func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) {
subtree := t subtree := t
for _, intermediateKey := range keys[:len(keys)-1] { for i, intermediateKey := range keys[:len(keys)-1] {
nextTree, exists := subtree.values[intermediateKey] nextTree, exists := subtree.values[intermediateKey]
if !exists { if !exists {
nextTree = newTree() nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})
subtree.values[intermediateKey] = nextTree // add new element here subtree.values[intermediateKey] = nextTree // add new element here
} }
switch node := nextTree.(type) { switch node := nextTree.(type) {
@@ -207,7 +211,7 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
// go to most recent element // go to most recent element
if len(node) == 0 { if len(node) == 0 {
// create element if it does not exist // create element if it does not exist
subtree.values[intermediateKey] = append(node, newTree()) subtree.values[intermediateKey] = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
} }
subtree = node[len(node)-1] subtree = node[len(node)-1]
} }
@@ -225,7 +229,11 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
v.comment = opts.Comment v.comment = opts.Comment
toInsert = v toInsert = v
default: default:
toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline} toInsert = &tomlValue{value: value,
comment: opts.Comment,
commented: opts.Commented,
multiline: opts.Multiline,
position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}}
} }
subtree.values[keys[len(keys)-1]] = toInsert subtree.values[keys[len(keys)-1]] = toInsert
@@ -254,42 +262,7 @@ func (t *Tree) SetPath(keys []string, value interface{}) {
// SetPathWithComment is the same as SetPath, but allows you to provide comment // SetPathWithComment is the same as SetPath, but allows you to provide comment
// information to the key, that will be reused by Marshal(). // information to the key, that will be reused by Marshal().
func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) {
subtree := t t.SetPathWithOptions(keys, SetOptions{Comment: comment, Commented: commented}, value)
for _, intermediateKey := range keys[:len(keys)-1] {
nextTree, exists := subtree.values[intermediateKey]
if !exists {
nextTree = newTree()
subtree.values[intermediateKey] = nextTree // add new element here
}
switch node := nextTree.(type) {
case *Tree:
subtree = node
case []*Tree:
// go to most recent element
if len(node) == 0 {
// create element if it does not exist
subtree.values[intermediateKey] = append(node, newTree())
}
subtree = node[len(node)-1]
}
}
var toInsert interface{}
switch v := value.(type) {
case *Tree:
v.comment = comment
toInsert = value
case []*Tree:
toInsert = value
case *tomlValue:
v.comment = comment
toInsert = v
default:
toInsert = &tomlValue{value: value, comment: comment, commented: commented}
}
subtree.values[keys[len(keys)-1]] = toInsert
} }
// Delete removes a key from the tree. // Delete removes a key from the tree.
@@ -329,10 +302,10 @@ func (t *Tree) DeletePath(keys []string) error {
// Returns nil on success, error object on failure // Returns nil on success, error object on failure
func (t *Tree) createSubTree(keys []string, pos Position) error { func (t *Tree) createSubTree(keys []string, pos Position) error {
subtree := t subtree := t
for _, intermediateKey := range keys { for i, intermediateKey := range keys {
nextTree, exists := subtree.values[intermediateKey] nextTree, exists := subtree.values[intermediateKey]
if !exists { if !exists {
tree := newTree() tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})
tree.position = pos tree.position = pos
subtree.values[intermediateKey] = tree subtree.values[intermediateKey] = tree
nextTree = tree nextTree = tree
+141 -40
View File
@@ -12,6 +12,18 @@ import (
"time" "time"
) )
type valueComplexity int
const (
valueSimple valueComplexity = iota + 1
valueComplex
)
type sortNode struct {
key string
complexity valueComplexity
}
// Encodes a string to a TOML-compliant multi-line string value // Encodes a string to a TOML-compliant multi-line string value
// This function is a clone of the existing encodeTomlString function, except that whitespace characters // This function is a clone of the existing encodeTomlString function, except that whitespace characters
// are preserved. Quotation marks and backslashes are also not escaped. // are preserved. Quotation marks and backslashes are also not escaped.
@@ -153,59 +165,113 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen
return "", fmt.Errorf("unsupported value type %T: %v", v, v) return "", fmt.Errorf("unsupported value type %T: %v", v, v)
} }
func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) { func getTreeArrayLine(trees []*Tree) (line int) {
simpleValuesKeys := make([]string, 0) // get lowest line number that is not 0
complexValuesKeys := make([]string, 0) for _, tv := range trees {
if tv.position.Line < line || line == 0 {
line = tv.position.Line
}
}
return
}
func sortByLines(t *Tree) (vals []sortNode) {
var (
line int
lines []int
tv *Tree
tom *tomlValue
node sortNode
)
vals = make([]sortNode, 0)
m := make(map[int]sortNode)
for k := range t.values {
v := t.values[k]
switch v.(type) {
case *Tree:
tv = v.(*Tree)
line = tv.position.Line
node = sortNode{key: k, complexity: valueComplex}
case []*Tree:
line = getTreeArrayLine(v.([]*Tree))
node = sortNode{key: k, complexity: valueComplex}
default:
tom = v.(*tomlValue)
line = tom.position.Line
node = sortNode{key: k, complexity: valueSimple}
}
lines = append(lines, line)
vals = append(vals, node)
m[line] = node
}
sort.Ints(lines)
for i, line := range lines {
vals[i] = m[line]
}
return vals
}
func sortAlphabetical(t *Tree) (vals []sortNode) {
var (
node sortNode
simpVals []string
compVals []string
)
vals = make([]sortNode, 0)
m := make(map[string]sortNode)
for k := range t.values { for k := range t.values {
v := t.values[k] v := t.values[k]
switch v.(type) { switch v.(type) {
case *Tree, []*Tree: case *Tree, []*Tree:
complexValuesKeys = append(complexValuesKeys, k) node = sortNode{key: k, complexity: valueComplex}
compVals = append(compVals, node.key)
default: default:
simpleValuesKeys = append(simpleValuesKeys, k) node = sortNode{key: k, complexity: valueSimple}
simpVals = append(simpVals, node.key)
} }
vals = append(vals, node)
m[node.key] = node
} }
sort.Strings(simpleValuesKeys) // Simples first to match previous implementation
sort.Strings(complexValuesKeys) sort.Strings(simpVals)
i := 0
for _, k := range simpleValuesKeys { for _, key := range simpVals {
v, ok := t.values[k].(*tomlValue) vals[i] = m[key]
if !ok { i++
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
} }
repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine) sort.Strings(compVals)
if err != nil { for _, key := range compVals {
return bytesCount, err vals[i] = m[key]
i++
} }
if v.comment != "" { return vals
comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1) }
start := "# "
if strings.HasPrefix(comment, "#") { func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
start = "" return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical)
} }
writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
bytesCount += int64(writtenBytesCountComment) func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder) (int64, error) {
if errc != nil { orderedVals := make([]sortNode, 0)
return bytesCount, errc
} switch ord {
case OrderPreserve:
orderedVals = sortByLines(t)
default:
orderedVals = sortAlphabetical(t)
} }
var commented string for _, node := range orderedVals {
if v.commented { switch node.complexity {
commented = "# " case valueComplex:
} k := node.key
writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
}
for _, k := range complexValuesKeys {
v := t.values[k] v := t.values[k]
combinedKey := k combinedKey := k
@@ -241,7 +307,7 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, a
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine) bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
@@ -253,12 +319,47 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, a
return bytesCount, err return bytesCount, err
} }
bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine) bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
} }
} }
default: // Simple
k := node.key
v, ok := t.values[k].(*tomlValue)
if !ok {
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
}
repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
if err != nil {
return bytesCount, err
}
if v.comment != "" {
comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
start := "# "
if strings.HasPrefix(comment, "#") {
start = ""
}
writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
bytesCount += int64(writtenBytesCountComment)
if errc != nil {
return bytesCount, errc
}
}
var commented string
if v.commented {
commented = "# "
}
writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
}
} }
return bytesCount, nil return bytesCount, nil