feat(encode): write the TOML 1.1 output form

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-19 12:18:18 +02:00
parent bccaf087c8
commit 8f85bb68fa
8 changed files with 278 additions and 52 deletions
+83 -7
View File
@@ -28,18 +28,50 @@ var (
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
)
// inlineLimit is the column past which an inline table is written across
// lines. TOML 1.1 lets an inline table carry newlines and a trailing comma, so
// a long one stays readable instead of running off the line.
const inlineLimit = 100
// noInlineBreak is the limit a measuring encoder carries, high enough that the
// form it renders is always the single-line one.
const noInlineBreak = 1 << 30
// encoder produces a TOML document from a Go value via a small intermediate
// representation that preserves the order in which fields were declared.
type encoder struct {
buf bytes.Buffer
ctx context.Context
opts Encoder
// inlineDepth is the nesting level inside inline tables, which decides
// their indentation.
inlineDepth int
// limit is the column at which an inline table is broken; only a
// measuring encoder raises it.
limit int
}
func newEncoder() *encoder { return &encoder{} }
func newEncoder() *encoder { return &encoder{limit: inlineLimit} }
// flat returns an encoder that measures a value by rendering it on one line,
// so a caller can decide which form to write before writing it.
func (e *encoder) flat() *encoder {
return &encoder{ctx: e.ctx, opts: e.opts, limit: noInlineBreak}
}
func (e *encoder) bytes() []byte { return e.buf.Bytes() }
// column reports how many bytes the current line already holds, so a form can
// be measured against the limit before it is written.
func (e *encoder) column() int {
if i := bytes.LastIndexByte(e.buf.Bytes(), '\n'); i >= 0 {
return e.buf.Len() - i - 1
}
return e.buf.Len()
}
func (e *encoder) checkCtx() error {
if e.ctx == nil {
return nil
@@ -891,7 +923,7 @@ func (e *encoder) writeValue(val any) error {
case float64:
return e.writeFloat(v)
case time.Time:
e.buf.WriteString(v.Format(time.RFC3339Nano))
e.buf.WriteString(offsetString(v))
return nil
case LocalDateTime:
e.buf.WriteString(v.String())
@@ -915,7 +947,7 @@ func (e *encoder) writeValue(val any) error {
e.buf.WriteByte(']')
return nil
case map[string]any:
return e.writeInlineTable(v)
return e.writeInlineMap(v)
case nil:
return fmt.Errorf("interpres: cannot encode nil value")
default:
@@ -923,10 +955,24 @@ func (e *encoder) writeValue(val any) error {
}
}
// writeInlineTable renders m as a TOML inline table with sorted keys, the
// order buildMapDoc uses for header tables. It backs the table elements of a
// value array, where the [[header]] form is not available.
func (e *encoder) writeInlineTable(m map[string]any) error {
// writeInlineMap renders m as a TOML inline table, on one line when it fits
// there and across lines when it does not.
func (e *encoder) writeInlineMap(m map[string]any) error {
flat := e.flat()
if err := flat.writeInlineMapFlat(m); err != nil {
return err
}
if e.column()+flat.buf.Len() <= e.limit {
e.buf.Write(flat.buf.Bytes())
return nil
}
return e.writeInlineMapMultiline(m)
}
// writeInlineMapFlat renders m as a single-line inline table with sorted keys,
// the order buildMapDoc uses for header tables. It backs the table elements of
// a value array, where the [[header]] form is not available.
func (e *encoder) writeInlineMapFlat(m map[string]any) error {
keys := slices.Sorted(maps.Keys(m))
e.buf.WriteByte('{')
for i, k := range keys {
@@ -945,6 +991,36 @@ func (e *encoder) writeInlineTable(m map[string]any) error {
return nil
}
// writeInlineMapMultiline renders m with one entry per line and a trailing
// comma, the form TOML 1.1 allows for an inline table too long for one line.
func (e *encoder) writeInlineMapMultiline(m map[string]any) error {
keys := slices.Sorted(maps.Keys(m))
e.buf.WriteString("{\n")
e.inlineDepth++
for _, k := range keys {
e.writeInlineIndent()
if err := e.writeKey(k); err != nil {
return err
}
e.buf.WriteString(" = ")
if err := e.writeValue(m[k]); err != nil {
return err
}
e.buf.WriteString(",\n")
}
e.inlineDepth--
e.writeInlineIndent()
e.buf.WriteByte('}')
return nil
}
// writeInlineIndent writes one tab per inline-table nesting level.
func (e *encoder) writeInlineIndent() {
for range e.inlineDepth {
e.buf.WriteByte('\t')
}
}
func (e *encoder) writeStringVal(s string) error {
if e.opts.literalMultilineAt > 0 && strings.ContainsRune(s, '\n') &&
len(s) >= e.opts.literalMultilineAt && canBeLiteralMultiline(s) {