internal/frontend: add breadcrumb.go

Logic related to rendering the breadcrumb is moved to breadcrumb.go.
Pure code in motion.

Change-Id: Icaa491d12090437e66816efc7fe0808df7f56acc
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/262000
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/frontend/breadcrumb.go b/internal/frontend/breadcrumb.go
new file mode 100644
index 0000000..9b8f1cf
--- /dev/null
+++ b/internal/frontend/breadcrumb.go
@@ -0,0 +1,82 @@
+// Copyright 2020 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 frontend
+
+import (
+	"path"
+
+	"golang.org/x/pkgsite/internal"
+	"golang.org/x/pkgsite/internal/stdlib"
+)
+
+// displayBreadcrumbs appends additional breadcrumb links for display
+// to those for the given unit.
+func displayBreadcrumb(um *internal.UnitMeta, requestedVersion string) breadcrumb {
+	bc := breadcrumbPath(um.Path, um.ModulePath, requestedVersion)
+	if um.ModulePath == stdlib.ModulePath && um.Path != stdlib.ModulePath {
+		bc.Links = append([]link{{Href: "/std", Body: "Standard library"}}, bc.Links...)
+	}
+	bc.Links = append([]link{{Href: "/", Body: "Discover Packages"}}, bc.Links...)
+	return bc
+}
+
+type breadcrumb struct {
+	Links    []link
+	Current  string
+	CopyData string
+}
+
+type link struct {
+	Href, Body string
+}
+
+// breadcrumbPath builds HTML that displays pkgPath as a sequence of links
+// to its parents.
+// pkgPath is a slash-separated path, and may be a package import path or a directory.
+// modPath is the package's module path. This will be a prefix of pkgPath, except
+// within the standard library.
+// version is the version for the module, or LatestVersion.
+//
+// See TestBreadcrumbPath for examples.
+func breadcrumbPath(pkgPath, modPath, requestedVersion string) breadcrumb {
+	if pkgPath == stdlib.ModulePath {
+		return breadcrumb{Current: "Standard library"}
+	}
+	// Obtain successive prefixes of pkgPath, stopping at modPath,
+	// or for the stdlib, at the end.
+	minLen := len(modPath) - 1
+	if modPath == stdlib.ModulePath {
+		minLen = 1
+	}
+	var dirs []string
+	for dir := pkgPath; len(dir) > minLen && len(path.Dir(dir)) < len(dir); dir = path.Dir(dir) {
+		dirs = append(dirs, dir)
+	}
+	// Construct the path elements of the result.
+	// They will be in reverse order of dirs.
+	// The first dir is the current page. If it is the only one, leave it
+	// as is. Otherwise, use its base. In neither case does it get a link.
+	d := dirs[0]
+	if len(dirs) > 1 {
+		d = path.Base(d)
+	}
+	b := breadcrumb{Current: d}
+	// Make all the other parts into links.
+	b.Links = make([]link, len(dirs)-1)
+	for i := 1; i < len(dirs); i++ {
+		href := "/" + dirs[i]
+		if requestedVersion != internal.LatestVersion {
+			href += "@" + linkVersion(requestedVersion, modPath)
+		}
+		el := dirs[i]
+		if i != len(dirs)-1 {
+			el = path.Base(el)
+		}
+		b.Links[len(b.Links)-i] = link{href, el}
+	}
+	// Add a "copy" button for the path.
+	b.CopyData = pkgPath
+	return b
+}
diff --git a/internal/frontend/breadcrumb_test.go b/internal/frontend/breadcrumb_test.go
new file mode 100644
index 0000000..9739d5a
--- /dev/null
+++ b/internal/frontend/breadcrumb_test.go
@@ -0,0 +1,103 @@
+// Copyright 2020 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 frontend
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/pkgsite/internal"
+)
+
+func TestBreadcrumbPath(t *testing.T) {
+	for _, test := range []struct {
+		pkgPath, modPath, version string
+		want                      breadcrumb
+	}{
+		{
+			"example.com/blob/s3blob", "example.com", internal.LatestVersion,
+			breadcrumb{
+				Current: "s3blob",
+				Links: []link{
+					{"/example.com", "example.com"},
+					{"/example.com/blob", "blob"},
+				},
+				CopyData: "example.com/blob/s3blob",
+			},
+		},
+		{
+			"example.com", "example.com", internal.LatestVersion,
+			breadcrumb{
+				Current:  "example.com",
+				Links:    []link{},
+				CopyData: "example.com",
+			},
+		},
+		{
+			"g/x/tools/go/a", "g/x/tools", internal.LatestVersion,
+			breadcrumb{
+				Current: "a",
+				Links: []link{
+					{"/g/x/tools", "g/x/tools"},
+					{"/g/x/tools/go", "go"},
+				},
+				CopyData: "g/x/tools/go/a",
+			},
+		},
+		{
+			"golang.org/x/tools", "golang.org/x/tools", internal.LatestVersion,
+			breadcrumb{
+				Current:  "golang.org/x/tools",
+				Links:    []link{},
+				CopyData: "golang.org/x/tools",
+			},
+		},
+		{
+			// Special case: stdlib package.
+			"encoding/json", "std", internal.LatestVersion,
+			breadcrumb{
+				Current:  "json",
+				Links:    []link{{"/encoding", "encoding"}},
+				CopyData: "encoding/json",
+			},
+		},
+		{
+			// Special case: stdlib package.
+			"encoding/json", "std", "go1.15",
+			breadcrumb{
+				Current:  "json",
+				Links:    []link{{"/encoding@go1.15", "encoding"}},
+				CopyData: "encoding/json",
+			},
+		},
+		{
+			// Special case: stdlib module.
+			"std", "std", internal.LatestVersion,
+			breadcrumb{
+				Current: "Standard library",
+				Links:   nil,
+			},
+		},
+		{
+			"example.com/blob/s3blob", "example.com", "v1",
+			breadcrumb{
+				Current: "s3blob",
+				Links: []link{
+					{"/example.com@v1", "example.com"},
+					{"/example.com/blob@v1", "blob"},
+				},
+				CopyData: "example.com/blob/s3blob",
+			},
+		},
+	} {
+		t.Run(fmt.Sprintf("%s-%s-%s", test.pkgPath, test.modPath, test.version), func(t *testing.T) {
+			got := breadcrumbPath(test.pkgPath, test.modPath, test.version)
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("mismatch (-want, +got):\n%s", diff)
+			}
+		})
+	}
+}
diff --git a/internal/frontend/header.go b/internal/frontend/header.go
index cdf3c14..b012186 100644
--- a/internal/frontend/header.go
+++ b/internal/frontend/header.go
@@ -143,65 +143,6 @@
 	return effectiveName(pkgPath, pkgName) + " command"
 }
 
