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)
23
24// RenderContext holds the contextual data for rendering markdown.
25// It can be initialized empty, and that'll skip any transformations.
26type RenderContext struct {
27 Ref string
28 FullRepoName string
29 RendererType RendererType
30}
31
32func (rctx *RenderContext) RenderMarkdown(source string) string {
33 md := goldmark.New(
34 goldmark.WithExtensions(extension.GFM),
35 goldmark.WithParserOptions(
36 parser.WithAutoHeadingID(),
37 ),
38 )
39
40 if rctx != nil {
41 var transformers []util.PrioritizedValue
42
43 transformers = append(transformers, util.Prioritized(&MarkdownTransformer{rctx: rctx}, 10000))
44
45 md.Parser().AddOptions(
46 parser.WithASTTransformers(transformers...),
47 )
48 }
49
50 var buf bytes.Buffer
51 if err := md.Convert([]byte(source), &buf); err != nil {
52 return source
53 }
54 return buf.String()
55}
56
57type MarkdownTransformer struct {
58 rctx *RenderContext
59}
60
61func (a *MarkdownTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
62 _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
63 if !entering {
64 return ast.WalkContinue, nil
65 }
66
67 switch a.rctx.RendererType {
68 case RendererTypeRepoMarkdown:
69 if v, ok := n.(*ast.Link); ok {
70 a.rctx.relativeLinkTransformer(v)
71 }
72 // more types here like RendererTypeIssue/Pull etc.
73 }
74
75 return ast.WalkContinue, nil
76 })
77}
78
79func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) {
80 dst := string(link.Destination)
81
82 if len(dst) == 0 || dst[0] == '#' ||
83 bytes.Contains(link.Destination, []byte("://")) ||
84 bytes.HasPrefix(link.Destination, []byte("mailto:")) {
85 return
86 }
87
88 newPath := path.Join("/", rctx.FullRepoName, "tree", rctx.Ref, dst)
89 link.Destination = []byte(newPath)
90}