internal/web: move SrcBreadcrumb SrcPkgLink, invoke into templates

There's no real need for these to be written in Go.

Change-Id: Ifb3ebc99a06bb9670a39238a1ddf370c2dcd6a22
Reviewed-on: https://go-review.googlesource.com/c/website/+/339402
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/_content/lib/godoc/example.html b/_content/lib/godoc/example.html
deleted file mode 100644
index be4d1b1..0000000
--- a/_content/lib/godoc/example.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{{with .Data}}
-<div id="example_{{.Name}}" class="toggle">
-  <div class="collapsed">
-    <p class="exampleHeading toggleButton">▹ <span class="text">Example{{.Page.ExampleSuffix .Name}}</span></p>
-  </div>
-  <div class="expanded">
-    <p class="exampleHeading toggleButton">▾ <span class="text">Example{{.Page.ExampleSuffix .Name}}</span></p>
-    {{with .Doc}}<p>{{.}}</p>{{end}}
-    {{$output := .Output}}
-    {{with .Play}}
-      <div class="play">
-        <div class="input"><textarea class="code" spellcheck="false">{{.}}</textarea></div>
-        <div class="output"><pre>{{html $output}}</pre></div>
-        <div class="buttons">
-          <button class="Button Button--primary run" title="Run this code [shift-enter]">Run</button>
-          <button class="Button fmt" title="Format this code">Format</button>
-          {{if not $.GoogleCN}}
-            <button class="Button share" title="Share this code">Share</button>
-          {{end}}
-        </div>
-      </div>
-    {{else}}
-      <p>Code:</p>
-      <pre class="code">{{.Code}}</pre>
-      {{with .Output}}
-        <p>Output:</p>
-        <pre class="output">{{.}}</pre>
-      {{end}}
-    {{end}}
-  </div>
-</div>
-{{end}}
diff --git a/_content/lib/godoc/package.html b/_content/lib/godoc/package.html
index 9189dcc..e2cbd4d 100644
--- a/_content/lib/godoc/package.html
+++ b/_content/lib/godoc/package.html
@@ -39,7 +39,7 @@
 			<div class="expanded">
 				<h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
 				{{$pkg.Comment .Doc}}
-				{{range $pkg.FmtExamples ""}}{{$.Invoke "example.html" .}}{{end}}
+				{{range $pkg.FmtExamples ""}}{{template "example" .}}{{end}}
 			</div>
 		</div>
 
@@ -126,7 +126,7 @@
 			</h2>
 			<pre>{{$pkg.Node .Decl}}</pre>
 			{{$pkg.Comment .Doc}}
-			{{range $pkg.FmtExamples .Name}}{{$.Invoke "example.html" .}}{{end}}
+			{{range $pkg.FmtExamples .Name}}{{template "example" .}}{{end}}
 		{{end}}
 		{{range .Types}}
 			{{$typeName := .Name}}
@@ -148,7 +148,7 @@
 				<pre>{{$pkg.Node .Decl}}</pre>
 			{{end}}
 
-			{{range $pkg.FmtExamples .Name}}{{$.Invoke "example.html" .}}{{end}}
+			{{range $pkg.FmtExamples .Name}}{{template "example" .}}{{end}}
 
 			{{range .Funcs}}
 				<h3 id="{{.Name}}">func <a href="{{$pkg.SrcPosLink .Decl}}">{{.Name}}</a>
@@ -158,7 +158,7 @@
 				</h3>
 				<pre>{{$pkg.Node .Decl}}</pre>
 				{{$pkg.Comment .Doc}}
-				{{range $pkg.FmtExamples .Name}}{{$.Invoke "example.html" .}}{{end}}
+				{{range $pkg.FmtExamples .Name}}{{template "example" .}}{{end}}
 			{{end}}
 
 			{{range .Methods}}
@@ -169,7 +169,7 @@
 				</h3>
 				<pre>{{$pkg.Node .Decl}}</pre>
 				{{$pkg.Comment .Doc}}
