fix(document): rebuild set nodes and write documents back round-trip
Assisted-by: GLM 5.3
This commit is contained in:
+122
-26
@@ -34,15 +34,31 @@ func (d *Document) Root() *Table {
|
||||
}
|
||||
|
||||
// Map returns the value tree, the shape ParseMap gives. It is the tree the
|
||||
// document was parsed into, not a copy.
|
||||
func (d *Document) Map() map[string]any { return d.root.values }
|
||||
// document was parsed into, not a copy. A nil document or one with no root
|
||||
// holds no values.
|
||||
func (d *Document) Map() map[string]any {
|
||||
if d == nil || d.root == nil {
|
||||
return nil
|
||||
}
|
||||
return d.root.values
|
||||
}
|
||||
|
||||
// Footer returns the comment lines that follow the last statement, and every
|
||||
// line of a document that holds no statement at all.
|
||||
func (d *Document) Footer() []string { return d.footer }
|
||||
func (d *Document) Footer() []string {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return d.footer
|
||||
}
|
||||
|
||||
// SetFooter replaces those lines.
|
||||
func (d *Document) SetFooter(lines []string) { d.footer = lines }
|
||||
func (d *Document) SetFooter(lines []string) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
d.footer = lines
|
||||
}
|
||||
|
||||
// The document-level convenience forms of the Table edit API; they act on
|
||||
// the root table.
|
||||
@@ -87,6 +103,11 @@ type Table struct {
|
||||
// rather than under a header or as a dotted key.
|
||||
inline bool
|
||||
|
||||
// dotted records that a dotted key introduced the table, `a.b = 1`
|
||||
// building the a around the leaf: the write side gives such a table back
|
||||
// as dotted key lines, the form that holds the position of a line.
|
||||
dotted bool
|
||||
|
||||
// comments are the lines above the table's header, trailing is the comment
|
||||
// on the header's own line. Both are empty for a table a dotted key
|
||||
// introduced, which has no line of its own.
|
||||
@@ -98,8 +119,12 @@ func newTable(values map[string]any) *Table {
|
||||
return &Table{values: values, index: map[string]*Entry{}}
|
||||
}
|
||||
|
||||
// Keys returns the table's keys in the order they were written.
|
||||
// Keys returns the table's keys in the order they were written. A nil table
|
||||
// holds none, the answer a document without a root gives through Root.
|
||||
func (t *Table) Keys() []string {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, len(t.entries))
|
||||
for i, e := range t.entries {
|
||||
keys[i] = e.key
|
||||
@@ -109,43 +134,75 @@ func (t *Table) Keys() []string {
|
||||
|
||||
// Values returns the table's values, which is the map the value tree holds for
|
||||
// it.
|
||||
func (t *Table) Values() map[string]any { return t.values }
|
||||
func (t *Table) Values() map[string]any {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return t.values
|
||||
}
|
||||
|
||||
// Entries returns the table's entries in written order.
|
||||
func (t *Table) Entries() []*Entry { return t.entries }
|
||||
func (t *Table) Entries() []*Entry {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return t.entries
|
||||
}
|
||||
|
||||
// Get returns the entry for key, and whether the table has one.
|
||||
func (t *Table) Get(key string) (*Entry, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
e, ok := t.index[key]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
// Inline reports whether the table was written as an inline table, `{…}`,
|
||||
// rather than under a header or introduced by a dotted key.
|
||||
func (t *Table) Inline() bool { return t.inline }
|
||||
func (t *Table) Inline() bool { return t != nil && t.inline }
|
||||
|
||||
// Comments returns the comment lines above the table's header, or above the
|
||||
// key that introduced it. Lines carry no leading '#' and no surrounding space.
|
||||
func (t *Table) Comments() []string { return t.comments }
|
||||
func (t *Table) Comments() []string {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return t.comments
|
||||
}
|
||||
|
||||
// SetComments replaces those lines. Each line is written back with a "# " in
|
||||
// front of it, so a line should not carry one.
|
||||
func (t *Table) SetComments(lines []string) { t.comments = lines }
|
||||
func (t *Table) SetComments(lines []string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
t.comments = lines
|
||||
}
|
||||
|
||||
// Trailing returns the comment on the header's own line, without the '#'.
|
||||
func (t *Table) Trailing() string { return t.trailing }
|
||||
func (t *Table) Trailing() string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return t.trailing
|
||||
}
|
||||
|
||||
// SetTrailing replaces that comment.
|
||||
func (t *Table) SetTrailing(line string) { t.trailing = line }
|
||||
func (t *Table) SetTrailing(line string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
t.trailing = line
|
||||
}
|
||||
|
||||
// addValue records a key of the table, in written order.
|
||||
// addValue records a key of the table, in written order. The caller gives
|
||||
// the entry a table node or element nodes when the value has that shape; a
|
||||
// map value left without a node writes as an inline table.
|
||||
func (t *Table) addValue(key string, val any, inline bool) *Entry {
|
||||
e := &Entry{table: t, key: key, inline: inline}
|
||||
t.entries = append(t.entries, e)
|
||||
t.index[key] = e
|
||||
if node, ok := val.(map[string]any); ok {
|
||||
e.child = newTable(node)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -178,6 +235,9 @@ func (t *Table) addElement(key string, values map[string]any) *Table {
|
||||
|
||||
// child returns the node of a table-valued key, or nil.
|
||||
func (t *Table) child(key string) *Table {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := t.index[key]; ok {
|
||||
return e.child
|
||||
}
|
||||
@@ -239,6 +299,9 @@ func (e *Entry) SetTrailing(line string) { e.trailing = line }
|
||||
|
||||
// GetString returns the string the key holds, and whether it holds one.
|
||||
func (t *Table) GetString(key string) (string, bool) {
|
||||
if t == nil {
|
||||
return "", false
|
||||
}
|
||||
v, ok := t.values[key]
|
||||
s, ok := v.(string)
|
||||
return s, ok
|
||||
@@ -246,6 +309,9 @@ func (t *Table) GetString(key string) (string, bool) {
|
||||
|
||||
// GetInt returns the integer the key holds, and whether it holds one.
|
||||
func (t *Table) GetInt(key string) (int64, bool) {
|
||||
if t == nil {
|
||||
return 0, false
|
||||
}
|
||||
v, ok := t.values[key]
|
||||
i, ok := v.(int64)
|
||||
return i, ok
|
||||
@@ -253,6 +319,9 @@ func (t *Table) GetInt(key string) (int64, bool) {
|
||||
|
||||
// GetFloat returns the float the key holds, and whether it holds one.
|
||||
func (t *Table) GetFloat(key string) (float64, bool) {
|
||||
if t == nil {
|
||||
return 0, false
|
||||
}
|
||||
v, ok := t.values[key]
|
||||
f, ok := v.(float64)
|
||||
return f, ok
|
||||
@@ -260,6 +329,9 @@ func (t *Table) GetFloat(key string) (float64, bool) {
|
||||
|
||||
// GetBool returns the boolean the key holds, and whether it holds one.
|
||||
func (t *Table) GetBool(key string) (bool, bool) {
|
||||
if t == nil {
|
||||
return false, false
|
||||
}
|
||||
v, ok := t.values[key]
|
||||
b, ok := v.(bool)
|
||||
return b, ok
|
||||
@@ -267,6 +339,9 @@ func (t *Table) GetBool(key string) (bool, bool) {
|
||||
|
||||
// GetArray returns the value array the key holds, and whether it holds one.
|
||||
func (t *Table) GetArray(key string) ([]any, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
v, ok := t.values[key]
|
||||
a, ok := v.([]any)
|
||||
return a, ok
|
||||
@@ -282,9 +357,13 @@ func (t *Table) GetTable(key string) (*Table, bool) {
|
||||
// Set stores value under key. A key the table already has keeps its position
|
||||
// and its comments; a new one joins the end. A value of map[string]any
|
||||
// becomes a table node of its own, written under a header like any other
|
||||
// table; a Go map carries no order, so its keys take sorted order. A value
|
||||
// table, and replaces the node the key held, which belonged to the value the
|
||||
// key held; a Go map carries no order, so its keys take sorted order. A value
|
||||
// of []map[string]any becomes an array-of-tables node.
|
||||
func (t *Table) Set(key string, value any) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
e, ok := t.index[key]
|
||||
if !ok {
|
||||
t.values[key] = value
|
||||
@@ -303,11 +382,10 @@ func (t *Table) Set(key string, value any) {
|
||||
t.values[key] = value
|
||||
switch v := value.(type) {
|
||||
case map[string]any:
|
||||
if e.child == nil {
|
||||
e.child = newOrderedTable(v)
|
||||
} else {
|
||||
e.child.values = v
|
||||
}
|
||||
// The node is rebuilt rather than patched: the entries and the index
|
||||
// belong to the table the key held, and writing the new value
|
||||
// through them would leave the old table's keys in the output.
|
||||
e.child = newOrderedTable(v)
|
||||
e.elements = nil
|
||||
case []map[string]any:
|
||||
e.child = nil
|
||||
@@ -322,15 +400,30 @@ func (t *Table) Set(key string, value any) {
|
||||
}
|
||||
|
||||
// newOrderedTable builds a table node for a value the caller set, its keys
|
||||
// entered as entries in sorted order, the order Marshal writes maps in.
|
||||
// entered in sorted order, the order Marshal writes maps in.
|
||||
func newOrderedTable(m map[string]any) *Table {
|
||||
return orderedTable(m, 0)
|
||||
}
|
||||
|
||||
// orderedTable is newOrderedTable's recursion. The depth bound is the value
|
||||
// encoder's: a cyclic map stopped here is written by the value writer, which
|
||||
// reports it instead of running the stack out.
|
||||
func orderedTable(m map[string]any, depth int) *Table {
|
||||
t := newTable(m)
|
||||
for _, k := range slices.Sorted(maps.Keys(m)) {
|
||||
v := m[k]
|
||||
_, isMap := v.(map[string]any)
|
||||
e := t.addValue(k, v, false)
|
||||
if isMap {
|
||||
e.child = newOrderedTable(v.(map[string]any))
|
||||
if depth >= maxEncodeDepth {
|
||||
continue
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case map[string]any:
|
||||
e.child = orderedTable(val, depth+1)
|
||||
case []map[string]any:
|
||||
e.elements = make([]*Table, len(val))
|
||||
for i, item := range val {
|
||||
e.elements[i] = orderedTable(item, depth+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return t
|
||||
@@ -338,6 +431,9 @@ func newOrderedTable(m map[string]any) *Table {
|
||||
|
||||
// Delete removes key and everything it holds.
|
||||
func (t *Table) Delete(key string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := t.values[key]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user