renderer.go

  1package ansi
  2
  3import (
  4	"fmt"
  5	"io"
  6	"net/url"
  7	"strings"
  8
  9	east "github.com/yuin/goldmark-emoji/ast"
 10	"github.com/yuin/goldmark/ast"
 11	astext "github.com/yuin/goldmark/extension/ast"
 12	"github.com/yuin/goldmark/renderer"
 13	"github.com/yuin/goldmark/util"
 14)
 15
 16// Options is used to configure an ANSIRenderer.
 17type Options struct {
 18	BaseURL          string
 19	WordWrap         int
 20	TableWrap        *bool
 21	InlineTableLinks bool
 22	PreserveNewLines bool
 23	Styles           StyleConfig
 24	ChromaFormatter  string
 25}
 26
 27// ANSIRenderer renders markdown content as ANSI escaped sequences.
 28type ANSIRenderer struct { //nolint: revive
 29	context RenderContext
 30}
 31
 32// NewRenderer returns a new ANSIRenderer with style and options set.
 33func NewRenderer(options Options) *ANSIRenderer {
 34	return &ANSIRenderer{
 35		context: NewRenderContext(options),
 36	}
 37}
 38
 39// RegisterFuncs implements NodeRenderer.RegisterFuncs.
 40func (r *ANSIRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 41	// blocks
 42	reg.Register(ast.KindDocument, r.renderNode)
 43	reg.Register(ast.KindHeading, r.renderNode)
 44	reg.Register(ast.KindBlockquote, r.renderNode)
 45	reg.Register(ast.KindCodeBlock, r.renderNode)
 46	reg.Register(ast.KindFencedCodeBlock, r.renderNode)
 47	reg.Register(ast.KindHTMLBlock, r.renderNode)
 48	reg.Register(ast.KindList, r.renderNode)
 49	reg.Register(ast.KindListItem, r.renderNode)
 50	reg.Register(ast.KindParagraph, r.renderNode)
 51	reg.Register(ast.KindTextBlock, r.renderNode)
 52	reg.Register(ast.KindThematicBreak, r.renderNode)
 53
 54	// inlines
 55	reg.Register(ast.KindAutoLink, r.renderNode)
 56	reg.Register(ast.KindCodeSpan, r.renderNode)
 57	reg.Register(ast.KindEmphasis, r.renderNode)
 58	reg.Register(ast.KindImage, r.renderNode)
 59	reg.Register(ast.KindLink, r.renderNode)
 60	reg.Register(ast.KindRawHTML, r.renderNode)
 61	reg.Register(ast.KindText, r.renderNode)
 62	reg.Register(ast.KindString, r.renderNode)
 63
 64	// tables
 65	reg.Register(astext.KindTable, r.renderNode)
 66	reg.Register(astext.KindTableHeader, r.renderNode)
 67	reg.Register(astext.KindTableRow, r.renderNode)
 68	reg.Register(astext.KindTableCell, r.renderNode)
 69
 70	// definitions
 71	reg.Register(astext.KindDefinitionList, r.renderNode)
 72	reg.Register(astext.KindDefinitionTerm, r.renderNode)
 73	reg.Register(astext.KindDefinitionDescription, r.renderNode)
 74
 75	// footnotes
 76	reg.Register(astext.KindFootnote, r.renderNode)
 77	reg.Register(astext.KindFootnoteList, r.renderNode)
 78	reg.Register(astext.KindFootnoteLink, r.renderNode)
 79	reg.Register(astext.KindFootnoteBacklink, r.renderNode)
 80
 81	// checkboxes
 82	reg.Register(astext.KindTaskCheckBox, r.renderNode)
 83
 84	// strikethrough
 85	reg.Register(astext.KindStrikethrough, r.renderNode)
 86
 87	// emoji
 88	reg.Register(east.KindEmoji, r.renderNode)
 89}
 90
 91func (r *ANSIRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 92	writeTo := io.Writer(w)
 93	bs := r.context.blockStack
 94
 95	// children get rendered by their parent
 96	if isChild(node) {
 97		return ast.WalkContinue, nil
 98	}
 99
100	e := r.NewElement(node, source)
101	if entering { //nolint: nestif
102		// everything below the Document element gets rendered into a block buffer
103		if bs.Len() > 0 {
104			writeTo = io.Writer(bs.Current().Block)
105		}
106
107		_, _ = io.WriteString(writeTo, e.Entering)
108		if e.Renderer != nil {
109			err := e.Renderer.Render(writeTo, r.context)
110			if err != nil {
111				return ast.WalkStop, fmt.Errorf("glamour: error rendering: %w", err)
112			}
113		}
114	} else {
115		// everything below the Document element gets rendered into a block buffer
116		if bs.Len() > 0 {
117			writeTo = io.Writer(bs.Parent().Block)
118		}
119
120		// if we're finished rendering the entire document,
121		// flush to the real writer
122		if node.Type() == ast.TypeDocument {
123			writeTo = w
124		}
125
126		if e.Finisher != nil {
127			err := e.Finisher.Finish(writeTo, r.context)
128			if err != nil {
129				return ast.WalkStop, fmt.Errorf("glamour: error finishing render: %w", err)
130			}
131		}
132
133		_, _ = io.WriteString(bs.Current().Block, e.Exiting)
134	}
135
136	return ast.WalkContinue, nil
137}
138
139func isChild(node ast.Node) bool {
140	for n := node.Parent(); n != nil; n = n.Parent() {
141		// These types are already rendered by their parent
142		switch n.Kind() {
143		case ast.KindCodeSpan, ast.KindAutoLink, ast.KindLink, ast.KindImage, ast.KindEmphasis, astext.KindStrikethrough, astext.KindTableCell:
144			return true
145		}
146	}
147
148	return false
149}
150
151func resolveRelativeURL(baseURL string, rel string) string {
152	u, err := url.Parse(rel)
153	if err != nil {
154		return rel
155	}
156	if u.IsAbs() {
157		return rel
158	}
159	u.Path = strings.TrimPrefix(u.Path, "/")
160
161	base, err := url.Parse(baseURL)
162	if err != nil {
163		return rel
164	}
165	return base.ResolveReference(u).String()
166}