Parse labels in [[wiki links | label]] and support escaped characters

pull/6/head
Mickaël Menu 3 years ago
parent a5b7639bcd
commit 6175a73c1c
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -3,6 +3,7 @@ package extensions
import ( import (
"strings" "strings"
"github.com/mickael-menu/zk/core/note"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
@ -10,7 +11,7 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
// WikiLink is an extension parsing wiki links and neuron's Folgezettel. // WikiLink is an extension parsing wiki links and Neuron's Folgezettel.
// //
// For example, [[wiki link]], [[[legacy downlink]]], #[[uplink]], [[downlink]]#. // For example, [[wiki link]], [[[legacy downlink]]], #[[uplink]], [[downlink]]#.
var WikiLink = &wikiLink{} var WikiLink = &wikiLink{}
@ -34,32 +35,46 @@ func (p *wlParser) Trigger() []byte {
func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine() line, _ := block.PeekLine()
openerCharCount := 0 var (
opened := false href string
closed := false label string
content := []byte{} rel note.LinkRelation
closerCharCount := 0 )
endPos := 0
var (
opened = false // Found at least [[
closed = false // Found at least ]]
escaping = false // Found a backslash, next character will be literal
parsingLabel = false // Found a | in a Wikilink, now we parse the link's label
openerCharCount = 0 // Number of [ encountered
closerCharCount = 0 // Number of ] encountered
endPos = 0 // Last position of the link in the line
)
// Folgezettel direction: -1 down, 0 unknown, 1 up appendChar := func(c byte) {
direction := 0 if parsingLabel {
label += string(c)
} else {
href += string(c)
}
}
for i, char := range line { for i, char := range line {
endPos = i endPos = i
if closed { if closed {
// Supports trailing hash syntax for neuron's Folgezettel, e.g. [[id]]# // Supports trailing hash syntax for Neuron's Folgezettel, e.g. [[id]]#
if char == '#' { if char == '#' {
direction = -1 rel = note.LinkRelationDown
} }
break break
} }
if !opened { if !opened {
switch char { switch char {
// Supports leading hash syntax for neuron's Folgezettel, e.g. #[[id]] // Supports leading hash syntax for Neuron's Folgezettel, e.g. #[[id]]
case '#': case '#':
direction = 1 rel = note.LinkRelationUp
continue continue
case '[': case '[':
openerCharCount += 1 openerCharCount += 1
@ -72,40 +87,59 @@ func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context)
} }
opened = true opened = true
if char == ']' { if !escaping {
closerCharCount += 1 switch char {
if closerCharCount == openerCharCount {
closed = true case '|': // [[href | label]]
// neuron's legacy [[[Folgezettel]]]. parsingLabel = true
if closerCharCount == 3 { continue
direction = -1
case '\\':
escaping = true
continue
case ']':
closerCharCount += 1
if closerCharCount == openerCharCount {
closed = true
// Neuron's legacy [[[Folgezettel]]].
if closerCharCount == 3 {
rel = note.LinkRelationDown
}
} }
continue
} }
} else { }
if closerCharCount > 0 { escaping = false
content = append(content, strings.Repeat("]", closerCharCount)...)
closerCharCount = 0 // Found incomplete number of closing brackets to close the link.
// We add them to the HREF and reset the count.
if closerCharCount > 0 {
for i := 0; i < closerCharCount; i++ {
appendChar(']')
} }
content = append(content, char) closerCharCount = 0
} }
appendChar(char)
} }
if !closed || len(content) == 0 { if !closed || len(href) == 0 {
return nil return nil
} }
block.Advance(endPos) block.Advance(endPos)
link := ast.NewLink() href = strings.TrimSpace(href)
link.Destination = content label = strings.TrimSpace(label)
if len(label) == 0 {
// Title will be parsed as the link's rels. label = href
switch direction {
case -1:
link.Title = []byte("down")
case 1:
link.Title = []byte("up")
} }
link := ast.NewLink()
link.Destination = []byte(href)
// Title will be parsed as the link's rel by the Markdown parser.
link.Title = []byte(rel)
link.AppendChild(link, ast.NewString([]byte(label)))
return link return link
} }

@ -170,7 +170,9 @@ A link can have [one relation](one "rel-1") or [several relations](several "rel-
An https://inline-link.com and http://another-inline-link.com. An https://inline-link.com and http://another-inline-link.com.
A [[Wiki link]] is surrounded by two brackets. A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].
It can contain [[esca]\]ped \[chara\\cters]].
A [[[Folgezettel link]]] is surrounded by three brackets. A [[[Folgezettel link]]] is surrounded by three brackets.
@ -178,6 +180,8 @@ Neuron also supports a [[trailing hash]]# for Folgezettel links.
A #[[leading hash]] is used for #uplinks. A #[[leading hash]] is used for #uplinks.
Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]
[External links](http://example.com) are marked [as such](ftp://domain). [External links](http://example.com) are marked [as such](ftp://domain).
`, []note.Link{ `, []note.Link{
{ {
@ -234,33 +238,61 @@ A link can have [one relation](one "rel-1") or [several relations](several "rel-
Snippet: "An https://inline-link.com and http://another-inline-link.com.", Snippet: "An https://inline-link.com and http://another-inline-link.com.",
}, },
{ {
Title: "", Title: "Wiki link",
Href: "Wiki link", Href: "Wiki link",
External: false, External: false,
Rels: []string{}, Rels: []string{},
Snippet: "A [[Wiki link]] is surrounded by two brackets.", Snippet: "A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].",
},
{
Title: "two brackets",
Href: "2-brackets",
External: false,
Rels: []string{},
Snippet: "A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].",
},
{
Title: `esca]]ped [chara\cters`,
Href: `esca]]ped [chara\cters`,
External: false,
Rels: []string{},
Snippet: `It can contain [[esca]\]ped \[chara\\cters]].`,
}, },
{ {
Title: "", Title: "Folgezettel link",
Href: "Folgezettel link", Href: "Folgezettel link",
External: false, External: false,
Rels: []string{"down"}, Rels: []string{"down"},
Snippet: "A [[[Folgezettel link]]] is surrounded by three brackets.", Snippet: "A [[[Folgezettel link]]] is surrounded by three brackets.",
}, },
{ {
Title: "", Title: "trailing hash",
Href: "trailing hash", Href: "trailing hash",
External: false, External: false,
Rels: []string{"down"}, Rels: []string{"down"},
Snippet: "Neuron also supports a [[trailing hash]]# for Folgezettel links.", Snippet: "Neuron also supports a [[trailing hash]]# for Folgezettel links.",
}, },
{ {
Title: "", Title: "leading hash",
Href: "leading hash", Href: "leading hash",
External: false, External: false,
Rels: []string{"up"}, Rels: []string{"up"},
Snippet: "A #[[leading hash]] is used for #uplinks.", Snippet: "A #[[leading hash]] is used for #uplinks.",
}, },
{
Title: "Trailing link",
Href: "trailing",
External: false,
Rels: []string{"down"},
Snippet: "Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]",
},
{
Title: "Leading link",
Href: "leading",
External: false,
Rels: []string{"up"},
Snippet: "Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]",
},
{ {
Title: "External links", Title: "External links",
Href: "http://example.com", Href: "http://example.com",

@ -23,6 +23,16 @@ type Link struct {
Snippet string Snippet string
} }
// LinkRelation defines the relationship between a link's source and target.
type LinkRelation string
const (
// LinkRelationDown defines the target note as a child of the source.
LinkRelationDown LinkRelation = "down"
// LinkRelationDown defines the target note as a parent of the source.
LinkRelationUp LinkRelation = "up"
)
type Parser interface { type Parser interface {
Parse(source string) (*Content, error) Parse(source string) (*Content, error)
} }

2
run

@ -1,2 +0,0 @@
#!/bin/bash
./go run main.go $@
Loading…
Cancel
Save