-				{{range $pkg.FmtExamples (printf "%s_%s" $typeName .Name)}}{{$.Invoke "example.html" .}}{{end}}
+				{{range $pkg.FmtExamples (printf "%s_%s" $typeName .Name)}}{{template "example" .}}{{end}}
 			{{end}}
 		{{end}}
 	{{end}}
@@ -211,7 +211,7 @@
 						</td>
 					{{end}}
 				{{else}}
-					<td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;">
+					<td class="pkg-name" style="padding-left: {{mul .Depth 20}}px;">
 						<a href="{{.Path}}/{{$pkg.ModeQuery}}">{{.Name}}</a>
 					</td>
 				{{end}}
@@ -223,3 +223,36 @@
 		</table>
 	</div>
 {{end}}
+
+{{define "example"}}
+<div id="example_{{.Name}}" class="toggle">
+  <div class="collapsed">
+    <p class="exampleHeading toggleButton">▹ <span class="text">Example{{.Page.ExampleSuffix .Name}}</span></p>
+  </div>
+  <div class="expanded">
+    <p class="exampleHeading toggleButton">▾ <span class="text">Example{{.Page.ExampleSuffix .Name}}</span></p>
+    {{with .Doc}}<p>{{.}}</p>{{end}}
+    {{$output := .Output}}
+    {{with .Play}}
+      <div class="play">
+        <div class="input"><textarea class="code" spellcheck="false">{{.}}</textarea></div>
+        <div class="output"><pre>{{html $output}}</pre></div>
+        <div class="buttons">
+          <button class="Button Button--primary run" title="Run this code [shift-enter]">Run</button>
+          <button class="Button fmt" title="Format this code">Format</button>
+          {{if not $.Page.Web.GoogleCN}}
+            <button class="Button share" title="Share this code">Share</button>
+          {{end}}
+        </div>
+      </div>
+    {{else}}
+      <p>Code:</p>
+      <pre class="code">{{.Code}}</pre>
+      {{with .Output}}
+        <p>Output:</p>
+        <pre class="output">{{.}}</pre>
+      {{end}}
+    {{end}}
+  </div>
+</div>
+{{end}}
diff --git a/_content/lib/godoc/packageroot.html b/_content/lib/godoc/packageroot.html
index e2041b9..aaa2c53 100644
--- a/_content/lib/godoc/packageroot.html
+++ b/_content/lib/godoc/packageroot.html
@@ -48,7 +48,7 @@
 										</td>
 								{{end}}
 							{{else}}
-									<td class="pkg-name" style="padding-left: {{multiply .Depth 20}}px;">
+									<td class="pkg-name" style="padding-left: {{mul .Depth 20}}px;">
 										<a href="{{.Path}}/{{$pkg.ModeQuery}}">{{.Name}}</a>
 									</td>
 							{{end}}
diff --git a/_content/lib/godoc/site.html b/_content/lib/godoc/site.html
index 2f3e496..09624be 100644
--- a/_content/lib/godoc/site.html
+++ b/_content/lib/godoc/site.html
@@ -61,10 +61,22 @@
 <main id="page" class="Site-content{{if .Title}} wide{{end}}">
 <div class="container">
 
+{{define "srcBreadcrumb"}}
+	{{$elems := split . "/"}}
+	{{$prefix := slice $elems 0 (sub (len $elems) 1)}}
+	{{if hasSuffix . "/"}}
+		{{$prefix = slice $elems 0 (sub (len $elems) 2)}}
+	{{end}}
+	{{range $i, $elem := $prefix -}}
+		<a href="/{{join (slice $prefix 0 (add $i 1)) "/"}}">{{$elem}}</a>/
+	{{- end -}}
+	<span class="text-muted">{{join (slice $elems (len $prefix) (len $elems)) "/"}} {{len $prefix}} {{len $elems}}</span>
+{{end}}
+
 {{if or .Title .SrcPath}}
   <h1>
     {{.Title}}
-    {{$.SrcBreadcrumb}}
+    {{template "srcBreadcrumb" .SrcPath}}
   </h1>
 {{end}}
 
