1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-13 05:50:27 +00:00

feat(markup): nested lists

This commit is contained in:
handlerug 2021-01-20 22:05:09 +07:00
parent 53981c28ca
commit 3d911fc51b
No known key found for this signature in database
GPG Key ID: 38009F0605051491
3 changed files with 190 additions and 52 deletions

View File

@ -31,6 +31,7 @@ type GemLexerState struct {
// Temporaries
img *Img
table *Table
list *List
}
type Line struct {
@ -64,12 +65,6 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) {
// Process empty lines depending on the current state
if "" == strings.TrimSpace(line) {
switch state.where {
case "list":
state.where = ""
addLine(state.buf + "</ul>")
case "number":
state.where = ""
addLine(state.buf + "</ol>")
case "pre":
state.buf += "\n"
case "launchpad":
@ -95,12 +90,10 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) {
goto imgState
case "table":
goto tableState
case "pre":
goto preformattedState
case "list":
goto listState
case "number":
goto numberState
case "pre":
goto preformattedState
case "launchpad":
goto launchpadState
default: // "p" or ""
@ -121,6 +114,14 @@ tableState:
}
return
listState:
if done := state.list.Parse(line); done {
state.list.Finalize()
state.where = ""
goto normalState
}
return
preformattedState:
switch {
case startsWith("```"):
@ -133,38 +134,6 @@ preformattedState:
}
return
listState:
switch {
case startsWith("* "):
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
numberState:
switch {
case startsWith("*. "):
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[3:]))
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
launchpadState:
switch {
case startsWith("=>"):
@ -190,16 +159,6 @@ normalState:
addParagraphIfNeeded()
state.where = "pre"
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
case startsWith("* "):
addParagraphIfNeeded()
state.where = "list"
state.buf = fmt.Sprintf("<ul id='%d'>\n", state.id)
goto listState
case startsWith("*. "):
addParagraphIfNeeded()
state.where = "number"
state.buf = fmt.Sprintf("<ol id='%d'>\n", state.id)
goto numberState
case startsWith("###### "):
addParagraphIfNeeded()
@ -241,6 +200,12 @@ normalState:
case MatchesHorizontalLine(line):
addParagraphIfNeeded()
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
case MatchesList(line):
addParagraphIfNeeded()
list, _ := NewList(line, state.name)
state.where = "list"
state.list = list
addLine(state.list)
case MatchesImg(line):
addParagraphIfNeeded()
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)

171
markup/list.go Normal file
View File

@ -0,0 +1,171 @@
package markup
import (
"errors"
"strings"
)
func parseListItem(line string) (level int, offset int, ordered bool, err error) {
for line[level] == '*' {
level++
}
if line[level] == '.' {
ordered = true
offset = level + 2
} else {
ordered = false
offset = level + 1
}
if line[offset-1] != ' ' || len(line) < offset+2 || level < 1 || level > 6 {
err = errors.New("ill-formatted list item")
}
return
}
func MatchesList(line string) bool {
level, _, _, err := parseListItem(line)
return err == nil && level == 1
}
type listItem struct {
content string
parent *listItem
children []*listItem
depth int
}
func newListItem(parent *listItem) *listItem {
depth := 0
if parent != nil {
depth = parent.depth + 1
}
return &listItem{
parent: parent,
children: make([]*listItem, 0),
depth: depth,
}
}
func (item *listItem) renderAsHtmlTo(b *strings.Builder, hyphaName string, ordered bool) {
if len(item.content) > 0 {
b.WriteString("<li>")
b.WriteString(ParagraphToHtml(hyphaName, item.content))
}
if len(item.children) > 0 {
if ordered {
b.WriteString("<ol>")
} else {
b.WriteString("<ul>")
}
for _, child := range item.children {
child.renderAsHtmlTo(b, hyphaName, ordered)
}
if ordered {
b.WriteString("</ol>")
} else {
b.WriteString("</ul>")
}
}
if len(item.content) > 0 {
b.WriteString("</li>")
}
}
// A structure representing ordered and unordered lists in the AST.
type List struct {
curr *listItem
hyphaName string
ordered bool
finalized bool
}
func NewList(line, hyphaName string) (*List, bool) {
list := &List{
hyphaName: hyphaName,
curr: newListItem(nil),
}
return list, list.Parse(line)
}
func (list *List) pushItem() {
item := newListItem(list.curr)
list.curr.children = append(list.curr.children, item)
list.curr = item
}
func (list *List) popItem() {
if list.curr == nil {
return
}
list.curr = list.curr.parent
}
func (list *List) balance(level int) {
for level > list.curr.depth {
list.pushItem()
}
for level < list.curr.depth {
list.popItem()
}
}
func (list *List) Parse(line string) (done bool) {
level, offset, ordered, err := parseListItem(line)
if err != nil {
list.Finalize()
return true
}
// update ordered flag if the current node is the root one
// (i.e. no parsing has been done yet)
if list.curr.parent == nil {
list.ordered = ordered
}
// if list type has suddenly changed (ill-formatted list), quit
if ordered != list.ordered {
list.Finalize()
return true
}
list.balance(level)
// if the current node already has content, create a new one
// to prevent overwriting existing content (effectively creating
// a new sibling node)
if len(list.curr.content) > 0 {
list.popItem()
list.pushItem()
}
list.curr.content = line[offset:]
return false
}
func (list *List) Finalize() {
if !list.finalized {
// close all opened nodes, effectively going up to the root node
list.balance(0)
list.finalized = true
}
}
func (list *List) RenderAsHtml() (html string) {
// for a good measure
list.Finalize()
b := &strings.Builder{}
// fire up recursive render process
list.curr.renderAsHtmlTo(b, list.hyphaName, list.ordered)
return b.String()
}

View File

@ -15,6 +15,8 @@ func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
html += v.ToHtml()
case Table:
html += v.asHtml()
case *List:
html += v.RenderAsHtml()
case string:
html += v
default: