*: more hackdoc updates

Change-Id: Ib9830c66fe36c423d38f447905c470b67cde5399
diff --git a/devtools/depotview/BUILD.bazel b/devtools/depotview/BUILD.bazel
index 223f64c..908a629 100644
--- a/devtools/depotview/BUILD.bazel
+++ b/devtools/depotview/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_layer", "container_push")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
 
 go_library(
@@ -18,3 +19,28 @@
     embed = [":go_default_library"],
     visibility = ["//visibility:public"],
 )
+
+container_layer(
+    name = "layer_bin",
+    files = [
+        ":depotview",
+    ],
+    directory = "/devtools/",
+)
+
+container_image(
+    name = "runtime",
+    base = "@prodimage-bionic//image",
+    layers = [
+        ":layer_bin",
+    ],
+)
+
+container_push(
+    name = "push",
+    image = ":runtime",
+    format = "Docker",
+    registry = "registry.k0.hswaw.net",
+    repository = "devtools/depotview",
+    tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
+)
diff --git a/devtools/depotview/README.md b/devtools/depotview/README.md
index 9b806a6..e1faca4 100644
--- a/devtools/depotview/README.md
+++ b/devtools/depotview/README.md
@@ -3,6 +3,11 @@
 
 Git-as-a-service over gRPC. Useful to get read-only access to hscloud.
 
+Production
+----------
+
+There's a prod instance running at depotview.devtools-prod.svc.cluster.local.
+
 Development
 -----------
 
diff --git a/devtools/hackdoc/BUILD.bazel b/devtools/hackdoc/BUILD.bazel
index 3988dfe..5536760 100644
--- a/devtools/hackdoc/BUILD.bazel
+++ b/devtools/hackdoc/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_layer", "container_push")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
 
 go_library(
@@ -15,6 +16,7 @@
         "//devtools/hackdoc/source:go_default_library",
         "//go/mirko:go_default_library",
         "//go/pki:go_default_library",
+        "@com_github_gabriel_vasile_mimetype//:go_default_library",
         "@com_github_golang_glog//:go_default_library",
         "@in_gopkg_russross_blackfriday_v2//:go_default_library",
         "@org_golang_google_grpc//:go_default_library",
@@ -26,3 +28,28 @@
     embed = [":go_default_library"],
     visibility = ["//visibility:public"],
 )
+
+container_layer(
+    name = "layer_bin",
+    files = [
+        ":hackdoc",
+    ],
+    directory = "/devtools/",
+)
+
+container_image(
+    name = "runtime",
+    base = "@prodimage-bionic//image",
+    layers = [
+        ":layer_bin",
+    ],
+)
+
+container_push(
+    name = "push",
+    image = ":runtime",
+    format = "Docker",
+    registry = "registry.k0.hswaw.net",
+    repository = "devtools/hackdoc",
+    tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
+)
diff --git a/devtools/hackdoc/README.md b/devtools/hackdoc/README.md
index 45d4486..3226cf2 100644
--- a/devtools/hackdoc/README.md
+++ b/devtools/hackdoc/README.md
@@ -18,6 +18,6 @@
 
 To run hackdoc locally on a filesystem checkout (ie. when working on docs, templates, or hackdoc itself), run:
 
-     bazel run //devtools/hackdoc:local
+     bazel run //devtools/hackdoc  -- -hspki_disable -docroot /path/to/hscloud
 
 The output log should tell you where hackdoc just started listening at. Currently this is `127.0.0.1:8080` by default. You can change this by passing a `-listen` flag, eg. `-listen 127.0.0.1:4242`.
diff --git a/devtools/hackdoc/config/BUILD.bazel b/devtools/hackdoc/config/BUILD.bazel
index fecf638..c5052c7 100644
--- a/devtools/hackdoc/config/BUILD.bazel
+++ b/devtools/hackdoc/config/BUILD.bazel
@@ -8,7 +8,6 @@
     deps = [
         "//devtools/hackdoc/source:go_default_library",
         "@com_github_burntsushi_toml//:go_default_library",
-        "@com_github_golang_glog//:go_default_library",
     ],
 )
 
diff --git a/devtools/hackdoc/main.go b/devtools/hackdoc/main.go
index 558268b..aae850f 100644
--- a/devtools/hackdoc/main.go
+++ b/devtools/hackdoc/main.go
@@ -111,7 +111,6 @@
 		return
 	}
 
-	glog.Infof("%+v", r.URL.Query())
 	ref := r.URL.Query().Get("ref")
 	if ref == "" {
 		ref = flagGitwebDefaultBranch
@@ -206,7 +205,7 @@
 		}
 
 		if file {
-			r.handleMarkdown(fpath, cfg)
+			http.Redirect(r.w, r.r, "/"+fpath, 302)
 			return
 		}
 	}
