Decouple render elements from blackfriday parser

This commit is contained in:
Christian Muehlhaeuser
2019-12-07 18:48:08 +01:00
parent 665477d690
commit 897b2dbd1e
13 changed files with 179 additions and 132 deletions
+2 -3
View File
@@ -11,7 +11,6 @@ import (
"github.com/logrusorgru/aurora"
"github.com/lucasb-eyer/go-colorful"
bf "gopkg.in/russross/blackfriday.v2"
)
type BaseElement struct {
@@ -137,8 +136,8 @@ func renderText(w io.Writer, rules ElementStyle, s string) {
_, _ = w.Write([]byte(out.String()))
}
func (e *BaseElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
bs := tr.context.blockStack
func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
renderText(w, bs.Current().Style, e.Prefix)
defer func() {
+2 -5
View File
@@ -4,7 +4,6 @@ import (
"io"
"github.com/alecthomas/chroma/quick"
bf "gopkg.in/russross/blackfriday.v2"
)
type CodeBlockElement struct {
@@ -12,9 +11,7 @@ type CodeBlockElement struct {
Language string
}
func (e *CodeBlockElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
var indent uint
var margin uint
rules := ctx.style[CodeBlock]
@@ -46,5 +43,5 @@ func (e *CodeBlockElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer)
Style: rules,
}
return el.Render(iw, node, tr)
return el.Render(iw, ctx)
}
+7
View File
@@ -0,0 +1,7 @@
package gold
type RenderContext struct {
style map[StyleType]ElementStyle
blockStack *BlockStack
table *TableElement
}
+4 -7
View File
@@ -3,15 +3,13 @@ package gold
import (
"bytes"
"io"
bf "gopkg.in/russross/blackfriday.v2"
)
type DocumentElement struct {
Width uint
}
func (e *DocumentElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *DocumentElement) Render(w io.Writer, ctx RenderContext) error {
rules := ctx.style[Document]
be := BlockElement{
@@ -24,8 +22,7 @@ func (e *DocumentElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) e
return nil
}
func (e *DocumentElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *DocumentElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := ctx.style[Document]
@@ -40,7 +37,7 @@ func (e *DocumentElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) e
suffix := rules.Suffix
pw := &PaddingWriter{
Padding: uint(tr.WordWrap) - margin,
Padding: e.Width - margin,
PadFunc: func(wr io.Writer) {
renderText(w, rules, " ")
},
+64 -11
View File
@@ -9,11 +9,11 @@ import (
)
type ElementRenderer interface {
Render(w io.Writer, node *bf.Node, tr *TermRenderer) error
Render(w io.Writer, ctx RenderContext) error
}
type ElementFinisher interface {
Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error
Finish(w io.Writer, ctx RenderContext) error
}
type Element struct {
@@ -28,7 +28,9 @@ func (tr *TermRenderer) NewElement(node *bf.Node) Element {
switch node.Type {
case bf.Document:
de := &DocumentElement{}
de := &DocumentElement{
Width: uint(tr.WordWrap),
}
return Element{
Renderer: de,
Finisher: de,
@@ -43,25 +45,49 @@ func (tr *TermRenderer) NewElement(node *bf.Node) Element {
},
}
case bf.List:
le := &ListElement{}
le := &ListElement{
Width: uint(tr.WordWrap),
Nested: node.Parent.Type == bf.Item,
}
return Element{
Renderer: le,
Finisher: le,
}
case bf.Item:
var l uint
if node.ListData.ListFlags&bf.ListTypeOrdered > 0 {
l = 1
n := node
for n.Prev != nil && (n.Prev.Type == bf.Item) {
l++
n = n.Prev
}
}
return Element{
Renderer: &ItemElement{},
Renderer: &ItemElement{
Text: string(node.Literal),
Enumeration: l,
},
}
case bf.Paragraph:
pe := &ParagraphElement{}
pe := &ParagraphElement{
Width: uint(tr.WordWrap),
InsideList: node.Parent != nil && node.Parent.Type == bf.Item,
}
return Element{
Renderer: pe,
Finisher: pe,
}
case bf.Heading:
return Element{
Exiting: "\n",
Renderer: &HeadingElement{},
Exiting: "\n",
Renderer: &HeadingElement{
Width: uint(tr.WordWrap),
Text: string(node.FirstChild.Literal),
Level: node.HeadingData.Level,
First: node.Prev == nil,
},
}
case bf.HorizontalRule:
return Element{
@@ -94,12 +120,28 @@ func (tr *TermRenderer) NewElement(node *bf.Node) Element {
},
}
case bf.Link:
var text string
if node.LastChild != nil {
text = string(node.LastChild.Literal)
}
return Element{
Renderer: &LinkElement{},
Renderer: &LinkElement{
Text: text,
BaseURL: tr.BaseURL,
URL: string(node.LinkData.Destination),
},
}
case bf.Image:
var text string
if node.LastChild != nil {
text = string(node.LastChild.Literal)
}
return Element{
Renderer: &ImageElement{},
Renderer: &ImageElement{
Text: text,
BaseURL: tr.BaseURL,
URL: string(node.LinkData.Destination),
},
}
case bf.Text:
return Element{
@@ -161,8 +203,19 @@ func (tr *TermRenderer) NewElement(node *bf.Node) Element {
Finisher: te,
}
case bf.TableCell:
s := ""
n := node.FirstChild
for n != nil {
s += string(n.Literal)
s += string(n.LinkData.Destination)
n = n.Next
}
return Element{
Renderer: &TableCellElement{},
Renderer: &TableCellElement{
Text: s,
Head: node.Parent.Parent.Type == bf.TableHead,
},
}
case bf.TableHead:
return Element{
+2 -2
View File
@@ -100,7 +100,7 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
_, _ = writeTo.Write([]byte(e.Entering))
if e.Renderer != nil {
err := e.Renderer.Render(writeTo, node, tr)
err := e.Renderer.Render(writeTo, tr.context)
if err != nil {
fmt.Println(err)
return bf.Terminate
@@ -118,7 +118,7 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
}
if e.Finisher != nil {
err := e.Finisher.Finish(writeTo, node, tr)
err := e.Finisher.Finish(writeTo, tr.context)
if err != nil {
fmt.Println(err)
return bf.Terminate
+10 -8
View File
@@ -4,18 +4,20 @@ import (
"io"
"github.com/muesli/reflow"
bf "gopkg.in/russross/blackfriday.v2"
)
type HeadingElement struct {
Width uint
Text string
Level int
First bool
}
func (e *HeadingElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := ctx.style[Heading]
switch node.HeadingData.Level {
switch e.Level {
case 1:
rules = cascadeStyles(false, rules, ctx.style[H1])
case 2:
@@ -49,20 +51,20 @@ func (e *HeadingElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) er
},
}
flow := reflow.NewReflow(tr.WordWrap -
flow := reflow.NewReflow(int(e.Width) -
int(indent) - int(margin*2) -
int(bs.Indent()) - int(bs.Margin())*2)
var pre string
if node.Prev != nil {
if !e.First {
pre = "\n"
}
el := &BaseElement{
Prefix: pre,
Token: string(node.FirstChild.Literal),
Token: string(e.Text),
Style: rules,
}
err := el.Render(flow, node, tr)
err := el.Render(flow, ctx)
if err != nil {
return err
}
+11 -11
View File
@@ -2,33 +2,33 @@ package gold
import (
"io"
bf "gopkg.in/russross/blackfriday.v2"
)
type ImageElement struct {
Text string
BaseURL string
URL string
Child ElementRenderer // FIXME
}
func (e *ImageElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
if len(node.LastChild.Literal) > 0 {
func (e *ImageElement) Render(w io.Writer, ctx RenderContext) error {
if len(e.Text) > 0 {
el := &BaseElement{
Token: string(node.LastChild.Literal),
Token: e.Text,
Style: ctx.style[ImageText],
}
err := el.Render(w, node.LastChild, tr)
err := el.Render(w, ctx)
if err != nil {
return err
}
}
if len(node.LinkData.Destination) > 0 {
if len(e.URL) > 0 {
el := &BaseElement{
Token: resolveRelativeURL(tr.BaseURL, string(node.LinkData.Destination)),
Token: resolveRelativeURL(e.BaseURL, string(e.URL)),
Prefix: " ",
Style: ctx.style[Image],
}
err := el.Render(w, node, tr)
err := el.Render(w, ctx)
if err != nil {
return err
}
+42 -26
View File
@@ -2,40 +2,56 @@ package gold
import (
"io"
bf "gopkg.in/russross/blackfriday.v2"
)
type LinkElement struct {
Text string
BaseURL string
URL string
Child ElementRenderer // FIXME
}
func (e *LinkElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *LinkElement) Render(w io.Writer, ctx RenderContext) error {
var textRendered bool
if node.LastChild != nil {
if node.LastChild.Type == bf.Image {
el := tr.NewElement(node.LastChild)
err := el.Renderer.Render(w, node.LastChild, tr)
if err != nil {
return err
}
if len(e.Text) > 0 &&
e.Text != e.URL {
textRendered = true
el := &BaseElement{
Token: e.Text,
Style: ctx.style[LinkText],
}
if len(node.LastChild.Literal) > 0 &&
string(node.LastChild.Literal) != string(node.LinkData.Destination) {
textRendered = true
el := &BaseElement{
Token: string(node.LastChild.Literal),
Style: ctx.style[LinkText],
}
err := el.Render(w, node.LastChild, tr)
if err != nil {
return err
}
err := el.Render(w, ctx)
if err != nil {
return err
}
}
if len(node.LinkData.Destination) > 0 {
/*
if node.LastChild != nil {
if node.LastChild.Type == bf.Image {
el := tr.NewElement(node.LastChild)
err := el.Renderer.Render(w, node.LastChild, tr)
if err != nil {
return err
}
}
if len(node.LastChild.Literal) > 0 &&
string(node.LastChild.Literal) != string(node.LinkData.Destination) {
textRendered = true
el := &BaseElement{
Token: string(node.LastChild.Literal),
Style: ctx.style[LinkText],
}
err := el.Render(w, node.LastChild, tr)
if err != nil {
return err
}
}
}
*/
if len(e.URL) > 0 {
pre := " "
style := ctx.style[Link]
if !textRendered {
@@ -45,11 +61,11 @@ func (e *LinkElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error
}
el := &BaseElement{
Token: resolveRelativeURL(tr.BaseURL, string(node.LinkData.Destination)),
Token: resolveRelativeURL(e.BaseURL, e.URL),
Prefix: pre,
Style: style,
}
err := el.Render(w, node, tr)
err := el.Render(w, ctx)
if err != nil {
return err
}
+8 -8
View File
@@ -5,16 +5,16 @@ import (
"io"
"github.com/muesli/reflow"
bf "gopkg.in/russross/blackfriday.v2"
)
type ListElement struct {
Width uint
Nested bool
}
func (e *ListElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *ListElement) Render(w io.Writer, ctx RenderContext) error {
rules := ctx.style[List]
if node.Parent.Type != bf.Item {
if !e.Nested {
_, _ = w.Write([]byte("\n"))
}
@@ -27,9 +27,9 @@ func (e *ListElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error
return nil
}
func (e *ListElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *ListElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
var indent uint
var margin uint
rules := bs.Current().Style
@@ -43,7 +43,7 @@ func (e *ListElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error
renderText(bs.Current().Block, rules, suffix)
pw := &PaddingWriter{
Padding: uint(tr.WordWrap - int(bs.Indent()) - int(bs.Margin()*2)),
Padding: uint(int(e.Width) - int(bs.Indent()) - int(bs.Margin()*2)),
PadFunc: func(wr io.Writer) {
renderText(w, rules, " ")
},
@@ -62,7 +62,7 @@ func (e *ListElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error
}
_, err := iw.Write(reflow.Bytes(bs.Current().Block.Bytes(),
tr.WordWrap-int(bs.Indent())-int(bs.Margin())*2))
int(e.Width)-int(bs.Indent())-int(bs.Margin())*2))
if err != nil {
return err
}
+8 -16
View File
@@ -3,36 +3,28 @@ package gold
import (
"io"
"strconv"
bf "gopkg.in/russross/blackfriday.v2"
)
type ItemElement struct {
Text string
Enumeration uint
}
func (e *ItemElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *ItemElement) Render(w io.Writer, ctx RenderContext) error {
var el *BaseElement
if node.ListData.ListFlags&bf.ListTypeOrdered > 0 {
var l int64
n := node
for n.Prev != nil && (n.Prev.Type == bf.Item) {
l++
n = n.Prev
}
if e.Enumeration > 0 {
el = &BaseElement{
Token: string(node.Literal),
Token: e.Text,
Style: ctx.style[Enumeration],
Prefix: strconv.FormatInt(l+1, 10),
Prefix: strconv.FormatInt(int64(e.Enumeration), 10),
}
} else {
el = &BaseElement{
Token: string(node.Literal),
Token: e.Text,
Style: ctx.style[Item],
}
}
return el.Render(w, node, tr)
return el.Render(w, ctx)
}
+9 -11
View File
@@ -6,18 +6,18 @@ import (
"strings"
"github.com/muesli/reflow"
bf "gopkg.in/russross/blackfriday.v2"
)
type ParagraphElement struct {
Width uint
InsideList bool
}
func (e *ParagraphElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
var rules ElementStyle
if node.Parent != nil && node.Parent.Type == bf.Item {
if e.InsideList {
// list item
rules = ctx.style[List]
} else {
@@ -34,15 +34,14 @@ func (e *ParagraphElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer)
return nil
}
func (e *ParagraphElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
rules := bs.Current().Style
var indent uint
var margin uint
keepNewlines := false
if node.Parent != nil && node.Parent.Type == bf.Item {
if e.InsideList {
// remove indent & margin for list items
rules = bs.Current().Style
keepNewlines = true
@@ -59,7 +58,7 @@ func (e *ParagraphElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer)
renderText(bs.Current().Block, rules, suffix)
pw := &PaddingWriter{
Padding: uint(tr.WordWrap - int(bs.Indent()) - int(bs.Margin()*2)),
Padding: uint(int(e.Width) - int(bs.Indent()) - int(bs.Margin()*2)),
PadFunc: func(wr io.Writer) {
renderText(w, rules, " ")
},
@@ -78,7 +77,7 @@ func (e *ParagraphElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer)
}
if len(strings.TrimSpace(bs.Current().Block.String())) > 0 {
flow := reflow.NewReflow(tr.WordWrap - int(bs.Indent()) - int(bs.Margin())*2)
flow := reflow.NewReflow(int(e.Width) - int(bs.Indent()) - int(bs.Margin())*2)
flow.KeepNewlines = keepNewlines
_, _ = flow.Write(bs.Current().Block.Bytes())
flow.Close()
@@ -91,8 +90,7 @@ func (e *ParagraphElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer)
}
bs.Current().Block.Reset()
if node.Parent != nil && node.Parent.Type == bf.Item {
} else {
if !e.InsideList {
bs.Pop()
}
return nil
+10 -24
View File
@@ -4,7 +4,6 @@ import (
"io"
"github.com/olekukonko/tablewriter"
bf "gopkg.in/russross/blackfriday.v2"
)
type TableElement struct {
@@ -20,11 +19,11 @@ type TableHeadElement struct {
}
type TableCellElement struct {
Text string
Head bool
}
func (e *TableElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *TableElement) Render(w io.Writer, ctx RenderContext) error {
var indent uint
var margin uint
rules := ctx.style[Table]
@@ -49,42 +48,29 @@ func (e *TableElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) erro
return nil
}
func (e *TableElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *TableElement) Finish(w io.Writer, ctx RenderContext) error {
ctx.table.writer.Render()
ctx.table.writer = nil
return nil
}
func (e *TableRowElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *TableRowElement) Finish(w io.Writer, ctx RenderContext) error {
ctx.table.writer.Append(ctx.table.cell)
ctx.table.cell = []string{}
return nil
}
func (e *TableHeadElement) Finish(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
func (e *TableHeadElement) Finish(w io.Writer, ctx RenderContext) error {
ctx.table.writer.SetHeader(ctx.table.header)
ctx.table.header = []string{}
return nil
}
func (e *TableCellElement) Render(w io.Writer, node *bf.Node, tr *TermRenderer) error {
ctx := tr.context
s := ""
n := node.FirstChild
for n != nil {
s += string(n.Literal)
s += string(n.LinkData.Destination)
n = n.Next
}
if node.Parent.Parent.Type == bf.TableHead {
ctx.table.header = append(ctx.table.header, s)
func (e *TableCellElement) Render(w io.Writer, ctx RenderContext) error {
if e.Head {
ctx.table.header = append(ctx.table.header, e.Text)
} else {
ctx.table.cell = append(ctx.table.cell, s)
ctx.table.cell = append(ctx.table.cell, e.Text)
}
return nil