From 6175a73c1cf0ff5734427cb7b27963875a1e5348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Sun, 28 Feb 2021 11:21:33 +0100 Subject: [PATCH] Parse labels in [[wiki links | label]] and support escaped characters --- adapter/markdown/extensions/wikilink.go | 104 ++++++++++++++++-------- adapter/markdown/markdown_test.go | 44 ++++++++-- core/note/parse.go | 10 +++ run | 2 - 4 files changed, 117 insertions(+), 43 deletions(-) delete mode 100755 run diff --git a/adapter/markdown/extensions/wikilink.go b/adapter/markdown/extensions/wikilink.go index 64d49b9..4762f9f 100644 --- a/adapter/markdown/extensions/wikilink.go +++ b/adapter/markdown/extensions/wikilink.go @@ -3,6 +3,7 @@ package extensions import ( "strings" + "github.com/mickael-menu/zk/core/note" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" @@ -10,7 +11,7 @@ import ( "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]]#. 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 { line, _ := block.PeekLine() - openerCharCount := 0 - opened := false - closed := false - content := []byte{} - closerCharCount := 0 - endPos := 0 + var ( + href string + label string + rel note.LinkRelation + ) + + 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 - direction := 0 + appendChar := func(c byte) { + if parsingLabel { + label += string(c) + } else { + href += string(c) + } + } for i, char := range line { endPos = i 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 == '#' { - direction = -1 + rel = note.LinkRelationDown } break } if !opened { 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 '#': - direction = 1 + rel = note.LinkRelationUp continue case '[': openerCharCount += 1 @@ -72,40 +87,59 @@ func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) } opened = true - if char == ']' { - closerCharCount += 1 - if closerCharCount == openerCharCount { - closed = true - // neuron's legacy [[[Folgezettel]]]. - if closerCharCount == 3 { - direction = -1 + if !escaping { + switch char { + + case '|': // [[href | label]] + parsingLabel = true + continue + + 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 { - content = append(content, strings.Repeat("]", closerCharCount)...) - closerCharCount = 0 + } + escaping = false + + // 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 } block.Advance(endPos) - link := ast.NewLink() - link.Destination = content - - // Title will be parsed as the link's rels. - switch direction { - case -1: - link.Title = []byte("down") - case 1: - link.Title = []byte("up") + href = strings.TrimSpace(href) + label = strings.TrimSpace(label) + if len(label) == 0 { + label = href } + 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 } diff --git a/adapter/markdown/markdown_test.go b/adapter/markdown/markdown_test.go index d46ab11..aecf5f4 100644 --- a/adapter/markdown/markdown_test.go +++ b/adapter/markdown/markdown_test.go @@ -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. -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. @@ -178,6 +180,8 @@ Neuron also supports a [[trailing hash]]# for Folgezettel links. 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). `, []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.", }, { - Title: "", + Title: "Wiki link", Href: "Wiki link", External: false, 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", External: false, Rels: []string{"down"}, Snippet: "A [[[Folgezettel link]]] is surrounded by three brackets.", }, { - Title: "", + Title: "trailing hash", Href: "trailing hash", External: false, Rels: []string{"down"}, Snippet: "Neuron also supports a [[trailing hash]]# for Folgezettel links.", }, { - Title: "", + Title: "leading hash", Href: "leading hash", External: false, Rels: []string{"up"}, 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", Href: "http://example.com", diff --git a/core/note/parse.go b/core/note/parse.go index 770d231..13bab9c 100644 --- a/core/note/parse.go +++ b/core/note/parse.go @@ -23,6 +23,16 @@ type Link struct { 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 { Parse(source string) (*Content, error) } diff --git a/run b/run deleted file mode 100755 index f53202e..0000000 --- a/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -./go run main.go $@