Encode: add comment struct tag (#711)

Similar to v1, add a `comment` struct that that makes the encoder emit a comment
before the annotated element, if permitted. Unlike v1, comments are compact by
default (and cannot be changed).

Fixes #595.
This commit is contained in:
Thomas Pelletier
2021-12-26 18:29:46 +01:00
committed by GitHub
parent 8ce5c3d78f
commit 5fd6e9cce0
2 changed files with 61 additions and 15 deletions
+34 -15
View File
@@ -104,30 +104,31 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// Intermediate tables are always printed. // Intermediate tables are always printed.
// //
// By default, strings are encoded as literal string, unless they contain either // By default, strings are encoded as literal string, unless they contain either
// a newline character or a single quote. In that case they are emitted as quoted // a newline character or a single quote. In that case they are emitted as
// strings. // quoted strings.
// //
// When encoding structs, fields are encoded in order of definition, with their // When encoding structs, fields are encoded in order of definition, with their
// exact name. // exact name.
// //
// Struct tags // Struct tags
// //
// The encoding of each public struct field can be customized by the // The encoding of each public struct field can be customized by the format
// format string in the "toml" key of the struct field's tag. This // string in the "toml" key of the struct field's tag. This follows
// follows encoding/json's convention. The format string starts with // encoding/json's convention. The format string starts with the name of the
// the name of the field, optionally followed by a comma-separated // field, optionally followed by a comma-separated list of options. The name may
// list of options. The name may be empty in order to provide options // be empty in order to provide options without overriding the default name.
// without overriding the default name.
// //
// The "multiline" option emits strings as quoted multi-line TOML // The "multiline" option emits strings as quoted multi-line TOML strings. It
// strings. It has no effect on fields that would not be encoded as // has no effect on fields that would not be encoded as strings.
// strings.
// //
// The "inline" option turns fields that would be emitted as tables // The "inline" option turns fields that would be emitted as tables into inline
// into inline tables instead. It has no effect on other fields. // tables instead. It has no effect on other fields.
// //
// The "omitempty" option prevents empty values or groups from being // The "omitempty" option prevents empty values or groups from being emitted.
// emitted. //
// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
// a TOML comment before the value being annotated. Comments are ignored inside
// inline tables.
func (enc *Encoder) Encode(v interface{}) error { func (enc *Encoder) Encode(v interface{}) error {
var ( var (
b []byte b []byte
@@ -156,6 +157,7 @@ func (enc *Encoder) Encode(v interface{}) error {
type valueOptions struct { type valueOptions struct {
multiline bool multiline bool
omitempty bool omitempty bool
comment string
} }
type encoderCtx struct { type encoderCtx struct {
@@ -306,6 +308,10 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
return b, nil return b, nil
} }
if !ctx.inline {
b = enc.encodeComment(ctx.indent, options.comment, b)
}
b = enc.indent(ctx.indent, b) b = enc.indent(ctx.indent, b)
b, err = enc.encodeKey(b, ctx.key) b, err = enc.encodeKey(b, ctx.key)
@@ -441,6 +447,8 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
return b, nil return b, nil
} }
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
b = enc.indent(ctx.indent, b) b = enc.indent(ctx.indent, b)
b = append(b, '[') b = append(b, '[')
@@ -590,6 +598,7 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
options := valueOptions{ options := valueOptions{
multiline: opts.multiline, multiline: opts.multiline,
omitempty: opts.omitempty, omitempty: opts.omitempty,
comment: fieldType.Tag.Get("comment"),
} }
if opts.inline || !willConvertToTableOrArrayTable(ctx, f) { if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
@@ -602,6 +611,16 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
return enc.encodeTable(b, ctx, t) return enc.encodeTable(b, ctx, t)
} }
func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
if comment != "" {
b = enc.indent(indent, b)
b = append(b, "# "...)
b = append(b, comment...)
b = append(b, '\n')
}
return b
}
func isValidName(s string) bool { func isValidName(s string) bool {
if s == "" { if s == "" {
return false return false
+27
View File
@@ -21,6 +21,12 @@ func TestMarshal(t *testing.T) {
A interface{} `toml:",inline"` A interface{} `toml:",inline"`
} }
type comments struct {
One int
Two int `comment:"Before kv"`
Three []int `comment:"Before array"`
}
examples := []struct { examples := []struct {
desc string desc string
v interface{} v interface{}
@@ -535,6 +541,27 @@ J = 42
K = 42 K = 42
L = 2.2`, L = 2.2`,
}, },
{
desc: "comments",
v: struct {
Table comments `comment:"Before table"`
}{
Table: comments{
One: 1,
Two: 2,
Three: []int{1, 2, 3},
},
},
expected: `
# Before table
[Table]
One = 1
# Before kv
Two = 2
# Before array
Three = [1, 2, 3]
`,
},
} }
for _, e := range examples { for _, e := range examples {