@@ -238,13 +237,18 @@
 	if file {
 		parts := strings.Split(r.rpath, "/")
 		dirpath := strings.Join(parts[:(len(parts)-1)], "/")
+		// TODO(q3k): figure out this hack, hopefully by implementing a real path type
+		if dirpath == "/" {
+			dirpath = "//"
+		}
+
 		cfg, err := config.ForPath(r.ctx, r.source, dirpath)
 		if err != nil {
 			glog.Errorf("could not get config for path %q: %w", dirpath, err)
 			r.handle500()
 			return
 		}
-		r.handleMarkdown(r.rpath, cfg)
+		r.handleFile(r.rpath, cfg)
 		return
 	}
 
diff --git a/devtools/hackdoc/markdown.go b/devtools/hackdoc/markdown.go
index 911c2c0..fb46c5d 100644
--- a/devtools/hackdoc/markdown.go
+++ b/devtools/hackdoc/markdown.go
@@ -8,6 +8,7 @@
 
 	"code.hackerspace.pl/hscloud/devtools/hackdoc/config"
 
+	"github.com/gabriel-vasile/mimetype"
 	"github.com/golang/glog"
 	"gopkg.in/russross/blackfriday.v2"
 )
@@ -31,6 +32,7 @@
 				q["ref"] = []string{ref}
 				u.RawQuery = q.Encode()
 				node.Destination = []byte(u.String())
+				glog.Infof("link fix %q -> %q", dest, u.String())
 			}
 		}
 		return r.RenderNode(&buf, node, entering)
@@ -38,7 +40,12 @@
 	return buf.Bytes()
 }
 
-func (r *request) handleMarkdown(path string, cfg *config.Config) {
+type pathPart struct {
+	Label string
+	Path  string
+}
+
+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)
@@ -46,30 +53,55 @@
 		return
 	}
 
-	rendered := renderMarkdown([]byte(data), r.ref)
+	// 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)
+		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)
+		// 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
+			pathParts = append(pathParts, pathPart{Label: label, Path: fullPath})
+		}
+
+		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
 	}
 
-	pathInDepot := strings.TrimPrefix(path, "//")
-	vars := map[string]interface{}{
-		"Rendered":    template.HTML(rendered),
-		"Title":       path,
-		"Path":        path,
-		"PathInDepot": pathInDepot,
-		"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)
-	}
+	// Just serve the file.
+	mime := mimetype.Detect(data)
+	r.w.Header().Set("Content-Type", mime.String())
+	r.w.Write(data)
 }
diff --git a/devtools/hackdoc/tpl/default.html b/devtools/hackdoc/tpl/default.html
index 7120962..a6c1d42 100644
--- a/devtools/hackdoc/tpl/default.html
+++ b/devtools/hackdoc/tpl/default.html
@@ -58,7 +58,7 @@
 }
 
 .column {
-    max-width: 80em;
+    width: 80em;
     padding: 1rem 0 1rem 0;
 }
 
@@ -85,8 +85,16 @@
     color: #b30014;
 }
 
-.header span.muted {
+.header span.part {
     color: #666;
+    padding-left: 0.2em;
+}
+
+.header span.part a {
+    color: rgb(27, 106, 203);
+}
+.header span.part a:visited {
+    color: rgb(27, 106, 203);
 }
 
 .footer {
@@ -98,6 +106,14 @@
     text-align: right;
 }
 
+.footer .left {
+    float: left;
+}
+
+.footer .right {
+    float: right;
+}
+
 .footer a {
     color: #bbb;
 }
@@ -164,15 +180,20 @@
     <div class="column">
         <div class="page">
             <div class="header">
-                <span class="red">hackdoc:</span><span>{{ .Path }}</span>
-                {{ range .WebLinks }}
-                <span class="muted">[{{ .Kind }} <a href="{{ .LinkURL }}">{{ .LinkLabel }}</a>]</span>
-                {{ end }}
+                <span class="red">hackdoc:</span>
+                {{ range .PathParts }}<span class="part"><a href="{{ .Path }}">{{ .Label }}</a></span>{{ end }}
+                <span class="red" style="margin-left: 1em;">shortcuts:</span> <a href="/">root</a>, <a href="/cluster/doc">cluster docs</a>, <a href="/doc/codelabs">codelabs</a>
             </div>
             {{ .Rendered }}
         </div>
         <div class="footer">
-            Generated by <a href="{{ .HackdocURL }}/devtools/hackdoc">hackdoc</a>.
+            <div class="left">
+                View in:
+                {{ range .WebLinks }}
+                <span class="muted">[{{ .Kind }} <a href="{{ .LinkURL }}">{{ .LinkLabel }}</a>]</span>
+                {{ end }}
+            </div>
+            <div class="right">Generated by <a href="{{ .HackdocURL }}/devtools/hackdoc">hackdoc</a>.</div>
         </div>
     </div>
 </div>