feat(encode): add the InlineTables option

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-19 12:18:30 +02:00
parent 8f85bb68fa
commit 959eaba4b0
6 changed files with 308 additions and 6 deletions
+121
View File
@@ -741,7 +741,20 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
return err
}
}
// An inlined sub-table is a value line, so it has to precede every
// header of this document: a line written after a [header] would be
// read back as part of that table.
headers := make([]entry, 0, len(tables))
for _, t := range tables {
inlined, err := e.writeInlineSubTableIfSmall(t.key, t.doc)
if err != nil {
return err
}
if !inlined {
headers = append(headers, t)
}
}
for _, t := range headers {
path := append(append([]string{}, prefix...), t.key)
e.writeBlankLine()
e.buf.WriteByte('[')
@@ -782,6 +795,13 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
return err
}
case entryTable:
inlined, err := e.writeInlineSubTableIfSmall(ent.key, ent.doc)
if err != nil {
return err
}
if inlined {
continue
}
path := append(append([]string{}, prefix...), ent.key)
e.writeBlankLine()
e.buf.WriteByte('[')
@@ -1021,6 +1041,107 @@ func (e *encoder) writeInlineIndent() {
}
}
// errInlineArrayOfTables reports an attempt to render an array of tables
// inline, which has no form that keeps the value's type.
var errInlineArrayOfTables = errors.New("interpres: an array of tables has no inline form")
// inlinableDoc reports whether doc can be written as an inline table without
// changing the type of any value: scalars, value arrays and further sub-tables
// are fine, while an array of tables is not, because its inline form would
// re-parse as a value array.
func inlinableDoc(doc *tomlDoc) bool {
for _, ent := range doc.entries {
switch ent.kind {
case entryArray:
return false
case entryTable:
if !inlinableDoc(ent.doc) {
return false
}
}
}
return true
}
// writeInlineDocEntry writes one "key = value" binding of an inline table,
// without the separator that follows it.
func (e *encoder) writeInlineDocEntry(ent entry) error {
if err := e.writeKey(ent.key); err != nil {
return err
}
e.buf.WriteString(" = ")
switch ent.kind {
case entryTable:
return e.writeInlineDoc(ent.doc)
case entryArray:
return errInlineArrayOfTables
default:
return e.writeValue(ent.val)
}
}
// writeInlineDoc renders doc as a single-line inline table in entry order, the
// order the fields were declared in.
func (e *encoder) writeInlineDoc(doc *tomlDoc) error {
e.buf.WriteByte('{')
for i, ent := range doc.entries {
if i > 0 {
e.buf.WriteString(", ")
}
if err := e.writeInlineDocEntry(ent); err != nil {
return err
}
}
e.buf.WriteByte('}')
return nil
}
// writeInlineDocMultiline renders doc 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) writeInlineDocMultiline(doc *tomlDoc) error {
e.buf.WriteString("{\n")
e.inlineDepth++
for _, ent := range doc.entries {
e.writeInlineIndent()
if err := e.writeInlineDocEntry(ent); err != nil {
return err
}
e.buf.WriteString(",\n")
}
e.inlineDepth--
e.writeInlineIndent()
e.buf.WriteByte('}')
return nil
}
// writeInlineSubTableIfSmall writes "key = {…}" for a sub-table whose
// single-line rendering fits the compact threshold, and reports whether it did
// so. An array of tables is never inlined, because its inline form would
// re-parse as a value array and change the value's Go type.
func (e *encoder) writeInlineSubTableIfSmall(name string, doc *tomlDoc) (bool, error) {
if e.opts.inlineTablesAt <= 0 || !inlinableDoc(doc) {
return false, nil
}
flat := e.flat()
if err := flat.writeInlineDoc(doc); err != nil {
return false, err
}
if flat.buf.Len() > e.opts.inlineTablesAt {
return false, nil
}
if err := e.writeKey(name); err != nil {
return false, err
}
e.buf.WriteString(" = ")
if e.column()+flat.buf.Len() <= e.limit {
e.buf.Write(flat.buf.Bytes())
} else if err := e.writeInlineDocMultiline(doc); err != nil {
return false, err
}
e.buf.WriteByte('\n')
return true, nil
}
func (e *encoder) writeStringVal(s string) error {
if e.opts.literalMultilineAt > 0 && strings.ContainsRune(s, '\n') &&
len(s) >= e.opts.literalMultilineAt && canBeLiteralMultiline(s) {