2020-10-30 13:25:48 +00:00
|
|
|
package markup
|
2020-08-05 15:08:59 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"html"
|
|
|
|
"strings"
|
2021-03-02 16:36:57 +00:00
|
|
|
|
|
|
|
"github.com/bouncepaw/mycorrhiza/util"
|
2020-08-05 15:08:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// HyphaExists holds function that checks that a hypha is present.
|
|
|
|
var HyphaExists func(string) bool
|
|
|
|
|
2020-12-17 12:59:59 +00:00
|
|
|
//
|
|
|
|
var HyphaImageForOG func(string) string
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// HyphaAccess holds function that accesses a hypha by its name.
|
|
|
|
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
|
|
|
|
|
2020-11-26 18:41:26 +00:00
|
|
|
// HyphaIterate is a function that iterates all hypha names existing.
|
|
|
|
var HyphaIterate func(func(string))
|
|
|
|
|
2020-10-30 13:25:48 +00:00
|
|
|
// GemLexerState is used by markup parser to remember what is going on.
|
2020-08-05 15:08:59 +00:00
|
|
|
type GemLexerState struct {
|
|
|
|
// Name of hypha being parsed
|
|
|
|
name string
|
|
|
|
where string // "", "list", "pre"
|
|
|
|
// Line id
|
|
|
|
id int
|
|
|
|
buf string
|
2020-11-03 15:41:50 +00:00
|
|
|
// Temporaries
|
2021-01-01 04:07:56 +00:00
|
|
|
img *Img
|
|
|
|
table *Table
|
2021-01-20 15:05:09 +00:00
|
|
|
list *List
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Line struct {
|
|
|
|
id int
|
2020-12-17 12:59:59 +00:00
|
|
|
// interface{} may be bad. TODO: a proper type
|
2020-08-05 15:08:59 +00:00
|
|
|
contents interface{}
|
|
|
|
}
|
|
|
|
|
2020-12-17 12:59:59 +00:00
|
|
|
func (md *MycoDoc) lex() (ast []Line) {
|
|
|
|
var state = GemLexerState{name: md.hyphaName}
|
2020-08-05 15:08:59 +00:00
|
|
|
|
2020-12-17 12:59:59 +00:00
|
|
|
for _, line := range append(strings.Split(md.contents, "\n"), "") {
|
|
|
|
lineToAST(line, &state, &ast)
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
|
|
|
return ast
|
|
|
|
}
|
|
|
|
|
2020-10-30 13:25:48 +00:00
|
|
|
// Lex `line` in markup and save it to `ast` using `state`.
|
2020-12-17 12:59:59 +00:00
|
|
|
func lineToAST(line string, state *GemLexerState, ast *[]Line) {
|
2020-08-28 19:17:32 +00:00
|
|
|
addLine := func(text interface{}) {
|
|
|
|
*ast = append(*ast, Line{id: state.id, contents: text})
|
|
|
|
}
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded := func() {
|
|
|
|
if state.where == "p" {
|
|
|
|
state.where = ""
|
|
|
|
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, strings.ReplaceAll(ParagraphToHtml(state.name, state.buf), "\n", "<br>")))
|
|
|
|
state.buf = ""
|
|
|
|
}
|
|
|
|
}
|
2020-08-28 19:17:32 +00:00
|
|
|
|
2020-11-21 08:52:20 +00:00
|
|
|
// Process empty lines depending on the current state
|
2020-08-05 15:08:59 +00:00
|
|
|
if "" == strings.TrimSpace(line) {
|
2020-11-21 08:52:20 +00:00
|
|
|
switch state.where {
|
|
|
|
case "pre":
|
|
|
|
state.buf += "\n"
|
2020-12-15 18:59:36 +00:00
|
|
|
case "launchpad":
|
|
|
|
state.where = ""
|
|
|
|
addLine(state.buf + "</ul>")
|
2021-01-03 20:39:54 +00:00
|
|
|
case "p":
|
|
|
|
addParagraphIfNeeded()
|
2020-08-28 19:17:32 +00:00
|
|
|
}
|
2020-08-05 15:08:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
startsWith := func(token string) bool {
|
|
|
|
return strings.HasPrefix(line, token)
|
|
|
|
}
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading := func(i int) {
|
2021-03-02 16:36:57 +00:00
|
|
|
id := util.LettersNumbersOnly(line[i+1:])
|
|
|
|
addLine(fmt.Sprintf(`<h%d id='%d'>%s<a href="#%s" id="%s" class="heading__link"></a></h%d>`, i, state.id, ParagraphToHtml(state.name, line[i+1:]), id, id, i))
|
2020-11-12 18:21:49 +00:00
|
|
|
}
|
2020-08-05 15:08:59 +00:00
|
|
|
|
|
|
|
// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code.
|
|
|
|
switch state.where {
|
2020-11-03 15:41:50 +00:00
|
|
|
case "img":
|
|
|
|
goto imgState
|
2021-01-01 04:07:56 +00:00
|
|
|
case "table":
|
|
|
|
goto tableState
|
2020-08-05 15:08:59 +00:00
|
|
|
case "list":
|
|
|
|
goto listState
|
2021-01-20 15:05:09 +00:00
|
|
|
case "pre":
|
|
|
|
goto preformattedState
|
2020-12-15 18:59:36 +00:00
|
|
|
case "launchpad":
|
|
|
|
goto launchpadState
|
2021-01-03 20:39:54 +00:00
|
|
|
default: // "p" or ""
|
2020-08-05 15:08:59 +00:00
|
|
|
goto normalState
|
|
|
|
}
|
|
|
|
|
2020-11-03 15:41:50 +00:00
|
|
|
imgState:
|
|
|
|
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
|
|
|
|
state.where = ""
|
2020-11-26 18:41:26 +00:00
|
|
|
addLine(*state.img)
|
2020-11-03 15:41:50 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
|
2021-01-01 04:07:56 +00:00
|
|
|
tableState:
|
|
|
|
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
|
|
|
|
state.where = ""
|
|
|
|
addLine(*state.table)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
listState:
|
2021-01-20 15:05:09 +00:00
|
|
|
if done := state.list.Parse(line); done {
|
|
|
|
state.list.Finalize()
|
2020-08-05 15:08:59 +00:00
|
|
|
state.where = ""
|
|
|
|
goto normalState
|
|
|
|
}
|
|
|
|
return
|
|
|
|
|
2021-01-20 15:05:09 +00:00
|
|
|
preformattedState:
|
2020-11-03 15:47:22 +00:00
|
|
|
switch {
|
|
|
|
case startsWith("```"):
|
|
|
|
state.where = ""
|
2021-01-20 15:05:09 +00:00
|
|
|
state.buf = strings.TrimSuffix(state.buf, "\n")
|
|
|
|
addLine(state.buf + "</code></pre>")
|
|
|
|
state.buf = ""
|
|
|
|
default:
|
|
|
|
state.buf += html.EscapeString(line) + "\n"
|
2020-11-03 15:47:22 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
|
2020-12-15 18:59:36 +00:00
|
|
|
launchpadState:
|
|
|
|
switch {
|
|
|
|
case startsWith("=>"):
|
2021-01-24 08:18:59 +00:00
|
|
|
href, text, class := Rocketlink(line, state.name)
|
2021-02-19 09:23:57 +00:00
|
|
|
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a href="%s" class="rocketlink %s">%s</a></li>`, href, class, text)
|
2020-12-15 18:59:36 +00:00
|
|
|
case startsWith("```"):
|
|
|
|
state.where = "pre"
|
|
|
|
addLine(state.buf + "</ul>")
|
|
|
|
state.id++
|
|
|
|
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
|
|
|
default:
|
|
|
|
state.where = ""
|
|
|
|
addLine(state.buf + "</ul>")
|
|
|
|
goto normalState
|
|
|
|
}
|
|
|
|
return
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
normalState:
|
|
|
|
state.id++
|
|
|
|
switch {
|
|
|
|
|
|
|
|
case startsWith("```"):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-08-05 15:08:59 +00:00
|
|
|
state.where = "pre"
|
|
|
|
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
|
|
|
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("###### "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(6)
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("##### "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(5)
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("#### "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(4)
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("### "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(3)
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("## "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(2)
|
2020-10-30 13:25:48 +00:00
|
|
|
case startsWith("# "):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-12 18:21:49 +00:00
|
|
|
addHeading(1)
|
2020-08-05 15:08:59 +00:00
|
|
|
|
|
|
|
case startsWith(">"):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2021-01-03 21:18:23 +00:00
|
|
|
addLine(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"<blockquote id='%d'>%s</blockquote>",
|
|
|
|
state.id,
|
|
|
|
ParagraphToHtml(state.name, remover(">")(line)),
|
|
|
|
),
|
|
|
|
)
|
2020-08-05 15:08:59 +00:00
|
|
|
case startsWith("=>"):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-12-15 18:59:36 +00:00
|
|
|
state.where = "launchpad"
|
|
|
|
state.buf = fmt.Sprintf("<ul class='launchpad' id='%d'>\n", state.id)
|
|
|
|
goto launchpadState
|
2020-08-05 15:08:59 +00:00
|
|
|
|
|
|
|
case startsWith("<="):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-08-05 15:08:59 +00:00
|
|
|
addLine(parseTransclusion(line, state.name))
|
2021-03-05 08:29:39 +00:00
|
|
|
case MatchesHorizontalLine(line):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-03 12:27:26 +00:00
|
|
|
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
|
2021-01-20 15:05:09 +00:00
|
|
|
case MatchesList(line):
|
|
|
|
addParagraphIfNeeded()
|
|
|
|
list, _ := NewList(line, state.name)
|
|
|
|
state.where = "list"
|
|
|
|
state.list = list
|
|
|
|
addLine(state.list)
|
2020-11-03 15:41:50 +00:00
|
|
|
case MatchesImg(line):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2020-11-26 18:41:26 +00:00
|
|
|
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
|
|
|
|
if shouldGoBackToNormal {
|
|
|
|
addLine(*img)
|
|
|
|
} else {
|
|
|
|
state.where = "img"
|
|
|
|
state.img = img
|
|
|
|
}
|
2021-01-01 04:07:56 +00:00
|
|
|
case MatchesTable(line):
|
2021-01-03 20:39:54 +00:00
|
|
|
addParagraphIfNeeded()
|
2021-01-01 04:07:56 +00:00
|
|
|
state.where = "table"
|
|
|
|
state.table = TableFromFirstLine(line, state.name)
|
2021-01-03 20:39:54 +00:00
|
|
|
|
|
|
|
case state.where == "p":
|
|
|
|
state.buf += "\n" + line
|
2020-08-05 15:08:59 +00:00
|
|
|
default:
|
2021-01-03 20:39:54 +00:00
|
|
|
state.where = "p"
|
|
|
|
state.buf = line
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
|
|
|
}
|