-type breadcrumb struct {
-	Links    []link
-	Current  string
-	CopyData string
-}
-
-type link struct {
-	Href, Body string
-}
-
-// breadcrumbPath builds HTML that displays pkgPath as a sequence of links
-// to its parents.
-// pkgPath is a slash-separated path, and may be a package import path or a directory.
-// modPath is the package's module path. This will be a prefix of pkgPath, except
-// within the standard library.
-// version is the version for the module, or LatestVersion.
-//
-// See TestBreadcrumbPath for examples.
-func breadcrumbPath(pkgPath, modPath, requestedVersion string) breadcrumb {
-	if pkgPath == stdlib.ModulePath {
-		return breadcrumb{Current: "Standard library"}
-	}
-	// Obtain successive prefixes of pkgPath, stopping at modPath,
-	// or for the stdlib, at the end.
-	minLen := len(modPath) - 1
-	if modPath == stdlib.ModulePath {
-		minLen = 1
-	}
-	var dirs []string
-	for dir := pkgPath; len(dir) > minLen && len(path.Dir(dir)) < len(dir); dir = path.Dir(dir) {
-		dirs = append(dirs, dir)
-	}
-	// Construct the path elements of the result.
-	// They will be in reverse order of dirs.
-	// The first dir is the current page. If it is the only one, leave it
-	// as is. Otherwise, use its base. In neither case does it get a link.
-	d := dirs[0]
-	if len(dirs) > 1 {
-		d = path.Base(d)
-	}
-	b := breadcrumb{Current: d}
-	// Make all the other parts into links.
-	b.Links = make([]link, len(dirs)-1)
-	for i := 1; i < len(dirs); i++ {
-		href := "/" + dirs[i]
-		if requestedVersion != internal.LatestVersion {
-			href += "@" + linkVersion(requestedVersion, modPath)
-		}
-		el := dirs[i]
-		if i != len(dirs)-1 {
-			el = path.Base(el)
-		}
-		b.Links[len(b.Links)-i] = link{href, el}
-	}
-	// Add a "copy" button for the path.
-	b.CopyData = pkgPath
-	return b
-}
-
 // moduleHTMLTitle constructs the <title> contents, for tabs in the browser.
 func moduleHTMLTitle(modulePath string) string {
 	if modulePath == stdlib.ModulePath {
diff --git a/internal/frontend/header_test.go b/internal/frontend/header_test.go
index 7f38857..b0ea081 100644
--- a/internal/frontend/header_test.go
+++ b/internal/frontend/header_test.go
@@ -5,7 +5,6 @@
 package frontend
 
 import (
-	"fmt"
 	"testing"
 	"time"
 
@@ -161,96 +160,6 @@
 	}
 }
 
-func TestBreadcrumbPath(t *testing.T) {
-	for _, test := range []struct {
-		pkgPath, modPath, version string
-		want                      breadcrumb
-	}{
-		{
-			"example.com/blob/s3blob", "example.com", internal.LatestVersion,
-			breadcrumb{
-				Current: "s3blob",
-				Links: []link{
-					{"/example.com", "example.com"},
-					{"/example.com/blob", "blob"},
-				},
-				CopyData: "example.com/blob/s3blob",
-			},
-		},
-		{
-			"example.com", "example.com", internal.LatestVersion,
-			breadcrumb{
-				Current:  "example.com",
-				Links:    []link{},
-				CopyData: "example.com",
-			},
-		},
-		{
-			"g/x/tools/go/a", "g/x/tools", internal.LatestVersion,
-			breadcrumb{
-				Current: "a",
-				Links: []link{
-					{"/g/x/tools", "g/x/tools"},
-					{"/g/x/tools/go", "go"},
-				},
-				CopyData: "g/x/tools/go/a",
-			},
-		},
-		{
-			"golang.org/x/tools", "golang.org/x/tools", internal.LatestVersion,
-			breadcrumb{
-				Current:  "golang.org/x/tools",
-				Links:    []link{},
-				CopyData: "golang.org/x/tools",
-			},
-		},
-		{
-			// Special case: stdlib package.
-			"encoding/json", "std", internal.LatestVersion,
-			breadcrumb{
-				Current:  "json",
-				Links:    []link{{"/encoding", "encoding"}},
-				CopyData: "encoding/json",
-			},
-		},
-		{
-			// Special case: stdlib package.
-			"encoding/json", "std", "go1.15",
-			breadcrumb{
-				Current:  "json",
-				Links:    []link{{"/encoding@go1.15", "encoding"}},
-				CopyData: "encoding/json",
-			},
-		},
-		{
-			// Special case: stdlib module.
-			"std", "std", internal.LatestVersion,
-			breadcrumb{
-				Current: "Standard library",
-				Links:   nil,
-			},
-		},
-		{
-			"example.com/blob/s3blob", "example.com", "v1",
-			breadcrumb{
-				Current: "s3blob",
-				Links: []link{
-					{"/example.com@v1", "example.com"},
-					{"/example.com/blob@v1", "blob"},
-				},
-				CopyData: "example.com/blob/s3blob",
-			},
-		},
-	} {
-		t.Run(fmt.Sprintf("%s-%s-%s", test.pkgPath, test.modPath, test.version), func(t *testing.T) {
-			got := breadcrumbPath(test.pkgPath, test.modPath, test.version)
-			if diff := cmp.Diff(test.want, got); diff != "" {
-				t.Errorf("mismatch (-want, +got):\n%s", diff)
-			}
-		})
-	}
-}
-
 // packageMetaFromLegacyPackage returns a PackageMeta based on data from a
 // LegacyPackage.
 func packageMetaFromLegacyPackage(pkg *internal.LegacyPackage) *internal.PackageMeta {
diff --git a/internal/frontend/unit.go b/internal/frontend/unit.go
index 92cd396..34e832c 100644
--- a/internal/frontend/unit.go
+++ b/internal/frontend/unit.go
@@ -192,14 +192,3 @@
 	}
 	return pageTypes
 }
-
-// displayBreadcrumbs appends additional breadcrumb links for display
-// to those for the given unit.
-func displayBreadcrumb(um *internal.UnitMeta, requestedVersion string) breadcrumb {
-	bc := breadcrumbPath(um.Path, um.ModulePath, requestedVersion)
-	if um.ModulePath == stdlib.ModulePath && um.Path != stdlib.ModulePath {
-		bc.Links = append([]link{{Href: "/std", Body: "Standard library"}}, bc.Links...)
-	}
-	bc.Links = append([]link{{Href: "/", Body: "Discover Packages"}}, bc.Links...)
-	return bc
-}