mirror of https://gerrit.hackerspace.pl/hscloud
158 lines
4.0 KiB
Go
158 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"html/template"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"code.hackerspace.pl/hscloud/devtools/hackdoc/config"
|
|
|
|
"github.com/gabriel-vasile/mimetype"
|
|
"github.com/golang/glog"
|
|
"gopkg.in/russross/blackfriday.v2"
|
|
)
|
|
|
|
// renderMarkdown renders markdown to HTML, replacing all relative (intra-hackdoc) links with version that have ref set.
|
|
func renderMarkdown(input []byte, ref string) []byte {
|
|
r := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
|
|
Flags: blackfriday.CommonHTMLFlags | blackfriday.TOC,
|
|
})
|
|
|
|
// master is the default branch - do not make special links for that, as
|
|
// that makes them kinda ugly.
|
|
if ref == "master" {
|
|
ref = ""
|
|
}
|
|
|
|
parser := blackfriday.New(blackfriday.WithRenderer(r), blackfriday.WithExtensions(blackfriday.CommonExtensions))
|
|
ast := parser.Parse(input)
|
|
|
|
// Render table of contents (raw HTML) into bytes.
|
|
var tocB bytes.Buffer
|
|
tocB.Write([]byte(`<div class="toc">`))
|
|
r.RenderHeader(&tocB, ast)
|
|
tocB.Write([]byte(`</div>`))
|
|
toc := tocB.Bytes()
|
|
|
|
var buf bytes.Buffer
|
|
buf.Write([]byte(`<div class="content">`))
|
|
// Render Markdown with some custom behaviour.
|
|
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
|
// Fix intra-hackdoc links to contain ?ref=
|
|
if ref != "" && entering && node.Type == blackfriday.Link || node.Type == blackfriday.Image {
|
|
dest := string(node.Destination)
|
|
u, err := url.Parse(dest)
|
|
if err == nil && !u.IsAbs() {
|
|
q := u.Query()
|
|
q["ref"] = []string{ref}
|
|
u.RawQuery = q.Encode()
|
|
node.Destination = []byte(u.String())
|
|
glog.V(10).Infof("link fix %q -> %q", dest, u.String())
|
|
}
|
|
}
|
|
// Replace [TOC] anchor with a rendered TOC.
|
|
if entering && node.Type == blackfriday.Text && string(node.Literal) == "[TOC]" {
|
|
buf.Write(toc)
|
|
return blackfriday.GoToNext
|
|
}
|
|
return r.RenderNode(&buf, node, entering)
|
|
})
|
|
buf.Write([]byte(`</div>`))
|
|
r.RenderFooter(&buf, ast)
|
|
return buf.Bytes()
|
|
}
|
|
|
|
type pathPart struct {
|
|
Label string
|
|
Path string
|
|
}
|
|
|
|
func (r *request) renderable(dirpath string) bool {
|
|
cfg, err := config.ForPath(r.ctx, r.source, dirpath)
|
|
if err != nil {
|
|
glog.Errorf("could not get config for path %q: %v", dirpath, err)
|
|
return false
|
|
}
|
|
|
|
for _, f := range cfg.DefaultIndex {
|
|
fpath := dirpath + "/" + f
|
|
file, err := r.source.IsFile(r.ctx, fpath)
|
|
if err != nil {
|
|
glog.Errorf("IsFile(%q): %v", fpath, err)
|
|
return false
|
|
}
|
|
|
|
if file {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (r *request) handleFile(path string, cfg *config.Config) {
|
|
data, err := r.source.ReadFile(r.ctx, path)
|
|
if err != nil {
|
|
glog.Errorf("ReadFile(%q): %w", err)
|
|
r.handle500()
|
|
return
|
|
}
|
|
|
|
// TODO(q3k): do MIME detection instead.
|
|
if strings.HasSuffix(path, ".md") {
|
|
rendered := renderMarkdown([]byte(data), r.ref)
|
|
|
|
r.logRequest("serving markdown at %s, cfg %+v", path, cfg)
|
|
|
|
// TODO(q3k): allow markdown files to override which template to load
|
|
tmpl, ok := cfg.Templates["default"]
|
|
if !ok {
|
|
glog.Errorf("No default template found for %s", path)
|
|
// TODO(q3k): implement fallback template
|
|
r.w.Write(rendered)
|
|
return
|
|
}
|
|
|
|
pathInDepot := strings.TrimPrefix(path, "//")
|
|
pathParts := []pathPart{
|
|
{Label: "//", Path: "/"},
|
|
}
|
|
parts := strings.Split(pathInDepot, "/")
|
|
fullPath := ""
|
|
for i, p := range parts {
|
|
label := p
|
|
if i != len(parts)-1 {
|
|
label = label + "/"
|
|
}
|
|
fullPath += "/" + p
|
|
target := fullPath
|
|
if i != len(parts)-1 && !r.renderable("/"+fullPath) {
|
|
target = ""
|
|
}
|
|
pathParts = append(pathParts, pathPart{Label: label, Path: target})
|
|
}
|
|
|
|
vars := map[string]interface{}{
|
|
"Rendered": template.HTML(rendered),
|
|
"Title": path,
|
|
"Path": path,
|
|
"PathInDepot": pathInDepot,
|
|
"PathParts": pathParts,
|
|
"HackdocURL": flagHackdocURL,
|
|
"WebLinks": r.source.WebLinks(pathInDepot),
|
|
}
|
|
err = tmpl.Execute(r.w, vars)
|
|
if err != nil {
|
|
glog.Errorf("Could not execute template for %s: %v", err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Just serve the file.
|
|
mime := mimetype.Detect(data)
|
|
r.w.Header().Set("Content-Type", mime.String())
|
|
r.w.Write(data)
|
|
}
|