0dede6a329
Phase out url_prefix and url_name. Eliminate some boilerplate and allocations while we're at it. Reduce the number of conditional statements in url validator loops. Better parsing of Site.URL to handle user error better. You will have to run the patcher / updater for this commit.
389 lines
8.8 KiB
Go
389 lines
8.8 KiB
Go
package extend
|
|
|
|
import (
|
|
"strings"
|
|
|
|
c "github.com/Azareal/Gosora/common"
|
|
)
|
|
|
|
var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings
|
|
var markdownUnclosedElement []byte
|
|
|
|
var markdownBoldTagOpen []byte
|
|
var markdownBoldTagClose []byte
|
|
var markdownItalicTagOpen []byte
|
|
var markdownItalicTagClose []byte
|
|
var markdownUnderlineTagOpen []byte
|
|
var markdownUnderlineTagClose []byte
|
|
var markdownStrikeTagOpen []byte
|
|
var markdownStrikeTagClose []byte
|
|
var markdownQuoteTagOpen []byte
|
|
var markdownQuoteTagClose []byte
|
|
var markdownSpoilerTagOpen []byte
|
|
var markdownSpoilerTagClose []byte
|
|
var markdownH1TagOpen []byte
|
|
var markdownH1TagClose []byte
|
|
|
|
func init() {
|
|
c.Plugins.Add(&c.Plugin{UName: "markdown", Name: "Markdown", Author: "Azareal", URL: "https://github.com/Azareal", Init: InitMarkdown, Deactivate: deactivateMarkdown})
|
|
}
|
|
|
|
func InitMarkdown(pl *c.Plugin) error {
|
|
pl.AddHook("parse_assign", MarkdownParse)
|
|
|
|
markdownUnclosedElement = []byte("<red>[Unclosed Element]</red>")
|
|
|
|
markdownBoldTagOpen = []byte("<b>")
|
|
markdownBoldTagClose = []byte("</b>")
|
|
markdownItalicTagOpen = []byte("<i>")
|
|
markdownItalicTagClose = []byte("</i>")
|
|
markdownUnderlineTagOpen = []byte("<u>")
|
|
markdownUnderlineTagClose = []byte("</u>")
|
|
markdownStrikeTagOpen = []byte("<s>")
|
|
markdownStrikeTagClose = []byte("</s>")
|
|
markdownQuoteTagOpen = []byte("<blockquote>")
|
|
markdownQuoteTagClose = []byte("</blockquote>")
|
|
markdownSpoilerTagOpen = []byte("<spoiler>")
|
|
markdownSpoilerTagClose = []byte("</spoiler>")
|
|
markdownH1TagOpen = []byte("<h2>")
|
|
markdownH1TagClose = []byte("</h2>")
|
|
return nil
|
|
}
|
|
|
|
func deactivateMarkdown(pl *c.Plugin) {
|
|
pl.RemoveHook("parse_assign", MarkdownParse)
|
|
}
|
|
|
|
// An adapter for the parser, so that the parser can call itself recursively.
|
|
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
|
|
func MarkdownParse(msg string) string {
|
|
msg = _markdownParse(msg+" ", 0)
|
|
if msg[len(msg)-1] == ' ' {
|
|
msg = msg[:len(msg)-1]
|
|
}
|
|
return msg
|
|
}
|
|
|
|
// Under Construction!
|
|
func _markdownParse(msg string, n int) string {
|
|
if n > markdownMaxDepth {
|
|
return "<red>[Markdown Error: Overflowed the max depth of 20]</red>"
|
|
}
|
|
|
|
var outbytes []byte
|
|
var lastElement int
|
|
breaking := false
|
|
c.DebugLogf("Initial Message: %+v\n", strings.Replace(msg, "\r", "\\r", -1))
|
|
|
|
for index := 0; index < len(msg); index++ {
|
|
simpleMatch := func(char byte, o []byte, c []byte) {
|
|
startIndex := index
|
|
if (index + 1) >= len(msg) {
|
|
breaking = true
|
|
return
|
|
}
|
|
|
|
index++
|
|
index = markdownSkipUntilChar(msg, index, char)
|
|
if (index-(startIndex+1)) < 1 || index >= len(msg) {
|
|
breaking = true
|
|
return
|
|
}
|
|
|
|
sIndex := startIndex + 1
|
|
lIndex := index
|
|
index++
|
|
|
|
outbytes = append(outbytes, msg[lastElement:startIndex]...)
|
|
outbytes = append(outbytes, o...)
|
|
// TODO: Implement this without as many type conversions
|
|
outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...)
|
|
outbytes = append(outbytes, c...)
|
|
|
|
lastElement = index
|
|
index--
|
|
}
|
|
|
|
startLine := func() {
|
|
startIndex := index
|
|
if (index + 1) >= len(msg) /*|| (index + 2) >= len(msg)*/ {
|
|
breaking = true
|
|
return
|
|
}
|
|
index++
|
|
|
|
index = markdownSkipUntilNotChar(msg, index, 32)
|
|
if (index + 1) >= len(msg) {
|
|
breaking = true
|
|
return
|
|
}
|
|
//index++
|
|
|
|
index = markdownSkipUntilStrongSpace(msg, index)
|
|
sIndex := startIndex + 1
|
|
lIndex := index
|
|
index++
|
|
|
|
outbytes = append(outbytes, msg[lastElement:startIndex]...)
|
|
outbytes = append(outbytes, markdownH1TagOpen...)
|
|
// TODO: Implement this without as many type conversions
|
|
//fmt.Println("msg[sIndex:lIndex]:", string(msg[sIndex:lIndex]))
|
|
// TODO: Quick hack to eliminate trailing spaces...
|
|
outbytes = append(outbytes, []byte(strings.TrimSpace(_markdownParse(msg[sIndex:lIndex], n+1)))...)
|
|
outbytes = append(outbytes, markdownH1TagClose...)
|
|
|
|
lastElement = index
|
|
index--
|
|
}
|
|
|
|
switch msg[index] {
|
|
// TODO: Do something slightly less hacky for skipping URLs
|
|
case '/':
|
|
if len(msg) > (index+2) && msg[index+1] == '/' {
|
|
for ; index < len(msg) && msg[index] != ' '; index++ {
|
|
}
|
|
index--
|
|
continue
|
|
}
|
|
case '_':
|
|
simpleMatch('_', markdownUnderlineTagOpen, markdownUnderlineTagClose)
|
|
if breaking {
|
|
break
|
|
}
|
|
case '~':
|
|
simpleMatch('~', markdownStrikeTagOpen, markdownStrikeTagClose)
|
|
if breaking {
|
|
break
|
|
}
|
|
case '*':
|
|
startIndex := index
|
|
italic := true
|
|
bold := false
|
|
if (index + 2) < len(msg) {
|
|
if msg[index+1] == '*' {
|
|
bold = true
|
|
index++
|
|
if msg[index+1] != '*' {
|
|
italic = false
|
|
} else {
|
|
index++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Does the string terminate abruptly?
|
|
if (index + 1) >= len(msg) {
|
|
break
|
|
}
|
|
index++
|
|
|
|
index = markdownSkipUntilAsterisk(msg, index)
|
|
if index >= len(msg) {
|
|
break
|
|
}
|
|
|
|
preBreak := func() {
|
|
outbytes = append(outbytes, msg[lastElement:startIndex]...)
|
|
lastElement = startIndex
|
|
}
|
|
|
|
sIndex := startIndex
|
|
lIndex := index
|
|
if bold && italic {
|
|
if (index + 3) >= len(msg) {
|
|
preBreak()
|
|
break
|
|
}
|
|
index += 3
|
|
sIndex += 3
|
|
} else if bold {
|
|
if (index + 2) >= len(msg) {
|
|
preBreak()
|
|
break
|
|
}
|
|
index += 2
|
|
sIndex += 2
|
|
} else {
|
|
if (index + 1) >= len(msg) {
|
|
preBreak()
|
|
break
|
|
}
|
|
index++
|
|
sIndex++
|
|
}
|
|
|
|
if lIndex <= sIndex {
|
|
preBreak()
|
|
break
|
|
}
|
|
if sIndex < 0 || lIndex < 0 {
|
|
preBreak()
|
|
break
|
|
}
|
|
outbytes = append(outbytes, msg[lastElement:startIndex]...)
|
|
|
|
if bold {
|
|
outbytes = append(outbytes, markdownBoldTagOpen...)
|
|
}
|
|
if italic {
|
|
outbytes = append(outbytes, markdownItalicTagOpen...)
|
|
}
|
|
|
|
// TODO: Implement this without as many type conversions
|
|
outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...)
|
|
|
|
if italic {
|
|
outbytes = append(outbytes, markdownItalicTagClose...)
|
|
}
|
|
if bold {
|
|
outbytes = append(outbytes, markdownBoldTagClose...)
|
|
}
|
|
|
|
lastElement = index
|
|
index--
|
|
case '\\':
|
|
if (index + 1) < len(msg) {
|
|
if isMarkdownStartChar(msg[index+1]) && msg[index+1] != '\\' {
|
|
outbytes = append(outbytes, msg[lastElement:index]...)
|
|
index++
|
|
lastElement = index
|
|
}
|
|
}
|
|
// TODO: Add a inline quote variant
|
|
case '`':
|
|
simpleMatch('`', markdownQuoteTagOpen, markdownQuoteTagClose)
|
|
if breaking {
|
|
break
|
|
}
|
|
// TODO: Might need to be double pipe
|
|
case '|':
|
|
simpleMatch('|', markdownSpoilerTagOpen, markdownSpoilerTagClose)
|
|
if breaking {
|
|
break
|
|
}
|
|
case 10: // newline
|
|
if (index + 1) >= len(msg) {
|
|
break
|
|
}
|
|
index++
|
|
|
|
if msg[index] != '#' {
|
|
continue
|
|
}
|
|
startLine()
|
|
if breaking {
|
|
break
|
|
}
|
|
case '#':
|
|
if index != 0 {
|
|
continue
|
|
}
|
|
startLine()
|
|
if breaking {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(outbytes) == 0 {
|
|
return msg
|
|
} else if lastElement < (len(msg) - 1) {
|
|
msg = string(outbytes) + msg[lastElement:]
|
|
return msg
|
|
}
|
|
return string(outbytes)
|
|
}
|
|
|
|
func isMarkdownStartChar(ch byte) bool { // char
|
|
return ch == '\\' || ch == '~' || ch == '_' || ch == 10 || ch == '`' || ch == '*' || ch == '|'
|
|
}
|
|
|
|
func markdownFindChar(data string, index int, char byte) bool {
|
|
for ; index < len(data); index++ {
|
|
item := data[index]
|
|
if item > 32 {
|
|
return (item == char)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func markdownSkipUntilChar(data string, index int, char byte) int {
|
|
for ; index < len(data); index++ {
|
|
if data[index] == char {
|
|
break
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func markdownSkipUntilNotChar(data string, index int, char byte) int {
|
|
for ; index < len(data); index++ {
|
|
if data[index] != char {
|
|
break
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func markdownSkipUntilStrongSpace(data string, index int) int {
|
|
inSpace := false
|
|
for ; index < len(data); index++ {
|
|
if inSpace && data[index] == 32 {
|
|
index--
|
|
break
|
|
} else if data[index] == 32 {
|
|
inSpace = true
|
|
} else if data[index] < 32 {
|
|
break
|
|
} else {
|
|
inSpace = false
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func markdownSkipUntilAsterisk(data string, index int) int {
|
|
SwitchLoop:
|
|
for ; index < len(data); index++ {
|
|
switch data[index] {
|
|
case 10:
|
|
if ((index + 1) < len(data)) && markdownFindChar(data, index, '*') {
|
|
index = markdownSkipList(data, index)
|
|
}
|
|
case '*':
|
|
break SwitchLoop
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
// plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them
|
|
func markdownSkipList(data string, index int) int {
|
|
var lastNewline int
|
|
datalen := len(data)
|
|
for ; index < datalen; index++ {
|
|
SkipListInnerLoop:
|
|
if data[index] == 10 {
|
|
lastNewline = index
|
|
for ; index < datalen; index++ {
|
|
if data[index] > 32 {
|
|
break
|
|
} else if data[index] == 10 {
|
|
goto SkipListInnerLoop
|
|
}
|
|
}
|
|
if index >= datalen {
|
|
if data[index] != '*' && data[index] != '-' {
|
|
if (lastNewline + 1) < datalen {
|
|
return lastNewline + 1
|
|
}
|
|
return lastNewline
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return index
|
|
}
|