202 lines
6.7 KiB
Go
202 lines
6.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package interpres
|
|
|
|
// A Document is a parsed TOML document: the values, plus what the map shape
|
|
// cannot carry, which is the order the keys were written in, whether a table
|
|
// was written inline or under a header, and the comments.
|
|
//
|
|
// The values are the tree ParseMap returns, shared rather than copied, so a
|
|
// value read from a Document and from that map is the same value. A comment
|
|
// belongs to the statement it precedes: the lines above a key belong to the
|
|
// key, the lines above a header belong to the header's table, and a comment
|
|
// block after the last statement belongs to the Document.
|
|
//
|
|
// Comments inside array and inline-table values are not carried yet; the
|
|
// parser skips them as it always has.
|
|
type Document struct {
|
|
root *Table
|
|
footer []string
|
|
}
|
|
|
|
// Root returns the document's root table. A nil document has no root.
|
|
func (d *Document) Root() *Table {
|
|
if d == nil {
|
|
return nil
|
|
}
|
|
return d.root
|
|
}
|
|
|
|
// 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 }
|
|
|
|
// 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 }
|
|
|
|
// SetFooter replaces those lines.
|
|
func (d *Document) SetFooter(lines []string) { d.footer = lines }
|
|
|
|
// A Table is one TOML table: its values, its keys in written order, and the
|
|
// comments around the header or the key that introduced it.
|
|
type Table struct {
|
|
values map[string]any
|
|
entries []*Entry
|
|
index map[string]*Entry
|
|
|
|
// inline records that the table was written as an inline table, `{…}`,
|
|
// rather than under a header or as a dotted key.
|
|
inline 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.
|
|
comments []string
|
|
trailing string
|
|
}
|
|
|
|
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.
|
|
func (t *Table) Keys() []string {
|
|
keys := make([]string, len(t.entries))
|
|
for i, e := range t.entries {
|
|
keys[i] = e.key
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// 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 }
|
|
|
|
// Entries returns the table's entries in written order.
|
|
func (t *Table) Entries() []*Entry { return t.entries }
|
|
|
|
// Get returns the entry for key, and whether the table has one.
|
|
func (t *Table) Get(key string) (*Entry, bool) {
|
|
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 }
|
|
|
|
// 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 }
|
|
|
|
// 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 }
|
|
|
|
// Trailing returns the comment on the header's own line, without the '#'.
|
|
func (t *Table) Trailing() string { return t.trailing }
|
|
|
|
// SetTrailing replaces that comment.
|
|
func (t *Table) SetTrailing(line string) { t.trailing = line }
|
|
|
|
// addValue records a key of the table, in written order.
|
|
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
|
|
}
|
|
|
|
// addTable records a key whose value is a table introduced by a header or a
|
|
// dotted key, and returns the table's node.
|
|
func (t *Table) addTable(key string, values map[string]any) *Table {
|
|
if e, ok := t.index[key]; ok {
|
|
// The key was seen before, as the leaf of an earlier dotted key.
|
|
if e.child == nil {
|
|
e.child = newTable(values)
|
|
}
|
|
return e.child
|
|
}
|
|
e := t.addValue(key, values, false)
|
|
e.child = newTable(values)
|
|
return e.child
|
|
}
|
|
|
|
// addElement records one element of an array of tables, and returns its node.
|
|
func (t *Table) addElement(key string, values map[string]any) *Table {
|
|
e, ok := t.index[key]
|
|
if !ok {
|
|
e = t.addValue(key, nil, false)
|
|
e.elements = []*Table{}
|
|
}
|
|
el := newTable(values)
|
|
e.elements = append(e.elements, el)
|
|
return el
|
|
}
|
|
|
|
// child returns the node of a table-valued key, or nil.
|
|
func (t *Table) child(key string) *Table {
|
|
if e, ok := t.index[key]; ok {
|
|
return e.child
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// lastElement returns the node of the newest element of an array of tables.
|
|
func (t *Table) lastElement(key string) *Table {
|
|
if e, ok := t.index[key]; ok && len(e.elements) > 0 {
|
|
return e.elements[len(e.elements)-1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// An Entry is one key of a table: the value and the comments around the key.
|
|
type Entry struct {
|
|
table *Table
|
|
key string
|
|
inline bool
|
|
comments []string
|
|
trailing string
|
|
|
|
// child is the table the value is, and elements are the tables of an array
|
|
// of tables; one of them is set only when the value has that shape.
|
|
child *Table
|
|
elements []*Table
|
|
}
|
|
|
|
// Key returns the key as it was written.
|
|
func (e *Entry) Key() string { return e.key }
|
|
|
|
// Value returns the value the key holds. It is read from the table's map, so
|
|
// it stays current if that map is changed.
|
|
func (e *Entry) Value() any { return e.table.values[e.key] }
|
|
|
|
// Inline reports whether the value was written as an inline table, `{…}`.
|
|
func (e *Entry) Inline() bool { return e.inline }
|
|
|
|
// Table returns the table the value is, or nil when it is not a table.
|
|
func (e *Entry) Table() *Table { return e.child }
|
|
|
|
// Elements returns the tables of an array of tables, or nil when the value is
|
|
// not one.
|
|
func (e *Entry) Elements() []*Table { return e.elements }
|
|
|
|
// Comments returns the comment lines above the key. Lines carry no leading '#'
|
|
// and no surrounding space.
|
|
func (e *Entry) Comments() []string { return e.comments }
|
|
|
|
// SetComments replaces those lines. Each line is written back with a "# " in
|
|
// front of it, so a line should not carry one.
|
|
func (e *Entry) SetComments(lines []string) { e.comments = lines }
|
|
|
|
// Trailing returns the comment on the key's own line, without the '#'.
|
|
func (e *Entry) Trailing() string { return e.trailing }
|
|
|
|
// SetTrailing replaces that comment.
|
|
func (e *Entry) SetTrailing(line string) { e.trailing = line }
|