From 49725d712a2f7479d8c9277fb0c234bf82941a54 Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Sat, 7 Nov 2020 17:21:36 +0500 Subject: [PATCH] Add image/link text render and blockquote support --- internal/gemini/renderer.go | 121 +++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 38 deletions(-) diff --git a/internal/gemini/renderer.go b/internal/gemini/renderer.go index dab3094..3f87b40 100644 --- a/internal/gemini/renderer.go +++ b/internal/gemini/renderer.go @@ -9,21 +9,19 @@ import ( ) var ( - lineBreak = []byte{'\n'} - space = []byte{' '} - linkPrefix = []byte("=> ") + lineBreak = []byte{'\n'} + lineBreakByte byte = 0x0a + space = []byte{' '} + linkPrefix = []byte("=> ") + quotePrefix = []byte("> ") ) // Renderer implements markdown.Renderer. -type Renderer struct { - LinkStack []ast.Node -} +type Renderer struct{} // NewRenderer returns a new Renderer. func NewRenderer() Renderer { - return Renderer{ - LinkStack: nil, - } + return Renderer{} } func (r Renderer) link(w io.Writer, node *ast.Link, entering bool) { @@ -37,6 +35,24 @@ func (r Renderer) link(w io.Writer, node *ast.Link, entering bool) { } } +func (r Renderer) linkText(w io.Writer, node *ast.Link) { + for _, text := range node.Children { + // TODO: Renderer.linkText: link can contain subblocks + if l := text.AsLeaf(); l != nil { + w.Write(l.Literal) + } + } +} + +func (r Renderer) imageText(w io.Writer, node *ast.Image) { + for _, text := range node.Children { + // TODO: Renderer.imageText: link can contain subblocks + if l := text.AsLeaf(); l != nil { + w.Write(l.Literal) + } + } +} + func (r Renderer) image(w io.Writer, node *ast.Image, entering bool) { if entering { w.Write(linkPrefix) @@ -53,11 +69,34 @@ func (r Renderer) image(w io.Writer, node *ast.Image, entering bool) { } } -func (r Renderer) citation(w io.Writer, node *ast.Citation) {} +func (r Renderer) blockquote(w io.Writer, node *ast.BlockQuote, entering bool) { + // TODO: Renderer.blockquote: needs support for subnode rendering; + // ideally to be merged with paragraph + if entering { + w.Write(quotePrefix) + if para, ok := node.Children[0].(*ast.Paragraph); ok { + for _, subnode := range para.Children { + if l := subnode.AsLeaf(); l != nil { + // TODO: Renderer.blockquote: rendering by byte is asking + // for optimizations + for _, b := range l.Literal { + w.Write([]byte{b}) + if b == lineBreakByte { + w.Write(quotePrefix) + } + } + } + } + } + } else { + w.Write(lineBreak) + w.Write(lineBreak) + } +} func (r Renderer) heading(w io.Writer, node *ast.Heading, entering bool) { if entering { - // prepend headings with the relevant number of #-s + // pad headings with the relevant number of #-s heading := make([]byte, node.Level+1) heading[len(heading)-1] = ' ' for i := 0; i < len(heading)-1; i++ { @@ -75,34 +114,44 @@ func (r Renderer) heading(w io.Writer, node *ast.Heading, entering bool) { func (r Renderer) paragraph(w io.Writer, node *ast.Paragraph, entering bool) { if entering { - if r.LinkStack != nil { - panic("link stack not empty") - } - r.LinkStack = make([]ast.Node, 0, len(node.Children)) + linkStack := make([]ast.Node, 0, len(node.Children)) + onlyElement := len(node.Children) == 1 for _, child := range node.Children { + // only render links text in the paragraph if they're + // combined with some other text on page if link, ok := child.(*ast.Link); ok { - r.LinkStack = append(r.LinkStack, link) + if !(len(link.Children) > 1) && !onlyElement { + r.linkText(w, link) + } + linkStack = append(linkStack, link) } if image, ok := child.(*ast.Image); ok { - r.LinkStack = append(r.LinkStack, image) + if !(len(image.Children) > 1) && !onlyElement { + r.imageText(w, image) + } + linkStack = append(linkStack, image) } if text, ok := child.(*ast.Text); ok { r.text(w, text) } } + // render a links block after paragraph + if len(linkStack) > 0 { + w.Write(lineBreak) + w.Write(lineBreak) + for _, link := range linkStack { + if link, ok := link.(*ast.Link); ok { + r.link(w, link, true) + } + if image, ok := link.(*ast.Image); ok { + r.image(w, image, true) + } + w.Write(lineBreak) + } + } } else { w.Write(lineBreak) w.Write(lineBreak) - for _, link := range r.LinkStack { - if link, ok := link.(*ast.Link); ok { - r.link(w, link, true) - } - if image, ok := link.(*ast.Image); ok { - r.image(w, image, true) - } - w.Write(lineBreak) - } - r.LinkStack = nil } } @@ -125,21 +174,17 @@ func (r Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Walk // despite most of the subroutines here accepting entering, most of // them don't really need an extra pass switch node := node.(type) { - case *ast.Link: - // TODO: shouldn't be here at all - r.link(w, node, entering) - case *ast.Image: - // TODO: neither this - r.image(w, node, entering) - case *ast.Citation: - // TODO: neither this - r.citation(w, node) + case *ast.BlockQuote: + r.blockquote(w, node, entering) case *ast.Heading: r.heading(w, node, entering) case *ast.Paragraph: - r.paragraph(w, node, entering) + // blockquote wraps paragraphs which makes for an extra render + if _, parentIsBlockQuote := node.Parent.(*ast.BlockQuote); !parentIsBlockQuote { + r.paragraph(w, node, entering) + } case *ast.Code: - // TODO: likely not even this + // TODO: *ast.Code render is likely to be merged into paragraph r.code(w, node, entering) } return ast.GoToNext