Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 4 | "bytes" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 5 | "html/template" |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 6 | "net/url" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 7 | "strings" |
| 8 | |
| 9 | "code.hackerspace.pl/hscloud/devtools/hackdoc/config" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 10 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 11 | "github.com/gabriel-vasile/mimetype" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 12 | "github.com/golang/glog" |
| 13 | "gopkg.in/russross/blackfriday.v2" |
| 14 | ) |
| 15 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 16 | // renderMarkdown renders markdown to HTML, replacing all relative (intra-hackdoc) links with version that have ref set. |
| 17 | func renderMarkdown(input []byte, ref string) []byte { |
| 18 | r := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ |
Sergiusz Bazanski | 5bce7ce | 2020-04-11 20:16:58 +0200 | [diff] [blame] | 19 | Flags: blackfriday.CommonHTMLFlags | blackfriday.TOC, |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 20 | }) |
| 21 | |
Serge Bazanski | 26f44da | 2020-09-23 18:12:11 +0000 | [diff] [blame] | 22 | // master is the default branch - do not make special links for that, as |
| 23 | // that makes them kinda ugly. |
Serge Bazanski | 81262ff | 2021-03-06 20:44:56 +0000 | [diff] [blame] | 24 | if ref == flagGitwebDefaultBranch { |
Serge Bazanski | 26f44da | 2020-09-23 18:12:11 +0000 | [diff] [blame] | 25 | ref = "" |
| 26 | } |
| 27 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 28 | parser := blackfriday.New(blackfriday.WithRenderer(r), blackfriday.WithExtensions(blackfriday.CommonExtensions)) |
| 29 | ast := parser.Parse(input) |
| 30 | |
Serge Bazanski | 0a2f413 | 2020-09-23 18:13:05 +0000 | [diff] [blame] | 31 | // Render table of contents (raw HTML) into bytes. |
| 32 | var tocB bytes.Buffer |
| 33 | tocB.Write([]byte(`<div class="toc">`)) |
| 34 | r.RenderHeader(&tocB, ast) |
| 35 | tocB.Write([]byte(`</div>`)) |
| 36 | toc := tocB.Bytes() |
| 37 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 38 | var buf bytes.Buffer |
Serge Bazanski | 0a2f413 | 2020-09-23 18:13:05 +0000 | [diff] [blame] | 39 | buf.Write([]byte(`<div class="content">`)) |
| 40 | // Render Markdown with some custom behaviour. |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 41 | ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { |
Serge Bazanski | 0a2f413 | 2020-09-23 18:13:05 +0000 | [diff] [blame] | 42 | // Fix intra-hackdoc links to contain ?ref= |
Sergiusz Bazanski | 4b4a33a | 2020-04-13 01:35:33 +0200 | [diff] [blame] | 43 | if ref != "" && entering && node.Type == blackfriday.Link || node.Type == blackfriday.Image { |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 44 | dest := string(node.Destination) |
| 45 | u, err := url.Parse(dest) |
| 46 | if err == nil && !u.IsAbs() { |
| 47 | q := u.Query() |
| 48 | q["ref"] = []string{ref} |
| 49 | u.RawQuery = q.Encode() |
| 50 | node.Destination = []byte(u.String()) |
Serge Bazanski | 0a2f413 | 2020-09-23 18:13:05 +0000 | [diff] [blame] | 51 | glog.V(10).Infof("link fix %q -> %q", dest, u.String()) |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 52 | } |
| 53 | } |
Serge Bazanski | 0a2f413 | 2020-09-23 18:13:05 +0000 | [diff] [blame] | 54 | // Replace [TOC] anchor with a rendered TOC. |
| 55 | if entering && node.Type == blackfriday.Text && string(node.Literal) == "[TOC]" { |
| 56 | buf.Write(toc) |
| 57 | return blackfriday.GoToNext |
| 58 | } |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 59 | return r.RenderNode(&buf, node, entering) |
| 60 | }) |
Sergiusz Bazanski | 5bce7ce | 2020-04-11 20:16:58 +0200 | [diff] [blame] | 61 | buf.Write([]byte(`</div>`)) |
| 62 | r.RenderFooter(&buf, ast) |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 63 | return buf.Bytes() |
| 64 | } |
| 65 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 66 | type pathPart struct { |
| 67 | Label string |
| 68 | Path string |
| 69 | } |
| 70 | |
Serge Bazanski | d701c4e | 2020-08-10 17:59:59 +0200 | [diff] [blame] | 71 | func (r *request) renderable(dirpath string) bool { |
| 72 | cfg, err := config.ForPath(r.ctx, r.source, dirpath) |
| 73 | if err != nil { |
| 74 | glog.Errorf("could not get config for path %q: %v", dirpath, err) |
| 75 | return false |
| 76 | } |
| 77 | |
| 78 | for _, f := range cfg.DefaultIndex { |
| 79 | fpath := dirpath + "/" + f |
| 80 | file, err := r.source.IsFile(r.ctx, fpath) |
| 81 | if err != nil { |
| 82 | glog.Errorf("IsFile(%q): %v", fpath, err) |
| 83 | return false |
| 84 | } |
| 85 | |
| 86 | if file { |
| 87 | return true |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | return false |
| 92 | } |
| 93 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 94 | func (r *request) handleFile(path string, cfg *config.Config) { |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 95 | data, err := r.source.ReadFile(r.ctx, path) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 96 | if err != nil { |
| 97 | glog.Errorf("ReadFile(%q): %w", err) |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 98 | r.handle500() |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 99 | return |
| 100 | } |
| 101 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 102 | // TODO(q3k): do MIME detection instead. |
| 103 | if strings.HasSuffix(path, ".md") { |
| 104 | rendered := renderMarkdown([]byte(data), r.ref) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 105 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 106 | r.logRequest("serving markdown at %s, cfg %+v", path, cfg) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 107 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 108 | // TODO(q3k): allow markdown files to override which template to load |
| 109 | tmpl, ok := cfg.Templates["default"] |
| 110 | if !ok { |
| 111 | glog.Errorf("No default template found for %s", path) |
| 112 | // TODO(q3k): implement fallback template |
| 113 | r.w.Write(rendered) |
| 114 | return |
| 115 | } |
| 116 | |
| 117 | pathInDepot := strings.TrimPrefix(path, "//") |
| 118 | pathParts := []pathPart{ |
| 119 | {Label: "//", Path: "/"}, |
| 120 | } |
| 121 | parts := strings.Split(pathInDepot, "/") |
| 122 | fullPath := "" |
| 123 | for i, p := range parts { |
| 124 | label := p |
| 125 | if i != len(parts)-1 { |
| 126 | label = label + "/" |
| 127 | } |
| 128 | fullPath += "/" + p |
Serge Bazanski | d701c4e | 2020-08-10 17:59:59 +0200 | [diff] [blame] | 129 | target := fullPath |
| 130 | if i != len(parts)-1 && !r.renderable("/"+fullPath) { |
| 131 | target = "" |
| 132 | } |
| 133 | pathParts = append(pathParts, pathPart{Label: label, Path: target}) |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | vars := map[string]interface{}{ |
| 137 | "Rendered": template.HTML(rendered), |
| 138 | "Title": path, |
| 139 | "Path": path, |
| 140 | "PathInDepot": pathInDepot, |
| 141 | "PathParts": pathParts, |
| 142 | "HackdocURL": flagHackdocURL, |
| 143 | "WebLinks": r.source.WebLinks(pathInDepot), |
| 144 | } |
| 145 | err = tmpl.Execute(r.w, vars) |
| 146 | if err != nil { |
| 147 | glog.Errorf("Could not execute template for %s: %v", err) |
| 148 | } |
| 149 | |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 150 | return |
| 151 | } |
| 152 | |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 153 | // Just serve the file. |
Serge Bazanski | 56c262f | 2021-03-23 15:50:19 +0000 | [diff] [blame] | 154 | var mime string |
| 155 | if strings.HasSuffix(path, ".js") { |
| 156 | // Force .js to always be the correct MIME type. |
| 157 | mime = "text/javascript" |
| 158 | } else { |
| 159 | // Otherwise, use magic to detect type. |
| 160 | mime = mimetype.Detect(data).String() |
| 161 | } |
| 162 | r.w.Header().Set("Content-Type", mime) |
Sergiusz Bazanski | 8adbd49 | 2020-04-10 21:20:53 +0200 | [diff] [blame] | 163 | r.w.Write(data) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 164 | } |