Terrace/packages/go/document.go
Joshua Bemenderfer 9d9757e868 Updates.
2025-09-08 16:24:38 -04:00

131 lines
3.0 KiB
Go

package terrace
import (
"io"
"strings"
)
// Reader is the interface that reads lines from a document.
type Reader interface {
Read() (string, error)
}
// TerraceNode represents a single node/line in a Terrace document.
type TerraceNode struct {
lineData *LineData
content string
lineNumber int
document *TerraceDocument
}
// Head returns the head of the node.
func (n *TerraceNode) Head() string {
return n.content[n.lineData.OffsetHead:n.lineData.OffsetTail]
}
// Tail returns the tail of the node.
func (n *TerraceNode) Tail() string {
if n.lineData.OffsetTail+1 >= len(n.content) {
return ""
}
return n.content[n.lineData.OffsetTail+1:]
}
// Content returns the content of the node.
func (n *TerraceNode) Content() string {
return n.content[n.lineData.OffsetHead:]
}
// Level returns the indentation level of the node.
func (n *TerraceNode) Level() int {
return n.lineData.Level
}
// LineNumber returns the line number of the node.
func (n *TerraceNode) LineNumber() int {
return n.lineNumber
}
// IsEmpty returns true if the node represents an empty line.
func (n *TerraceNode) IsEmpty() bool {
return strings.TrimSpace(n.content) == ""
}
// Is returns true if the node's head matches the given value.
func (n *TerraceNode) Is(value string) bool {
return n.Head() == value
}
// Raw returns the raw content of the node starting from the given offset.
func (n *TerraceNode) Raw(offset int) string {
if offset >= len(n.content) {
return ""
}
return n.content[offset:]
}
// TerraceDocument is the main document iterator.
type TerraceDocument struct {
reader Reader
indent rune
lineData *LineData
currentLineNumber int
pushedBackNodes []*TerraceNode
isExhausted bool
}
// NewTerraceDocument creates a new Terrace document iterator.
func NewTerraceDocument(reader Reader, indent rune) *TerraceDocument {
return &TerraceDocument{
reader: reader,
indent: indent,
lineData: NewLineData(indent),
currentLineNumber: -1,
}
}
// Next returns the next node in the document.
func (d *TerraceDocument) Next() (*TerraceNode, error) {
if len(d.pushedBackNodes) > 0 {
node := d.pushedBackNodes[len(d.pushedBackNodes)-1]
d.pushedBackNodes = d.pushedBackNodes[:len(d.pushedBackNodes)-1]
return node, nil
}
if d.isExhausted {
return nil, io.EOF
}
line, err := d.reader.Read()
if err != nil {
if err == io.EOF {
d.isExhausted = true
return nil, io.EOF
}
return nil, err
}
d.currentLineNumber++
ParseLine(line, d.lineData)
// Copy the lineData to avoid mutations affecting existing nodes
lineDataCopy := &LineData{
Level: d.lineData.Level,
Indent: d.lineData.Indent,
OffsetHead: d.lineData.OffsetHead,
OffsetTail: d.lineData.OffsetTail,
}
return &TerraceNode{
lineData: lineDataCopy,
content: line,
lineNumber: d.currentLineNumber,
document: d,
}, nil
}
// PushBack pushes a node back to the document.
func (d *TerraceDocument) PushBack(node *TerraceNode) {
d.pushedBackNodes = append(d.pushedBackNodes, node)
}