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:
parent
53981c28ca
commit
3d911fc51b
@ -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
171
markup/list.go
Normal 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()
|
||||
}
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user