Migration
This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs.
Contact me
internal/modules/markdown.go
caching_trials
· 2.7 KB · 83 lines
raw
| 1 | package modules |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "html/template" |
| 6 | "path" |
| 7 | "strings" |
| 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 | ) |
| 15 | |
| 16 | // The options here mirror Hugo's goldmark defaults on purpose: GFM, |
| 17 | // typographer, and automatic heading IDs. Same renderer, same extensions, |
| 18 | // so a README renders identically to the same file on your blog. |
| 19 | // |
| 20 | // WithUnsafe is deliberately NOT set -- also Hugo's default. Anyone who can |
| 21 | // push can write a README, so raw HTML in markdown is untrusted input; |
| 22 | // goldmark drops it rather than passing a <script> through into the page. |
| 23 | var md = goldmark.New( |
| 24 | goldmark.WithExtensions(extension.GFM, extension.Typographer), |
| 25 | goldmark.WithParserOptions(parser.WithAutoHeadingID()), |
| 26 | ) |
| 27 | |
| 28 | // Markdown renders README source. |
| 29 | // |
| 30 | // base — "/slug", the repository root URL |
| 31 | // ref — the ref being viewed, for building links |
| 32 | // dir — directory the source file lives in, for resolving relative links |
| 33 | // |
| 34 | // Swap this variable to change engines; nothing else imports goldmark. |
| 35 | var Markdown = func(src []byte, base, ref, dir string) (template.HTML, error) { |
| 36 | doc := md.Parser().Parse(text.NewReader(src)) |
| 37 | rewriteLinks(doc, base, ref, dir) |
| 38 | |
| 39 | var buf bytes.Buffer |
| 40 | if err := md.Renderer().Render(&buf, src, doc); err != nil { |
| 41 | return "", err |
| 42 | } |
| 43 | return template.HTML(buf.String()), nil |
| 44 | } |
| 45 | |
| 46 | // rewriteLinks points relative markdown links at the browser instead of |
| 47 | // leaving them dangling. A link to docs/design.md becomes the blob view; |
| 48 | // an image becomes /raw/, because the blob view is an HTML page and an |
| 49 | // <img> needs actual bytes. |
| 50 | func rewriteLinks(doc ast.Node, base, ref, dir string) { |
| 51 | ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) { |
| 52 | if !entering { |
| 53 | return ast.WalkContinue, nil |
| 54 | } |
| 55 | switch v := n.(type) { |
| 56 | case *ast.Link: |
| 57 | if p, ok := resolve(string(v.Destination), dir); ok { |
| 58 | v.Destination = []byte(base + "/blob/" + ref + "/" + p) |
| 59 | } |
| 60 | case *ast.Image: |
| 61 | if p, ok := resolve(string(v.Destination), dir); ok { |
| 62 | v.Destination = []byte(base + "/raw/" + ref + "/" + p) |
| 63 | } |
| 64 | } |
| 65 | return ast.WalkContinue, nil |
| 66 | }) |
| 67 | } |
| 68 | |
| 69 | // resolve reports whether dest is repo-relative, and if so its path from the |
| 70 | // repo root. Absolute URLs, protocol-relative URLs, site-absolute paths and |
| 71 | // bare fragments are left alone. |
| 72 | func resolve(dest, dir string) (string, bool) { |
| 73 | if dest == "" || strings.HasPrefix(dest, "#") || strings.HasPrefix(dest, "/") || |
| 74 | strings.HasPrefix(dest, "//") || strings.Contains(dest, "://") || |
| 75 | strings.HasPrefix(dest, "mailto:") { |
| 76 | return "", false |
| 77 | } |
| 78 | p := path.Clean(path.Join(dir, dest)) |
| 79 | if p == "." || strings.HasPrefix(p, "..") { |
| 80 | return "", false // escapes the repo root |
| 81 | } |
| 82 | return p, true |
| 83 | } |