mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-08-29 09:02:31 +00:00
Add inline links
This commit is contained in:
@@ -127,7 +127,7 @@ func (img Img) ToHtml() (html string) {
|
||||
if i > 0 {
|
||||
html += `<br>`
|
||||
}
|
||||
html += ParagraphToHtml(line)
|
||||
html += ParagraphToHtml(img.hyphaName, line)
|
||||
}
|
||||
}
|
||||
html += `</figcaption>`
|
||||
|
@@ -12,14 +12,14 @@ func TestParseStartOfEntry(t *testing.T) {
|
||||
entry imgEntry
|
||||
followedByDesc bool
|
||||
}{
|
||||
{"apple", imgEntry{"apple", "", "", ""}, false},
|
||||
{"pear:", imgEntry{"pear", "", "", ""}, false},
|
||||
{"яблоко: 30*60", imgEntry{"яблоко", "30", "60", ""}, false},
|
||||
{"груша : 65 ", imgEntry{"груша", "65", "", ""}, false},
|
||||
{"жеронимо : 30 { full desc }", imgEntry{"жеронимо", "30", "", " full desc "}, false},
|
||||
{"жорно жованна : *5555 {partial description", imgEntry{"жорно_жованна", "", "5555", "partial description"}, true},
|
||||
{"иноске : {full}", imgEntry{"иноске", "", "", "full"}, false},
|
||||
{"j:{partial", imgEntry{"j", "", "", "partial"}, true},
|
||||
{"apple", imgEntry{"/binary/apple", "", "", ""}, false},
|
||||
{"pear|", imgEntry{"/binary/pear", "", "", ""}, false},
|
||||
{"яблоко| 30*60", imgEntry{"/binary/яблоко", "30", "60", ""}, false},
|
||||
{"груша | 65 ", imgEntry{"/binary/груша", "65", "", ""}, false},
|
||||
{"жеронимо | 30 { full desc }", imgEntry{"/binary/жеронимо", "30", "", " full desc "}, false},
|
||||
{"жорно жованна | *5555 {partial description", imgEntry{"/binary/жорно_жованна", "", "5555", "partial description"}, true},
|
||||
{"иноске | {full}", imgEntry{"/binary/иноске", "", "", "full"}, false},
|
||||
{"j|{partial", imgEntry{"/binary/j", "", "", "partial"}, true},
|
||||
}
|
||||
for _, triplet := range tests {
|
||||
entry, followedByDesc := img.parseStartOfEntry(triplet.line)
|
||||
|
@@ -3,7 +3,6 @@ package markup
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -31,50 +30,6 @@ type Line struct {
|
||||
contents interface{}
|
||||
}
|
||||
|
||||
// Parse markup line starting with "=>" according to wikilink rules.
|
||||
// See http://localhost:1737/page/wikilink
|
||||
func wikilink(src string, state *GemLexerState) (href, text, class string) {
|
||||
src = strings.TrimSpace(remover("=>")(src))
|
||||
if src == "" {
|
||||
return
|
||||
}
|
||||
// Href is text after => till first whitespace
|
||||
href = strings.Fields(src)[0]
|
||||
// Text is everything after whitespace.
|
||||
// If there's no text, make it same as href
|
||||
if text = strings.TrimPrefix(src, href); text == "" {
|
||||
text = href
|
||||
}
|
||||
|
||||
class = "wikilink_internal"
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(href, "./"):
|
||||
hyphaName := canonicalName(path.Join(
|
||||
state.name, strings.TrimPrefix(href, "./")))
|
||||
if !HyphaExists(hyphaName) {
|
||||
class += " wikilink_new"
|
||||
}
|
||||
href = path.Join("/page", hyphaName)
|
||||
case strings.HasPrefix(href, "../"):
|
||||
hyphaName := canonicalName(path.Join(
|
||||
path.Dir(state.name), strings.TrimPrefix(href, "../")))
|
||||
if !HyphaExists(hyphaName) {
|
||||
class += " wikilink_new"
|
||||
}
|
||||
href = path.Join("/page", hyphaName)
|
||||
case strings.HasPrefix(href, "/"):
|
||||
case strings.ContainsRune(href, ':'):
|
||||
class = "wikilink_external"
|
||||
default:
|
||||
if !HyphaExists(canonicalName(href)) {
|
||||
class += " wikilink_new"
|
||||
}
|
||||
href = path.Join("/page", href)
|
||||
}
|
||||
return href, strings.TrimSpace(text), class
|
||||
}
|
||||
|
||||
func lex(name, content string) (ast []Line) {
|
||||
var state = GemLexerState{name: name}
|
||||
|
||||
@@ -141,7 +96,7 @@ preformattedState:
|
||||
listState:
|
||||
switch {
|
||||
case startsWith("* "):
|
||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(line[2:]))
|
||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[2:]))
|
||||
case startsWith("```"):
|
||||
state.where = "pre"
|
||||
addLine(state.buf + "</ul>")
|
||||
@@ -157,7 +112,7 @@ listState:
|
||||
numberState:
|
||||
switch {
|
||||
case startsWith("*. "):
|
||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(line[3:]))
|
||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[3:]))
|
||||
case startsWith("```"):
|
||||
state.where = "pre"
|
||||
addLine(state.buf + "</ol>")
|
||||
@@ -209,9 +164,9 @@ normalState:
|
||||
addLine(fmt.Sprintf(
|
||||
"<blockquote id='%d'>%s</blockquote>", state.id, remover(">")(line)))
|
||||
case startsWith("=>"):
|
||||
source, content, class := wikilink(line, state)
|
||||
href, text, class := Rocketlink(line, state.name)
|
||||
addLine(fmt.Sprintf(
|
||||
`<p><a id='%d' class='%s' href="%s">%s</a></p>`, state.id, class, source, content))
|
||||
`<p><a id='%d' class='rocketlink %s' href="%s">%s</a></p>`, state.id, class, href, text))
|
||||
|
||||
case startsWith("<="):
|
||||
addLine(parseTransclusion(line, state.name))
|
||||
@@ -221,6 +176,6 @@ normalState:
|
||||
state.where = "img"
|
||||
state.img = ImgFromFirstLine(line, state.name)
|
||||
default:
|
||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(line)))
|
||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
||||
}
|
||||
}
|
||||
|
@@ -41,13 +41,13 @@ func TestLex(t *testing.T) {
|
||||
</ul>`},
|
||||
{6, "<p id='6'>text</p>"},
|
||||
{7, "<p id='7'>more text</p>"},
|
||||
{8, `<p><a id='8' class='wikilink_internal' href="/page/Pear">some link</a></p>`},
|
||||
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/Pear">some link</a></p>`},
|
||||
{9, `<ul id='9'>
|
||||
<li>lin"+</li>
|
||||
</ul>`},
|
||||
{10, `<pre id='10' alt='alt text goes here' class='codeblock'><code>=> preformatted text
|
||||
where markup is not lexed</code></pre>`},
|
||||
{11, `<p><a id='11' class='wikilink_internal' href="/page/linking">linking</a></p>`},
|
||||
{11, `<p><a id='11' class='rocketlink wikilink_internal' href="/page/linking">linking</a></p>`},
|
||||
{12, "<p id='12'>text</p>"},
|
||||
{13, `<pre id='13' alt='' class='codeblock'><code>()
|
||||
/\</code></pre>`},
|
||||
@@ -56,11 +56,11 @@ where markup is not lexed</code></pre>`},
|
||||
hyphaName: "Apple",
|
||||
inDesc: false,
|
||||
entries: []imgEntry{
|
||||
{"hypha1", "", "", ""},
|
||||
{"hypha2", "", "", ""},
|
||||
{"hypha3", "60", "", ""},
|
||||
{"hypha4", "", "", " line1\nline2\n"},
|
||||
{"hypha5", "", "", "\nstate of minnesota\n"},
|
||||
{"/binary/hypha1", "", "", ""},
|
||||
{"/binary/hypha2", "", "", ""},
|
||||
{"/binary/hypha3", "60", "", ""},
|
||||
{"/binary/hypha4", "", "", " line1\nline2\n"},
|
||||
{"/binary/hypha5", "", "", "\nstate of minnesota\n"},
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
49
markup/link.go
Normal file
49
markup/link.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name.
|
||||
//
|
||||
// => addr display
|
||||
// [[addr|display]]
|
||||
func LinkParts(addr, display, hyphaName string) (href, text, class string) {
|
||||
if display == "" {
|
||||
text = addr
|
||||
} else {
|
||||
text = strings.TrimSpace(display)
|
||||
}
|
||||
class = "wikilink_internal"
|
||||
|
||||
switch {
|
||||
case strings.ContainsRune(addr, ':'):
|
||||
return addr, text, "wikilink_external"
|
||||
case strings.HasPrefix(addr, "/"):
|
||||
return addr, text, class
|
||||
case strings.HasPrefix(addr, "./"):
|
||||
hyphaName = canonicalName(path.Join(hyphaName, addr[2:]))
|
||||
case strings.HasPrefix(addr, "../"):
|
||||
hyphaName = canonicalName(path.Join(path.Dir(hyphaName), addr[3:]))
|
||||
default:
|
||||
hyphaName = canonicalName(addr)
|
||||
}
|
||||
if !HyphaExists(hyphaName) {
|
||||
class += " wikilink_new"
|
||||
}
|
||||
return "/page/" + hyphaName, text, class
|
||||
}
|
||||
|
||||
// Parse markup line starting with "=>" according to wikilink rules.
|
||||
// See http://localhost:1737/page/wikilink
|
||||
func Rocketlink(src, hyphaName string) (href, text, class string) {
|
||||
src = strings.TrimSpace(src[2:]) // Drop =>
|
||||
if src == "" {
|
||||
return
|
||||
}
|
||||
// Href is text after => till first whitespace
|
||||
addr := strings.Fields(src)[0]
|
||||
display := strings.TrimPrefix(src, addr)
|
||||
return LinkParts(addr, display, hyphaName)
|
||||
}
|
@@ -17,6 +17,7 @@ const (
|
||||
spanSuper
|
||||
spanSub
|
||||
spanMark
|
||||
spanLink
|
||||
)
|
||||
|
||||
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
|
||||
@@ -32,7 +33,33 @@ func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, o
|
||||
}
|
||||
}
|
||||
|
||||
// getTextNode splits the `p` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = p`, `rest = ""`. It handles escaping with backslash.
|
||||
func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
||||
input.Next(2)
|
||||
var (
|
||||
escaping = false
|
||||
addrBuf = bytes.Buffer{}
|
||||
displayBuf = bytes.Buffer{}
|
||||
currBuf = &addrBuf
|
||||
)
|
||||
for input.Len() != 0 {
|
||||
b, _ := input.ReadByte()
|
||||
if escaping {
|
||||
currBuf.WriteByte(b)
|
||||
escaping = false
|
||||
} else if b == '|' && currBuf == &addrBuf {
|
||||
currBuf = &displayBuf
|
||||
} else if b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
||||
input.Next(1)
|
||||
break
|
||||
} else {
|
||||
currBuf.WriteByte(b)
|
||||
}
|
||||
}
|
||||
href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName)
|
||||
return fmt.Sprintf(`<a href="%s" class="%s">%s</a>`, href, class, html.EscapeString(text))
|
||||
}
|
||||
|
||||
// getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash.
|
||||
func getTextNode(input *bytes.Buffer) string {
|
||||
var (
|
||||
textNodeBuffer = bytes.Buffer{}
|
||||
@@ -51,7 +78,7 @@ func getTextNode(input *bytes.Buffer) string {
|
||||
escaping = false
|
||||
} else if b == '\\' {
|
||||
escaping = true
|
||||
} else if strings.IndexByte("/*`^,!", b) >= 0 {
|
||||
} else if strings.IndexByte("/*`^,![", b) >= 0 {
|
||||
input.UnreadByte()
|
||||
break
|
||||
} else {
|
||||
@@ -61,7 +88,7 @@ func getTextNode(input *bytes.Buffer) string {
|
||||
return textNodeBuffer.String()
|
||||
}
|
||||
|
||||
func ParagraphToHtml(input string) string {
|
||||
func ParagraphToHtml(hyphaName, input string) string {
|
||||
var (
|
||||
p = bytes.NewBufferString(input)
|
||||
ret strings.Builder
|
||||
@@ -73,6 +100,7 @@ func ParagraphToHtml(input string) string {
|
||||
spanSuper: false,
|
||||
spanSub: false,
|
||||
spanMark: false,
|
||||
spanLink: false,
|
||||
}
|
||||
startsWith = func(t string) bool {
|
||||
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
||||
@@ -99,6 +127,8 @@ func ParagraphToHtml(input string) string {
|
||||
case startsWith("!!"):
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||
p.Next(2)
|
||||
case startsWith("[["):
|
||||
ret.WriteString(getLinkNode(p, hyphaName))
|
||||
default:
|
||||
ret.WriteString(html.EscapeString(getTextNode(p)))
|
||||
}
|
||||
@@ -119,6 +149,8 @@ func ParagraphToHtml(input string) string {
|
||||
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
|
||||
case spanMark:
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||
case spanLink:
|
||||
ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,10 +35,15 @@ func TestParagraphToHtml(t *testing.T) {
|
||||
{"this is a left **bold", "this is a left <strong>bold</strong>"},
|
||||
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
|
||||
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
|
||||
{"A [[simple]] link", `A <a href="/page/simple" class="wikilink_internal">simple</a> link`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if ParagraphToHtml(test[0]) != test[1] {
|
||||
t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml(test[0])))
|
||||
if ParagraphToHtml("Apple", test[0]) != test[1] {
|
||||
t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml("Apple", test[0])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
HyphaExists = func(_ string) bool { return true }
|
||||
}
|
||||
|
8
markup/testdata/test.myco
vendored
8
markup/testdata/test.myco
vendored
@@ -25,13 +25,13 @@ text
|
||||
|
||||
img {
|
||||
hypha1
|
||||
hypha2:
|
||||
hypha3: 60
|
||||
hypha4: { line1
|
||||
hypha2|
|
||||
hypha3| 60
|
||||
hypha4| { line1
|
||||
line2
|
||||
} this is ignored
|
||||
|
||||
hypha5: {
|
||||
hypha5| {
|
||||
state of minnesota
|
||||
}
|
||||
}
|
||||
|
Submodule metarrhiza updated: 9fa5334ee9...d78a90718a
Reference in New Issue
Block a user