this repo has no description
1// Package markup is an umbrella package for all markups and their renderers.
2package markup
3
4import (
5 "bytes"
6 "path"
7
8 "github.com/yuin/goldmark"
9 "github.com/yuin/goldmark/ast"
10 "github.com/yuin/goldmark/extension"
11 "github.com/yuin/goldmark/parser"
12 "github.com/yuin/goldmark/text"
13 "github.com/yuin/goldmark/util"
14)
15
16// RendererType defines the type of renderer to use based on context
17type RendererType int
18
19const (
20 // RendererTypeRepoMarkdown is for repository documentation markdown files
21 RendererTypeRepoMarkdown RendererType = iota
22 // RendererTypeIssueComment is for issue comments
23 RendererTypeIssueComment
24 // RendererTypePullComment is for pull request comments
25 RendererTypePullComment
26 // RendererTypeDefault is the default renderer with minimal transformations
27 RendererTypeDefault
28)
29
30// RenderContext holds the contextual data for rendering markdown.
31// It can be initialized empty, and that'll skip any transformations
32// and use the default renderer (RendererTypeDefault).
33type RenderContext struct {
34 Ref string
35 FullRepoName string
36 RendererType RendererType
37}
38
39func (rctx *RenderContext) RenderMarkdown(source string) string {
40 md := goldmark.New(
41 goldmark.WithExtensions(extension.GFM),
42 goldmark.WithParserOptions(
43 parser.WithAutoHeadingID(),
44 ),
45 )
46
47 if rctx != nil {
48 var transformers []util.PrioritizedValue
49
50 transformers = append(transformers, util.Prioritized(&MarkdownTransformer{rctx: rctx}, 10000))
51
52 md.Parser().AddOptions(
53 parser.WithASTTransformers(transformers...),
54 )
55 }
56
57 var buf bytes.Buffer
58 if err := md.Convert([]byte(source), &buf); err != nil {
59 return source
60 }
61 return buf.String()
62}
63
64type MarkdownTransformer struct {
65 rctx *RenderContext
66}
67
68func (a *MarkdownTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
69 _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
70 if !entering {
71 return ast.WalkContinue, nil
72 }
73
74 switch a.rctx.RendererType {
75 case RendererTypeRepoMarkdown:
76 a.rctx.relativeLinkTransformer(n.(*ast.Link))
77 case RendererTypeDefault:
78 a.rctx.relativeLinkTransformer(n.(*ast.Link))
79 // more types here like RendererTypeIssue/Pull etc.
80 }
81
82 return ast.WalkContinue, nil
83 })
84}
85
86func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) {
87 dst := string(link.Destination)
88
89 if len(dst) == 0 || dst[0] == '#' ||
90 bytes.Contains(link.Destination, []byte("://")) ||
91 bytes.HasPrefix(link.Destination, []byte("mailto:")) {
92 return
93 }
94
95 newPath := path.Join("/", rctx.FullRepoName, "tree", rctx.Ref, dst)
96 link.Destination = []byte(newPath)
97}