Headlines are nested if their level is higher (more stars) than the current headline. We're abusing the token.lvl field for this - as headlines can never be indented we know the indentation must be 0 so we can cache the lvl (count of stars) of the headline in that field. This doesn't change anything right now so I'll postpone adding tests and stuff until there are actual use cases for the AST and stuff.
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
package org
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type Outline struct {
|
|
*Section
|
|
last *Section
|
|
count int
|
|
}
|
|
|
|
type Section struct {
|
|
Headline *Headline
|
|
Parent *Section
|
|
Children []*Section
|
|
}
|
|
|
|
type Headline struct {
|
|
Index int
|
|
Lvl int
|
|
Status string
|
|
Priority string
|
|
Properties *PropertyDrawer
|
|
Title []Node
|
|
Tags []string
|
|
Children []Node
|
|
}
|
|
|
|
var headlineRegexp = regexp.MustCompile(`^([*]+)\s+(.*)`)
|
|
var tagRegexp = regexp.MustCompile(`(.*?)\s+(:[A-Za-z0-9_@#%:]+:\s*$)`)
|
|
|
|
func lexHeadline(line string) (token, bool) {
|
|
if m := headlineRegexp.FindStringSubmatch(line); m != nil {
|
|
return token{"headline", len(m[1]), m[2], m}, true
|
|
}
|
|
return nilToken, false
|
|
}
|
|
|
|
func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
|
|
t, headline := d.tokens[i], Headline{}
|
|
headline.Lvl = t.lvl
|
|
|
|
headline.Index = d.addHeadline(&headline)
|
|
|
|
text := t.content
|
|
todoKeywords := strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' })
|
|
for _, k := range todoKeywords {
|
|
if strings.HasPrefix(text, k) && len(text) > len(k) && unicode.IsSpace(rune(text[len(k)])) {
|
|
headline.Status = k
|
|
text = text[len(k)+1:]
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(text) >= 3 && text[0:2] == "[#" && strings.Contains("ABC", text[2:3]) && text[3] == ']' {
|
|
headline.Priority = text[2:3]
|
|
text = strings.TrimSpace(text[4:])
|
|
}
|
|
|
|
if m := tagRegexp.FindStringSubmatch(text); m != nil {
|
|
text = m[1]
|
|
headline.Tags = strings.FieldsFunc(m[2], func(r rune) bool { return r == ':' })
|
|
}
|
|
|
|
headline.Title = d.parseInline(text)
|
|
|
|
stop := func(d *Document, i int) bool {
|
|
return parentStop(d, i) || d.tokens[i].kind == "headline" && d.tokens[i].lvl <= headline.Lvl
|
|
}
|
|
consumed, nodes := d.parseMany(i+1, stop)
|
|
if len(nodes) > 0 {
|
|
if d, ok := nodes[0].(PropertyDrawer); ok {
|
|
headline.Properties = &d
|
|
nodes = nodes[1:]
|
|
}
|
|
}
|
|
headline.Children = nodes
|
|
return consumed + 1, headline
|
|
}
|
|
|
|
func (h Headline) ID() string {
|
|
if customID, ok := h.Properties.Get("CUSTOM_ID"); ok {
|
|
return customID
|
|
}
|
|
return fmt.Sprintf("headline-%d", h.Index)
|
|
}
|
|
|
|
func (parent *Section) add(current *Section) {
|
|
if parent.Headline == nil || parent.Headline.Lvl < current.Headline.Lvl {
|
|
parent.Children = append(parent.Children, current)
|
|
current.Parent = parent
|
|
} else {
|
|
parent.Parent.add(current)
|
|
}
|
|
}
|
|
|
|
func (n Headline) String() string { return orgWriter.nodesAsString(n) }
|