@@ -72,9 +84,15 @@
   <h2>{{.}}</h2>
 {{end}}
 
-{{with .SrcPath}}
+{{if hasPrefix .SrcPath "src/"}}
   <h2>
-    Documentation: {{$.SrcPkgLink}}
+    Documentation:
+    {{$path := trimPrefix .SrcPath "src/"}}
+    {{if $path}}
+      <a href="/pkg/{{$path}}">{{$path}}</a>
+    {{else}}
+      <a href="/pkg">Index</a>
+    {{end}}
   </h2>
 {{end}}
 
@@ -82,11 +100,7 @@
      Do not delete this <div>. */}}
 <div id="nav"></div>
 
-{{if .Template}}
-{{.Invoke .Template .Data}}
-{{else}}
-{{.Data}}
-{{end}}
+{{.HTML}}
 
 </div><!-- .container -->
 </main><!-- #page -->
diff --git a/internal/web/site.go b/internal/web/site.go
index c4d9a30..4d57ab7 100644
--- a/internal/web/site.go
+++ b/internal/web/site.go
@@ -40,6 +40,22 @@
 	docFuncs template.FuncMap
 }
 
+var siteFuncs = template.FuncMap{
+	"add": func(a, b int) int { return a + b },
+	"sub": func(a, b int) int { return a - b },
+	"mul": func(a, b int) int { return a * b },
+	"div": func(a, b int) int { return a / b },
+
+	"basename": path.Base,
+
+	"split":      strings.Split,
+	"join":       strings.Join,
+	"hasPrefix":  strings.HasPrefix,
+	"hasSuffix":  strings.HasSuffix,
+	"trimPrefix": strings.TrimPrefix,
+	"trimSuffix": strings.TrimSuffix,
+}
+
 // NewSite returns a new Presentation from a file system.
 func NewSite(fsys fs.FS) (*Site, error) {
 	p := &Site{
@@ -80,6 +96,18 @@
 	if d, ok := page.Data.(interface{ SetWebPage(*Page) }); ok {
 		d.SetWebPage(&page)
 	}
+
+	if page.Template != "" {
+		t := s.Templates.Lookup(page.Template)
+		var buf bytes.Buffer
+		if err := t.Execute(&buf, &page); err != nil {
+			log.Printf("%s.Execute: %s", t.Name(), err)
+		}
+		page.HTML = template.HTML(buf.String())
+	} else {
+		page.HTML = page.Data.(template.HTML)
+	}
+
 	applyTemplateToResponseWriter(w, s.Templates.Lookup("site.html"), &page)
 }
 
@@ -106,6 +134,8 @@
 	Template string      // template to apply to data (empty string when Data is raw template.HTML)
 	Data     interface{} // data to be rendered into page frame
 
+	HTML template.HTML
+
 	// Filled in automatically by ServePage
 	GoogleCN        bool   // served on golang.google.cn
 	GoogleAnalytics string // Google Analytics tag
@@ -125,19 +155,6 @@
 	return page
 }
 
-// Invoke invokes the template with the given name on
-// a copy of p with .Data set to data, returning the resulting HTML.
-func (p *Page) Invoke(name string, data interface{}) template.HTML {
-	t := p.Site.Templates.Lookup(name)
-	var buf bytes.Buffer
-	p1 := *p
-	p1.Data = data
-	if err := t.Execute(&buf, &p1); err != nil {
-		log.Printf("%s.Execute: %s", t.Name(), err)
-	}
-	return template.HTML(buf.String())
-}
-
 type writeErrorSaver struct {
 	w   io.Writer
 	err error
@@ -328,10 +345,11 @@
 		}
 	}
 
+	dirpath := strings.TrimSuffix(relpath, "/") + "/"
 	s.ServePage(w, r, Page{
 		Title:    "Directory",
-		SrcPath:  relpath,
-		TabTitle: relpath,
+		SrcPath:  dirpath,
+		TabTitle: dirpath,
 		Template: "dirlist.html",
 		Data:     info,
 	})
