From 897b2dbd1ec7937901a32013f5d4fb35403e3a23 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 7 Dec 2019 18:48:08 +0100 Subject: [PATCH] Decouple render elements from blackfriday parser --- baseelement.go | 5 ++-- codeblock.go | 7 ++--- context.go | 7 +++++ document.go | 11 +++----- elements.go | 75 ++++++++++++++++++++++++++++++++++++++++++-------- gold.go | 4 +-- heading.go | 18 ++++++------ image.go | 22 +++++++-------- link.go | 68 ++++++++++++++++++++++++++++----------------- list.go | 16 +++++------ listitem.go | 24 ++++++---------- paragraph.go | 20 ++++++-------- table.go | 34 +++++++---------------- 13 files changed, 179 insertions(+), 132 deletions(-) create mode 100644 context.go diff --git a/baseelement.go b/baseelement.go index ae99b33..939d3cd 100644 --- a/baseelement.go +++ b/baseelement.go @@ -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() { diff --git a/codeblock.go b/codeblock.go index d22cada..1e9b74f 100644 --- a/codeblock.go +++ b/codeblock.go @@ -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) } diff --git a/context.go b/context.go new file mode 100644 index 0000000..41f8890 --- /dev/null +++ b/context.go @@ -0,0 +1,7 @@ +package gold + +type RenderContext struct { + style map[StyleType]ElementStyle + blockStack *BlockStack + table *TableElement +} diff --git a/document.go b/document.go index 2a99a26..970c2f5 100644 --- a/document.go +++ b/document.go @@ -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, " ") }, diff --git a/elements.go b/elements.go index ff8c217..86b7061 100644 --- a/elements.go +++ b/elements.go @@ -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{ diff --git a/gold.go b/gold.go index 28696f2..5eb718e 100644 --- a/gold.go +++ b/gold.go @@ -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 diff --git a/heading.go b/heading.go index 3331064..7457149 100644 --- a/heading.go +++ b/heading.go @@ -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 } diff --git a/image.go b/image.go index e14b9fc..a41833b 100644 --- a/image.go +++ b/image.go @@ -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 } diff --git a/link.go b/link.go index 53857db..d571fb9 100644 --- a/link.go +++ b/link.go @@ -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 } diff --git a/list.go b/list.go index 98717e9..e129f79 100644 --- a/list.go +++ b/list.go @@ -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 } diff --git a/listitem.go b/listitem.go index de853a4..48896b0 100644 --- a/listitem.go +++ b/listitem.go @@ -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) } diff --git a/paragraph.go b/paragraph.go index d336079..478f4cc 100644 --- a/paragraph.go +++ b/paragraph.go @@ -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 diff --git a/table.go b/table.go index 06139c3..9a3a1d1 100644 --- a/table.go +++ b/table.go @@ -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