131 lines
3.0 KiB
Go
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)
|
|
}
|