Add support for headline CUSTOM_ID property & linking
this introduces the PropertyDrawer node to make it easier to access the properties associated to a headline - normal drawers don't parse their content into kv pairs
This commit is contained in:
parent
ec895cbe83
commit
aa42998dbc
8 changed files with 79 additions and 14 deletions
|
@ -10,8 +10,13 @@ type Drawer struct {
|
|||
Children []Node
|
||||
}
|
||||
|
||||
type PropertyDrawer struct {
|
||||
Properties [][]string
|
||||
}
|
||||
|
||||
var beginDrawerRegexp = regexp.MustCompile(`^(\s*):(\S+):\s*$`)
|
||||
var endDrawerRegexp = regexp.MustCompile(`^(\s*):END:\s*$`)
|
||||
var propertyRegexp = regexp.MustCompile(`^(\s*):(\S+):(\s+(.*)$|\s*$)`)
|
||||
|
||||
func lexDrawer(line string) (token, bool) {
|
||||
if m := endDrawerRegexp.FindStringSubmatch(line); m != nil {
|
||||
|
@ -23,7 +28,11 @@ func lexDrawer(line string) (token, bool) {
|
|||
}
|
||||
|
||||
func (d *Document) parseDrawer(i int, parentStop stopFn) (int, Node) {
|
||||
drawer, start := Drawer{Name: strings.ToUpper(d.tokens[i].content)}, i
|
||||
name := strings.ToUpper(d.tokens[i].content)
|
||||
if name == "PROPERTIES" {
|
||||
return d.parsePropertyDrawer(i, parentStop)
|
||||
}
|
||||
drawer, start := Drawer{Name: name}, i
|
||||
i++
|
||||
stop := func(d *Document, i int) bool {
|
||||
if parentStop(d, i) {
|
||||
|
@ -49,3 +58,34 @@ func (d *Document) parseDrawer(i int, parentStop stopFn) (int, Node) {
|
|||
}
|
||||
return i - start, drawer
|
||||
}
|
||||
|
||||
func (d *Document) parsePropertyDrawer(i int, parentStop stopFn) (int, Node) {
|
||||
drawer, start := PropertyDrawer{}, i
|
||||
i++
|
||||
stop := func(d *Document, i int) bool {
|
||||
return parentStop(d, i) || (d.tokens[i].kind != "text" && d.tokens[i].kind != "beginDrawer")
|
||||
}
|
||||
for ; !stop(d, i); i++ {
|
||||
m := propertyRegexp.FindStringSubmatch(d.tokens[i].matches[0])
|
||||
k, v := strings.ToUpper(m[2]), strings.TrimSpace(m[4])
|
||||
drawer.Properties = append(drawer.Properties, []string{k, v})
|
||||
}
|
||||
if i < len(d.tokens) && d.tokens[i].kind == "endDrawer" {
|
||||
i++
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
return i - start, drawer
|
||||
}
|
||||
|
||||
func (d *PropertyDrawer) Get(key string) (string, bool) {
|
||||
if d == nil {
|
||||
return "", false
|
||||
}
|
||||
for _, kvPair := range d.Properties {
|
||||
if kvPair[0] == key {
|
||||
return kvPair[1], true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ type Headline struct {
|
|||
Lvl int
|
||||
Status string
|
||||
Priority string
|
||||
Properties Node
|
||||
Properties *PropertyDrawer
|
||||
Title []Node
|
||||
Tags []string
|
||||
Children []Node
|
||||
|
@ -62,8 +62,8 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
|
|||
}
|
||||
consumed, nodes := d.parseMany(i+1, stop)
|
||||
if len(nodes) > 0 {
|
||||
if d, ok := nodes[0].(Drawer); ok && d.Name == "PROPERTIES" {
|
||||
headline.Properties = d
|
||||
if d, ok := nodes[0].(PropertyDrawer); ok {
|
||||
headline.Properties = &d
|
||||
nodes = nodes[1:]
|
||||
}
|
||||
}
|
||||
|
|
10
org/html.go
10
org/html.go
|
@ -89,6 +89,8 @@ func (w *HTMLWriter) writeNodes(ns ...Node) {
|
|||
w.writeBlock(n)
|
||||
case Drawer:
|
||||
w.writeDrawer(n)
|
||||
case PropertyDrawer:
|
||||
continue
|
||||
|
||||
case FootnoteDefinition:
|
||||
w.writeFootnoteDefinition(n)
|
||||
|
@ -207,7 +209,13 @@ func (w *HTMLWriter) writeHeadline(h Headline) {
|
|||
if h.Lvl == 1 && title == w.FootnotesHeadingTitle {
|
||||
return
|
||||
}
|
||||
w.WriteString(fmt.Sprintf("<h%d>\n", h.Lvl))
|
||||
|
||||
if id, ok := h.Properties.Get("CUSTOM_ID"); ok {
|
||||
w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl, id) + "\n")
|
||||
} else {
|
||||
w.WriteString(fmt.Sprintf("<h%d>\n", h.Lvl))
|
||||
}
|
||||
|
||||
if h.Status != "" {
|
||||
w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n")
|
||||
}
|
||||
|
|
16
org/org.go
16
org/org.go
|
@ -63,6 +63,8 @@ func (w *OrgWriter) writeNodes(ns ...Node) {
|
|||
w.writeBlock(n)
|
||||
case Drawer:
|
||||
w.writeDrawer(n)
|
||||
case PropertyDrawer:
|
||||
w.writePropertyDrawer(n)
|
||||
|
||||
case FootnoteDefinition:
|
||||
w.writeFootnoteDefinition(n)
|
||||
|
@ -132,7 +134,7 @@ func (w *OrgWriter) writeHeadline(h Headline) {
|
|||
w.WriteString(w.indent)
|
||||
}
|
||||
if h.Properties != nil {
|
||||
w.writeNodes(h.Properties)
|
||||
w.writeNodes(*h.Properties)
|
||||
}
|
||||
w.writeNodes(h.Children...)
|
||||
}
|
||||
|
@ -159,6 +161,18 @@ func (w *OrgWriter) writeDrawer(d Drawer) {
|
|||
w.WriteString(w.indent + ":END:\n")
|
||||
}
|
||||
|
||||
func (w *OrgWriter) writePropertyDrawer(d PropertyDrawer) {
|
||||
w.WriteString(":PROPERTIES:\n")
|
||||
for _, kvPair := range d.Properties {
|
||||
k, v := kvPair[0], kvPair[1]
|
||||
if v != "" {
|
||||
v = " " + v
|
||||
}
|
||||
w.WriteString(fmt.Sprintf(":%s:%s\n", k, v))
|
||||
}
|
||||
w.WriteString(":END:\n")
|
||||
}
|
||||
|
||||
func (w *OrgWriter) writeFootnoteDefinition(f FootnoteDefinition) {
|
||||
w.WriteString(fmt.Sprintf("[fn:%s]", f.Name))
|
||||
if !(len(f.Children) >= 1 && isEmptyLineParagraph(f.Children[0])) {
|
||||
|
|
4
org/testdata/headlines.html
vendored
4
org/testdata/headlines.html
vendored
|
@ -25,12 +25,12 @@ not just where they are actually meant to be - even here > <code class="stati
|
|||
<span class="priority">[B]</span>
|
||||
Headline with todo status & priority
|
||||
</h1>
|
||||
<h1>
|
||||
<h1 id="this-will-be-the-id-of-the-headline">
|
||||
<span class="todo">DONE</span>
|
||||
Headline with TODO status
|
||||
</h1>
|
||||
<p>
|
||||
the <strong>content</strong>
|
||||
we can link to headlines that define a custom_id: <a href="#this-will-be-the-id-of-the-headline">#this-will-be-the-id-of-the-headline</a>
|
||||
</p>
|
||||
<h1>
|
||||
<span class="priority">[A]</span>
|
||||
|
|
5
org/testdata/headlines.org
vendored
5
org/testdata/headlines.org
vendored
|
@ -7,10 +7,11 @@
|
|||
* TODO [#B] Headline with todo status & priority
|
||||
* DONE Headline with TODO status
|
||||
:PROPERTIES:
|
||||
:Note: property drawers are not exported as html like other drawers
|
||||
:custom_id: this-will-be-the-id-of-the-headline
|
||||
:note: property drawers are not exported as html like other drawers
|
||||
:END:
|
||||
|
||||
the *content*
|
||||
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
|
||||
* [#A] Headline with tags & priority :foo:bar:
|
||||
Still outside the drawer
|
||||
:DRAWERNAME:
|
||||
|
|
5
org/testdata/headlines.pretty_org
vendored
5
org/testdata/headlines.pretty_org
vendored
|
@ -7,10 +7,11 @@
|
|||
* TODO [#B] Headline with todo status & priority
|
||||
* DONE Headline with TODO status
|
||||
:PROPERTIES:
|
||||
:Note: property drawers are not exported as html like other drawers
|
||||
:CUSTOM_ID: this-will-be-the-id-of-the-headline
|
||||
:NOTE: property drawers are not exported as html like other drawers
|
||||
:END:
|
||||
|
||||
the *content*
|
||||
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
|
||||
* [#A] Headline with tags & priority :foo:bar:
|
||||
Still outside the drawer
|
||||
:DRAWERNAME:
|
||||
|
|
5
org/testdata/misc.html
vendored
5
org/testdata/misc.html
vendored
|
@ -52,10 +52,11 @@ src block
|
|||
* TODO [#B] Headline with todo status & priority
|
||||
* DONE Headline with TODO status
|
||||
:PROPERTIES:
|
||||
:Note: property drawers are not exported as html like other drawers
|
||||
:custom_id: this-will-be-the-id-of-the-headline
|
||||
:note: property drawers are not exported as html like other drawers
|
||||
:END:
|
||||
|
||||
the *content*
|
||||
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
|
||||
* [#A] Headline with tags & priority :foo:bar:
|
||||
Still outside the drawer
|
||||
:DRAWERNAME:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue