forked from tangled.org/core
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 "net/url" 7 "path" 8 9 "github.com/yuin/goldmark" 10 "github.com/yuin/goldmark/ast" 11 "github.com/yuin/goldmark/extension" 12 "github.com/yuin/goldmark/parser" 13 "github.com/yuin/goldmark/text" 14 "github.com/yuin/goldmark/util" 15 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 16) 17 18// RendererType defines the type of renderer to use based on context 19type RendererType int 20 21const ( 22 // RendererTypeRepoMarkdown is for repository documentation markdown files 23 RendererTypeRepoMarkdown RendererType = iota 24) 25 26// RenderContext holds the contextual data for rendering markdown. 27// It can be initialized empty, and that'll skip any transformations. 28type RenderContext struct { 29 repoinfo.RepoInfo 30 IsDev bool 31 RendererType RendererType 32} 33 34func (rctx *RenderContext) RenderMarkdown(source string) string { 35 md := goldmark.New( 36 goldmark.WithExtensions(extension.GFM), 37 goldmark.WithParserOptions( 38 parser.WithAutoHeadingID(), 39 ), 40 ) 41 42 if rctx != nil { 43 var transformers []util.PrioritizedValue 44 45 transformers = append(transformers, util.Prioritized(&MarkdownTransformer{rctx: rctx}, 10000)) 46 47 md.Parser().AddOptions( 48 parser.WithASTTransformers(transformers...), 49 ) 50 } 51 52 var buf bytes.Buffer 53 if err := md.Convert([]byte(source), &buf); err != nil { 54 return source 55 } 56 return buf.String() 57} 58 59type MarkdownTransformer struct { 60 rctx *RenderContext 61} 62 63func (a *MarkdownTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { 64 _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { 65 if !entering { 66 return ast.WalkContinue, nil 67 } 68 69 switch a.rctx.RendererType { 70 case RendererTypeRepoMarkdown: 71 switch n.(type) { 72 case *ast.Link: 73 a.rctx.relativeLinkTransformer(n.(*ast.Link)) 74 case *ast.Image: 75 a.rctx.imageFromKnotTransformer(n.(*ast.Image)) 76 } 77 // more types here like RendererTypeIssue/Pull etc. 78 } 79 80 return ast.WalkContinue, nil 81 }) 82} 83 84func (rctx *RenderContext) relativeLinkTransformer(link *ast.Link) { 85 dst := string(link.Destination) 86 87 if isAbsoluteUrl(dst) { 88 return 89 } 90 91 newPath := path.Join("/", rctx.RepoInfo.FullName(), "tree", rctx.RepoInfo.Ref, dst) 92 link.Destination = []byte(newPath) 93} 94 95func (rctx *RenderContext) imageFromKnotTransformer(img *ast.Image) { 96 dst := string(img.Destination) 97 98 if isAbsoluteUrl(dst) { 99 return 100 } 101 102 // strip leading './' 103 if len(dst) >= 2 && dst[0:2] == "./" { 104 dst = dst[2:] 105 } 106 107 scheme := "https" 108 if rctx.IsDev { 109 scheme = "http" 110 } 111 parsedURL := &url.URL{ 112 Scheme: scheme, 113 Host: rctx.Knot, 114 Path: path.Join("/", 115 rctx.RepoInfo.OwnerDid, 116 rctx.RepoInfo.Name, 117 "raw", 118 url.PathEscape(rctx.RepoInfo.Ref), 119 dst), 120 } 121 newPath := parsedURL.String() 122 img.Destination = []byte(newPath) 123} 124 125func isAbsoluteUrl(link string) bool { 126 parsed, err := url.Parse(link) 127 if err != nil { 128 return false 129 } 130 return parsed.IsAbs() 131}