1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-12 13:30:26 +00:00
mycorrhiza/markup/lexer.go

262 lines
6.2 KiB
Go
Raw Normal View History

2020-10-30 13:25:48 +00:00
package markup
import (
"fmt"
"html"
"strings"
)
// 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
// 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.
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
}
type Line struct {
id int
2020-12-17 12:59:59 +00:00
// interface{} may be bad. TODO: a proper type
contents interface{}
}
2020-12-17 12:59:59 +00:00
func (md *MycoDoc) lex() (ast []Line) {
var state = GemLexerState{name: md.hyphaName}
2020-12-17 12:59:59 +00:00
for _, line := range append(strings.Split(md.contents, "\n"), "") {
lineToAST(line, &state, &ast)
}
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})
}
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
// Process empty lines depending on the current state
if "" == strings.TrimSpace(line) {
switch state.where {
case "list":
2020-08-28 19:17:32 +00:00
state.where = ""
addLine(state.buf + "</ul>")
case "number":
state.where = ""
addLine(state.buf + "</ol>")
case "pre":
state.buf += "\n"
2020-12-15 18:59:36 +00:00
case "launchpad":
state.where = ""
addLine(state.buf + "</ul>")
case "p":
addParagraphIfNeeded()
2020-08-28 19:17:32 +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) {
addLine(fmt.Sprintf("<h%d id='%d'>%s</h%d>", i, state.id, ParagraphToHtml(state.name, line[i+1:]), i))
}
// 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
case "pre":
goto preformattedState
case "list":
goto listState
2020-11-03 15:47:22 +00:00
case "number":
goto numberState
2020-12-15 18:59:36 +00:00
case "launchpad":
goto launchpadState
default: // "p" or ""
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
preformattedState:
switch {
case startsWith("```"):
state.where = ""
state.buf = strings.TrimSuffix(state.buf, "\n")
addLine(state.buf + "</code></pre>")
state.buf = ""
default:
state.buf += html.EscapeString(line) + "\n"
}
return
listState:
switch {
case startsWith("* "):
2020-11-04 17:42:02 +00:00
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[2:]))
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-11-03 15:47:22 +00:00
numberState:
switch {
case startsWith("*. "):
2020-11-04 17:42:02 +00:00
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[3:]))
2020-11-03 15:47:22 +00:00
case startsWith("```"):
state.where = "pre"
addLine(state.buf + "</ol>")
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 + "</ol>")
goto normalState
}
return
2020-12-15 18:59:36 +00:00
launchpadState:
switch {
case startsWith("=>"):
2020-12-15 19:05:51 +00:00
href, text, class, icon := Rocketlink(line, state.name)
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a class="rocketlink %s" href="%s">%s%s</a></li>`, class, href, text, icon)
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
normalState:
state.id++
switch {
case startsWith("```"):
addParagraphIfNeeded()
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("* "):
addParagraphIfNeeded()
state.where = "list"
state.buf = fmt.Sprintf("<ul id='%d'>\n", state.id)
goto listState
2020-11-03 15:47:22 +00:00
case startsWith("*. "):
addParagraphIfNeeded()
2020-11-03 15:47:22 +00:00
state.where = "number"
state.buf = fmt.Sprintf("<ol id='%d'>\n", state.id)
goto numberState
2020-10-30 13:25:48 +00:00
case startsWith("###### "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(6)
2020-10-30 13:25:48 +00:00
case startsWith("##### "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(5)
2020-10-30 13:25:48 +00:00
case startsWith("#### "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(4)
2020-10-30 13:25:48 +00:00
case startsWith("### "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(3)
2020-10-30 13:25:48 +00:00
case startsWith("## "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(2)
2020-10-30 13:25:48 +00:00
case startsWith("# "):
addParagraphIfNeeded()
2020-11-12 18:21:49 +00:00
addHeading(1)
case startsWith(">"):
addParagraphIfNeeded()
2021-01-03 21:18:23 +00:00
addLine(
fmt.Sprintf(
"<blockquote id='%d'>%s</blockquote>",
state.id,
ParagraphToHtml(state.name, remover(">")(line)),
),
)
case startsWith("=>"):
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
case startsWith("<="):
addParagraphIfNeeded()
addLine(parseTransclusion(line, state.name))
case line == "----":
addParagraphIfNeeded()
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
2020-11-03 15:41:50 +00:00
case MatchesImg(line):
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):
addParagraphIfNeeded()
2021-01-01 04:07:56 +00:00
state.where = "table"
state.table = TableFromFirstLine(line, state.name)
case state.where == "p":
state.buf += "\n" + line
default:
state.where = "p"
state.buf = line
}
}