diff --git a/internal/web/sitefuncs.go b/internal/web/sitefuncs.go
deleted file mode 100644
index 747973b..0000000
--- a/internal/web/sitefuncs.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package web
-
-import (
-	"bytes"
-	"fmt"
-	"html"
-	"path"
-	"strings"
-
-	"golang.org/x/website/internal/backport/html/template"
-)
-
-var siteFuncs = template.FuncMap{
-	// various helpers
-	"basename": path.Base,
-
-	// Number operation
-	"multiply": func(a, b int) int { return a * b },
-}
-
-func srcToPkg(path string) string {
-	// because of the irregular mapping under goroot
-	// we need to correct certain relative paths
-	path = strings.TrimPrefix(path, "/")
-	path = strings.TrimPrefix(path, "src/")
-	path = strings.TrimPrefix(path, "pkg/")
-	return "pkg/" + path
-}
-
-// SrcPkgLink builds an <a> tag linking to the package documentation
-// for p.SrcPath.
-func (p *Page) SrcPkgLink() template.HTML {
-	dir := path.Dir(srcToPkg(p.SrcPath))
-	if dir == "pkg" {
-		return `<a href="/pkg">Index</a>`
-	}
-	dir = html.EscapeString(dir)
-	return template.HTML(fmt.Sprintf(`<a href="/%s">%s</a>`, dir, dir[len("pkg/"):]))
-}
-
-// SrcBreadcrumb converts each segment of p.SrcPath to a HTML <a>.
-// Each segment links to its corresponding src directories.
-func (p *Page) SrcBreadcrumb() template.HTML {
-	segments := strings.Split(p.SrcPath, "/")
-	var buf bytes.Buffer
-	var selectedSegment string
-	var selectedIndex int
-
-	if strings.HasSuffix(p.SrcPath, "/") {
-		// relpath is a directory ending with a "/".
-		// Selected segment is the segment before the last slash.
-		selectedIndex = len(segments) - 2
-		selectedSegment = segments[selectedIndex] + "/"
-	} else {
-		selectedIndex = len(segments) - 1
-		selectedSegment = segments[selectedIndex]
-	}
-
-	for i := range segments[:selectedIndex] {
-		buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
-			html.EscapeString(strings.Join(segments[:i+1], "/")),
-			html.EscapeString(segments[i]),
-		))
-	}
-
-	buf.WriteString(`<span class="text-muted">`)
-	buf.WriteString(html.EscapeString(selectedSegment))
-	buf.WriteString(`</span>`)
-	return template.HTML(buf.String())
-}
diff --git a/internal/web/template_test.go b/internal/web/template_test.go
deleted file mode 100644
index 0c8371a..0000000
--- a/internal/web/template_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package web
-
-import (
-	"testing"
-
-	"golang.org/x/website/internal/backport/html/template"
-)
-
-func TestSrcToPkg(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want string
-	}{
-		{"/src/fmt", "pkg/fmt"},
-		{"src/fmt", "pkg/fmt"},
-		{"/fmt", "pkg/fmt"},
-		{"fmt", "pkg/fmt"},
-		{"src/pkg/fmt", "pkg/fmt"},
-		{"/src/pkg/fmt", "pkg/fmt"},
-	} {
-		if got := srcToPkg(tc.path); got != tc.want {
-			t.Errorf("srcToPkg(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}
-
-func TestSrcBreadcrumbFunc(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want template.HTML
-	}{
-		{"src/", `<span class="text-muted">src/</span>`},
-		{"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`},
-		{"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`},
-	} {
-		if got := (&Page{SrcPath: tc.path}).SrcBreadcrumb(); got != tc.want {
-			t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}
-
-func TestSrcPkgLink(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want template.HTML
-	}{
-		{"src/", `<a href="/pkg">Index</a>`},
-		{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
-		{"pkg/", `<a href="/pkg">Index</a>`},
-		{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
-	} {
-		if got := (&Page{SrcPath: tc.path}).SrcPkgLink(); got != tc.want {
-			t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}