Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 73 additions & 10 deletions md/md_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strings"

"github.com/gomarkdown/markdown/ast"
Expand All @@ -21,14 +22,43 @@ type Renderer struct {
listDepth int
indentSize int
lastNormalText string

C *RendererConfig

linkcache map[string]bool // cache for link definitions to write in the footer, if renderLinksInFooter is set
}

type RendererConfig struct {
Flags Flags
}

type Flags int

const renderLinksInFooter Flags = 1 << iota

type RendererOpt func(c *RendererConfig)

// NewRenderer returns a Markdown renderer.
func NewRenderer() *Renderer {
func NewRenderer(opts ...RendererOpt) *Renderer {
c := &RendererConfig{}
for _, opt := range opts {
opt(c)
}
return &Renderer{
orderedListCounter: map[int]int{},
paragraph: map[int]bool{},
indentSize: 4,
C: c,
}
}

func WithRenderInFooter(renderInFooter bool) RendererOpt {
return func(c *RendererConfig) {
if renderInFooter {
c.Flags |= renderLinksInFooter
} else {
c.Flags &^= renderLinksInFooter
}
}
}

Expand All @@ -44,7 +74,7 @@ func (r *Renderer) outs(w io.Writer, s string) {

func (r *Renderer) doubleSpace(w io.Writer) {
// TODO: need to remember number of written bytes
//if out.Len() > 0 {
// if out.Len() > 0 {
r.outs(w, "\n")
//}
}
Expand Down Expand Up @@ -83,7 +113,7 @@ func (r *Renderer) listItem(w io.Writer, node *ast.ListItem, entering bool) {

func (r *Renderer) para(w io.Writer, node *ast.Paragraph, entering bool) {
if !entering && r.lastOutputLen > 0 {
var br = "\n\n"
br := "\n\n"

// List items don't need the extra line-break.
if _, ok := node.Parent.(*ast.ListItem); ok {
Expand Down Expand Up @@ -279,14 +309,29 @@ func (r *Renderer) link(w io.Writer, node *ast.Link, entering bool) {
} else {
link := string(escape(node.Destination))
title := string(node.Title)
r.outs(w, "](")
r.outs(w, link)
if r.C == nil || r.C.Flags&renderLinksInFooter == 0 {
r.outs(w, "](")
r.outs(w, link)
if len(title) != 0 {
r.outs(w, ` "`)
r.outs(w, title)
r.outs(w, `"`)
}
r.outs(w, ")")
return
}

r.outs(w, "]")
child, _ := ast.GetFirstChild(node).(*ast.Text)
linkdefn := fmt.Sprintf("[%s]: %s", string(escape(child.Leaf.Literal)), link)
if len(title) != 0 {
r.outs(w, ` "`)
r.outs(w, title)
r.outs(w, `"`)
linkdefn += fmt.Sprintf(" \"%s\"", title)
}
r.outs(w, ")")
if r.linkcache == nil {
r.linkcache = make(map[string]bool)
}
r.linkcache[linkdefn] = true

}
}

Expand Down Expand Up @@ -382,5 +427,23 @@ func (r *Renderer) RenderHeader(w io.Writer, ast ast.Node) {

// RenderFooter renders footer
func (r *Renderer) RenderFooter(w io.Writer, ast ast.Node) {
// do nothing
if r.C != nil && r.C.Flags&renderLinksInFooter != 0 {
if r.linkcache == nil {
return
}

// Extract links so we can write links in a predictable order.
links := make([]string, 0, len(r.linkcache))
for k := range r.linkcache {
links = append(links, k)
}
// Sort the keys to ensure consistent order.
sort.Strings(links)

for _, linkdefn := range links {
r.outs(w, "\n")
r.outs(w, linkdefn)
}
r.outs(w, "\n")
}
}
32 changes: 19 additions & 13 deletions md/md_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
)

func TestRenderDocument(t *testing.T) {
var source = []byte("# title\n* aaa\n* bbb\n* ccc")
var input = markdown.Parse(source, nil)
var expected = "# title\n\n* aaa\n* bbb\n* ccc\n\n"
source := []byte("# title\n* aaa\n* bbb\n* ccc")
input := markdown.Parse(source, nil)
expected := "# title\n\n* aaa\n* bbb\n* ccc\n\n"
testRendering(t, input, expected)
}

Expand Down Expand Up @@ -64,21 +64,21 @@ func TestRenderImage(t *testing.T) {
}

func TestRenderCode(t *testing.T) {
var input = &ast.Code{}
input := &ast.Code{}
input.Literal = []byte(string("val x : Int = 42"))
expected := "`val x : Int = 42`"
testRendering(t, input, expected)
}

func TestRenderCodeBlock(t *testing.T) {
var input = &ast.CodeBlock{Info: []byte(string("scala"))}
input := &ast.CodeBlock{Info: []byte(string("scala"))}
input.Literal = []byte(string("val x : Int = 42"))
expected := "\n```scala\nval x : Int = 42\n```\n"
testRendering(t, input, expected)
}

func TestRenderParagraph(t *testing.T) {
var input = &ast.Paragraph{}
input := &ast.Paragraph{}
ast.AppendChild(input, &ast.Text{Leaf: ast.Leaf{Literal: []byte(string("Hello World !"))}})
expected := "Hello World !\n\n"
testRendering(t, input, expected)
Expand All @@ -101,23 +101,23 @@ func TestRenderCodeWithParagraph(t *testing.T) {
}

func TestRenderHTMLSpan(t *testing.T) {
var input = &ast.HTMLSpan{}
input := &ast.HTMLSpan{}
input.Literal = []byte(string("hello"))
expected := "hello"
testRendering(t, input, expected)
}

func TestRenderHTMLBlock(t *testing.T) {
var input = &ast.HTMLBlock{}
input := &ast.HTMLBlock{}
input.Literal = []byte(string("hello"))
expected := "\nhello\n\n"
testRendering(t, input, expected)
}

func TestRenderList(t *testing.T) {
var source = []byte("* aaa\n* bbb\n* ccc\n* ddd\n")
var input = markdown.Parse(source, nil)
var expected = "* aaa\n* bbb\n* ccc\n* ddd\n\n"
source := []byte("* aaa\n* bbb\n* ccc\n* ddd\n")
input := markdown.Parse(source, nil)
expected := "* aaa\n* bbb\n* ccc\n* ddd\n\n"
testRendering(t, input, expected)

source = []byte("+ aaa\n+ bbb\n+ ccc\n+ ddd\n")
Expand Down Expand Up @@ -149,10 +149,16 @@ func TestRenderList(t *testing.T) {
input = markdown.Parse(source, nil)
expected = "* aaa\n * aaa1\n * aaa2\n\n* bbb\n* ccc\n* ddd\n\n"
testRendering(t, input, expected)

source = []byte("This is an [example](https://example.com) and another [website](https://github.com).")
input = markdown.Parse(source, nil)
rendererOpts := []RendererOpt{WithRenderInFooter(true)}
expected = "This is an [example] and another [website].\n\n\n[example]: https://example.com\n[website]: https://github.com\n"
testRendering(t, input, expected, rendererOpts...)
}

func testRendering(t *testing.T, input ast.Node, expected string) {
renderer := NewRenderer()
func testRendering(t *testing.T, input ast.Node, expected string, opts ...RendererOpt) {
renderer := NewRenderer(opts...)
result := string(markdown.Render(input, renderer))
if strings.Compare(result, expected) != 0 {
t.Errorf("[%s] is not equal to [%s]", result, expected)
Expand Down