internal/godoc: delete Corpus

There's nothing left in Corpus but the file system and the API database.
Hoist them into Presentation and delete the Corpus itself.

Change-Id: I2cb61b77122b2f7216b0b2a96fd6b48e29eae189
Reviewed-on: https://go-review.googlesource.com/c/website/+/317651
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/golangorg/godoc_test.go b/cmd/golangorg/godoc_test.go
index 0900aba..a9d5b8b 100644
--- a/cmd/golangorg/godoc_test.go
+++ b/cmd/golangorg/godoc_test.go
@@ -176,8 +176,11 @@
 		releaseTag  string // optional release tag that must be in go/build.ReleaseTags
 	}{
 		{
-			path:     "/",
-			contains: []string{"Go is an open source programming language"},
+			path: "/",
+			contains: []string{
+				"Go is an open source programming language",
+				"Binary distributions available for",
+			},
 		},
 		{
 			path:     "/conduct",
diff --git a/cmd/golangorg/main.go b/cmd/golangorg/main.go
index 4f9e280..be8205c 100644
--- a/cmd/golangorg/main.go
+++ b/cmd/golangorg/main.go
@@ -78,12 +78,7 @@
 	}
 	fsys = unionFS{content, os.DirFS(*goroot)}
 
-	corpus := godoc.NewCorpus(fsys)
-	// Initialize the version info before readTemplates, which saves
-	// the map value in a method value.
-	corpus.InitVersionInfo()
-
-	pres = godoc.NewPresentation(corpus)
+	pres = godoc.NewPresentation(fsys)
 	pres.GoogleCN = googleCN
 
 	readTemplates(pres)
diff --git a/cmd/golangorg/release_test.go b/cmd/golangorg/release_test.go
index 98c54c5..9dbbaf5 100644
--- a/cmd/golangorg/release_test.go
+++ b/cmd/golangorg/release_test.go
@@ -27,7 +27,7 @@
 	origFS, origPres := fsys, pres
 	defer func() { fsys, pres = origFS, origPres }()
 	fsys = website.Content
-	pres = godoc.NewPresentation(godoc.NewCorpus(fsys))
+	pres = godoc.NewPresentation(fsys)
 	readTemplates(pres)
 	mux := registerHandlers(pres)
 
diff --git a/internal/api/api.go b/internal/api/api.go
index 59e6740..a9669f2 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -12,8 +12,8 @@
 
 import (
 	"bufio"
-	"go/build"
-	"os"
+	"io/fs"
+	"path"
 	"path/filepath"
 	"sort"
 	"strconv"
@@ -64,15 +64,10 @@
 	return ""
 }
 
-func Load() (DB, error) {
-	var apiGlob string
-	if os.Getenv("GOROOT") == "" {
-		apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
-	} else {
-		apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
-	}
-
-	files, err := filepath.Glob(apiGlob)
+// Load loads a database from fsys's api/go*.txt files.
+// Typically, fsys should be the root of a Go repository (a $GOROOT).
+func Load(fsys fs.FS) (DB, error) {
+	files, err := fs.Glob(fsys, "api/go*.txt")
 	if err != nil {
 		return nil, err
 	}
@@ -87,7 +82,7 @@
 	// when the symbol was added. See golang.org/issue/44081.
 	//
 	ver := func(name string) int {
-		base := filepath.Base(name)
+		base := path.Base(name)
 		ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.")
 		if ver == "go1" {
 			return 0
@@ -98,7 +93,7 @@
 	sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) })
 	vp := new(parser)
 	for _, f := range files {
-		if err := vp.parseFile(f); err != nil {
+		if err := vp.parseFile(fsys, f); err != nil {
 			return nil, err
 		}
 	}
@@ -116,8 +111,8 @@
 // vp.res to VERSION, overwriting any previous value.
 // As a special case, if goVERSION is "go1", it deletes
 // from the map instead.
-func (vp *parser) parseFile(name string) error {
-	f, err := os.Open(name)
+func (vp *parser) parseFile(fsys fs.FS, name string) error {
+	f, err := fsys.Open(name)
 	if err != nil {
 		return err
 	}
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
index bfddc5d..64c8800 100644
--- a/internal/api/api_test.go
+++ b/internal/api/api_test.go
@@ -9,6 +9,8 @@
 
 import (
 	"go/build"
+	"os"
+	"runtime"
 	"testing"
 )
 
@@ -91,7 +93,7 @@
 }
 
 func TestAPIVersion(t *testing.T) {
-	av, err := Load()
+	av, err := Load(os.DirFS(runtime.GOROOT()))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/godoc/astfuncs.go b/internal/godoc/astfuncs.go
index 22e1bdc..f6cafd4 100644
--- a/internal/godoc/astfuncs.go
+++ b/internal/godoc/astfuncs.go
@@ -67,7 +67,6 @@
 	var pkgName, structName string
 	var apiInfo api.PkgDB
 	if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
-		p.Corpus != nil &&
 		gd.Tok == token.TYPE && len(gd.Specs) != 0 {
 		pkgName = pageInfo.PDoc.ImportPath
 		if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
@@ -75,7 +74,7 @@
 				structName = ts.Name.Name
 			}
 		}
-		apiInfo = p.Corpus.pkgAPIInfo[pkgName]
+		apiInfo = p.api[pkgName]
 	}
 
 	var out = w
diff --git a/internal/godoc/corpus.go b/internal/godoc/corpus.go
deleted file mode 100644
index 2bdb113..0000000
--- a/internal/godoc/corpus.go
+++ /dev/null
@@ -1,37 +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.
-
-//go:build go1.16
-// +build go1.16
-
-package godoc
-
-import (
-	"io/fs"
-
-	"golang.org/x/website/internal/api"
-)
-
-// A Corpus holds all the state related to serving and indexing a
-// collection of Go code.
-//
-// Construct a new Corpus with NewCorpus, then modify options,
-// then call its Init method.
-type Corpus struct {
-	fs fs.FS
-
-	// pkgAPIInfo contains the information about which package API
-	// features were added in which version of Go.
-	pkgAPIInfo api.DB
-}
-
-// NewCorpus returns a new Corpus from a filesystem.
-// The returned corpus has all indexing enabled and MaxResults set to 1000.
-// Change or set any options on Corpus before calling the Corpus.Init method.
-func NewCorpus(fsys fs.FS) *Corpus {
-	c := &Corpus{
-		fs: fsys,
-	}
-	return c
-}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index 5b8ca55..ff0a202 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -38,8 +38,8 @@
 }
 
 func (p *Presentation) initFuncMap() {
-	if p.Corpus == nil {
-		panic("nil Presentation.Corpus")
+	if p.fs == nil {
+		panic("nil Presentation.fs")
 	}
 	p.templateFuncs = template.FuncMap{
 		"code":     p.code,
@@ -48,7 +48,7 @@
 	p.funcMap = template.FuncMap{
 		// various helpers
 		"filename": filenameFunc,
-		"since":    p.Corpus.pkgAPIInfo.Func,
+		"since":    p.api.Func,
 
 		// formatting of AST nodes
 		"node":         p.nodeFunc,
diff --git a/internal/godoc/meta.go b/internal/godoc/meta.go
index b6b7791..0c5809e 100644
--- a/internal/godoc/meta.go
+++ b/internal/godoc/meta.go
@@ -12,6 +12,7 @@
 	"encoding/json"
 	"io/fs"
 	"log"
+	"path"
 	"strings"
 )
 
@@ -21,28 +22,28 @@
 	jsonEnd   = []byte("}-->")
 )
 
-type Metadata struct {
-	// Copied from document metadata
+type file struct {
+	// Copied from document metadata directives
 	Title    string
 	Subtitle string
 	Template bool
 
 	Path     string // URL path
 	FilePath string // filesystem path relative to goroot
+	Body     []byte // content after metadata
 }
 
-type MetaJSON struct {
+type metaJSON struct {
 	Title    string
 	Subtitle string
 	Template bool
 	Redirect string // if set, redirect to other URL
 }
 
-// extractMetadata extracts the MetaJSON from a byte slice.
-// It returns the Metadata value and the remaining data.
-// If no metadata is present the original byte slice is returned.
-//
-func extractMetadata(b []byte) (meta MetaJSON, tail []byte, err error) {
+// extractMetadata extracts the metaJSON from a byte slice.
+// It returns the metadata and the remaining text.
+// If no metadata is present, it returns an empty metaJSON and the original text.
+func extractMetadata(b []byte) (meta metaJSON, tail []byte, err error) {
 	tail = b
 	if !bytes.HasPrefix(b, jsonStart) {
 		return
@@ -59,66 +60,77 @@
 	return
 }
 
-// MetadataFor returns the *Metadata for a given absolute path
-// or nil if none exists.
-func (c *Corpus) MetadataFor(path string) *Metadata {
-	// Strip any .html or .md; it all names the same page.
+var join = path.Join
+
+// open returns the file for a given absolute path or nil if none exists.
+func open(fsys fs.FS, path string) *file {
+	// Strip trailing .html or .md or /; it all names the same page.
 	if strings.HasSuffix(path, ".html") {
 		path = strings.TrimSuffix(path, ".html")
 	} else if strings.HasSuffix(path, ".md") {
 		path = strings.TrimSuffix(path, ".md")
+	} else if path != "/" && strings.HasSuffix(path, "/") {
+		path = strings.TrimSuffix(path, "/")
 	}
 
-	file := path + ".html"
-	b, err := fs.ReadFile(c.fs, toFS(file))
-	if err != nil {
-		file = path + ".md"
-		b, err = fs.ReadFile(c.fs, toFS(file))
-	}
-	if err != nil {
-		// Special case for memory model and spec, which live
-		// in the main Go repo's doc directory and therefore have not
-		// been renamed to their serving paths.
-		// We wait until the ReadFiles above have failed so that the
-		// code works if these are ever moved to /ref/spec and /ref/mem.
-		switch path {
-		case "/ref/spec":
-			if m := c.MetadataFor("/doc/go_spec"); m != nil {
-				return m
-			}
-		case "/ref/mem":
-			if m := c.MetadataFor("/doc/go_mem"); m != nil {
-				return m
-			}
+	files := []string{path + ".html", path + ".md", join(path, "index.html"), join(path, "index.md")}
+	var filePath string
+	var b []byte
+	var err error
+	for _, filePath = range files {
+		b, err = fs.ReadFile(fsys, toFS(filePath))
+		if err == nil {
+			break
 		}
-		return nil
 	}
 
-	js, _, err := extractMetadata(b)
+	// Special case for memory model and spec, which live
+	// in the main Go repo's doc directory and therefore have not
+	// been renamed to their serving paths.
+	// We wait until the ReadFiles above have failed so that the
+	// code works if these are ever moved to /ref/spec and /ref/mem.
+	if err != nil && path == "/ref/spec" {
+		return open(fsys, "/doc/go_spec")
+	}
+	if err != nil && path == "/ref/mem" {
+		return open(fsys, "/doc/go_mem")
+	}
+
 	if err != nil {
-		log.Printf("MetadataFor %s: %v", path, err)
 		return nil
 	}
 
-	meta := &Metadata{
-		Title:    js.Title,
-		Subtitle: js.Subtitle,
-		Template: js.Template,
-		Path:     path,
-		FilePath: file,
-	}
-	if js.Redirect != "" {
-		// Allow (placeholder) documents to declare a redirect.
-		meta.Path = js.Redirect
-	}
-
 	// Special case for memory model and spec, continued.
 	switch path {
 	case "/doc/go_spec":
-		meta.Path = "/ref/spec"
+		path = "/ref/spec"
 	case "/doc/go_mem":
-		meta.Path = "/ref/mem"
+		path = "/ref/mem"
 	}
 
-	return meta
+	// If we read an index.md or index.html, the canonical path is without the index.md/index.html suffix.
+	if strings.HasSuffix(filePath, "/index.md") || strings.HasSuffix(filePath, "/index.html") {
+		path = filePath[:strings.LastIndex(filePath, "/")+1]
+	}
+
+	js, body, err := extractMetadata(b)
+	if err != nil {
+		log.Printf("extractMetadata %s: %v", path, err)
+		return nil
+	}
+
+	f := &file{
+		Title:    js.Title,
+		Subtitle: js.Subtitle,
+		Template: js.Template,
+		Path:     path,
+		FilePath: filePath,
+		Body:     body,
+	}
+	if js.Redirect != "" {
+		// Allow (placeholder) documents to declare a redirect.
+		f.Path = js.Redirect
+	}
+
+	return f
 }
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
index 4aaa41b..5ce3c22 100644
--- a/internal/godoc/pres.go
+++ b/internal/godoc/pres.go
@@ -8,16 +8,20 @@
 package godoc
 
 import (
+	"io/fs"
+	"log"
 	"net/http"
 	"sync"
 	"text/template"
 
+	"golang.org/x/website/internal/api"
 	"golang.org/x/website/internal/pkgdoc"
 )
 
-// Presentation generates output from a corpus.
+// Presentation generates output from a file system.
 type Presentation struct {
-	Corpus *Corpus
+	fs  fs.FS
+	api api.DB
 
 	mux        *http.ServeMux
 	fileServer http.Handler
@@ -42,19 +46,21 @@
 	templateFuncs   template.FuncMap
 }
 
-// NewPresentation returns a new Presentation from a corpus.
-func NewPresentation(c *Corpus) *Presentation {
-	if c == nil {
-		panic("nil Corpus")
+// NewPresentation returns a new Presentation from a file system.
+func NewPresentation(fsys fs.FS) *Presentation {
+	apiDB, err := api.Load(fsys)
+	if err != nil {
+		log.Fatalf("NewPresentation loading api: %v", err)
 	}
 	p := &Presentation{
-		Corpus:     c,
+		fs:         fsys,
+		api:        apiDB,
 		mux:        http.NewServeMux(),
-		fileServer: http.FileServer(http.FS(c.fs)),
+		fileServer: http.FileServer(http.FS(fsys)),
 	}
 	docs := &docServer{
 		p: p,
-		d: pkgdoc.NewDocs(c.fs),
+		d: pkgdoc.NewDocs(fsys),
 	}
 	p.mux.Handle("/cmd/", docs)
 	p.mux.Handle("/pkg/", docs)
diff --git a/internal/godoc/server.go b/internal/godoc/server.go
index cdcd59c..4d3a740 100644
--- a/internal/godoc/server.go
+++ b/internal/godoc/server.go
@@ -203,8 +203,8 @@
 	return nil
 }
 
-func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
-	src, err := fs.ReadFile(p.Corpus.fs, toFS(abspath))
+func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
+	src, err := fs.ReadFile(p.fs, toFS(abspath))
 	if err != nil {
 		log.Printf("ReadFile: %s", err)
 		p.ServeError(w, r, relpath, err)
@@ -230,6 +230,10 @@
 
 	fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
 
+	title := "Text file"
+	if strings.HasSuffix(relpath, ".go") {
+		title = "Source file"
+	}
 	p.ServePage(w, Page{
 		Title:    title,
 		SrcPath:  relpath,
@@ -244,7 +248,7 @@
 		return
 	}
 
-	list, err := fs.ReadDir(p.Corpus.fs, toFS(abspath))
+	list, err := fs.ReadDir(p.fs, toFS(abspath))
 	if err != nil {
 		p.ServeError(w, r, relpath, err)
 		return
@@ -267,15 +271,9 @@
 	})
 }
 
-func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
-	// get HTML body contents
-	src, err := fs.ReadFile(p.Corpus.fs, toFS(abspath))
-	if err != nil {
-		log.Printf("ReadFile: %s", err)
-		p.ServeError(w, r, relpath, err)
-		return
-	}
-	isMarkdown := strings.HasSuffix(abspath, ".md")
+func (p *Presentation) serveHTML(w http.ResponseWriter, r *http.Request, f *file) {
+	src := f.Body
+	isMarkdown := strings.HasSuffix(f.FilePath, ".md")
 
 	// if it begins with "<!DOCTYPE " assume it is standalone
 	// html that doesn't need the template wrapping.
@@ -284,30 +282,24 @@
 		return
 	}
 
-	// if it begins with a JSON blob, read in the metadata.
-	meta, src, err := extractMetadata(src)
-	if err != nil {
-		log.Printf("decoding metadata %s: %v", relpath, err)
-	}
-
 	page := Page{
-		Title:    meta.Title,
-		Subtitle: meta.Subtitle,
+		Title:    f.Title,
+		Subtitle: f.Subtitle,
 		GoogleCN: p.googleCN(r),
 	}
 
 	// evaluate as template if indicated
-	if meta.Template {
+	if f.Template {
 		tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
 		if err != nil {
-			log.Printf("parsing template %s: %v", relpath, err)
-			p.ServeError(w, r, relpath, err)
+			log.Printf("parsing template %s: %v", f.Path, err)
+			p.ServeError(w, r, f.Path, err)
 			return
 		}
 		var buf bytes.Buffer
 		if err := tmpl.Execute(&buf, page); err != nil {
-			log.Printf("executing template %s: %v", relpath, err)
-			p.ServeError(w, r, relpath, err)
+			log.Printf("executing template %s: %v", f.Path, err)
+			p.ServeError(w, r, f.Path, err)
 			return
 		}
 		src = buf.Bytes()
@@ -318,15 +310,15 @@
 	if isMarkdown {
 		html, err := renderMarkdown(src)
 		if err != nil {
-			log.Printf("executing markdown %s: %v", relpath, err)
-			p.ServeError(w, r, relpath, err)
+			log.Printf("executing markdown %s: %v", f.Path, err)
+			p.ServeError(w, r, f.Path, err)
 			return
 		}
 		src = html
 	}
 
 	// if it's the language spec, add tags to EBNF productions
-	if strings.HasSuffix(abspath, "go_spec.html") {
+	if strings.HasSuffix(f.FilePath, "go_spec.html") {
 		var buf bytes.Buffer
 		spec.Linkify(&buf, src)
 		src = buf.Bytes()
@@ -350,36 +342,26 @@
 
 	// Check to see if we need to redirect or serve another file.
 	abspath := r.URL.Path
-	if m := p.Corpus.MetadataFor(abspath); m != nil {
-		if m.Path != abspath {
+	if f := open(p.fs, abspath); f != nil {
+		if f.Path != abspath {
 			// Redirect to canonical path.
-			http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
+			http.Redirect(w, r, f.Path, http.StatusMovedPermanently)
 			return
 		}
 		// Serve from the actual filesystem path.
-		p.ServeHTMLDoc(w, r, m.FilePath, m.Path)
+		p.serveHTML(w, r, f)
 		return
 	}
 
 	relpath := abspath[1:] // strip leading slash
 
-	switch path.Ext(relpath) {
-	case ".html":
-		p.ServeHTMLDoc(w, r, abspath, relpath)
-		return
-
-	case ".go":
-		p.serveTextFile(w, r, abspath, relpath, "Source file")
-		return
-	}
-
-	dir, err := fs.Stat(p.Corpus.fs, toFS(abspath))
+	dir, err := fs.Stat(p.fs, toFS(abspath))
 	if err != nil {
 		// Check for spurious trailing slash.
 		if strings.HasSuffix(abspath, "/") {
 			trimmed := abspath[:len(abspath)-1]
-			if _, err := fs.Stat(p.Corpus.fs, toFS(trimmed)); err == nil ||
-				p.Corpus.MetadataFor(trimmed) != nil {
+			if _, err := fs.Stat(p.fs, toFS(trimmed)); err == nil ||
+				open(p.fs, trimmed) != nil {
 				http.Redirect(w, r, trimmed, http.StatusMovedPermanently)
 				return
 			}
@@ -393,20 +375,15 @@
 		if redirect(w, r) {
 			return
 		}
-		index := path.Join(fsPath, "index.html")
-		if isTextFile(p.Corpus.fs, index) || isTextFile(p.Corpus.fs, path.Join(fsPath, "index.md")) {
-			p.ServeHTMLDoc(w, r, index, index)
-			return
-		}
 		p.serveDirectory(w, r, abspath, relpath)
 		return
 	}
 
-	if isTextFile(p.Corpus.fs, fsPath) {
+	if isTextFile(p.fs, fsPath) {
 		if redirectFile(w, r) {
 			return
 		}
-		p.serveTextFile(w, r, abspath, relpath, "Text file")
+		p.serveTextFile(w, r, abspath, relpath)
 		return
 	}
 
diff --git a/internal/godoc/server_test.go b/internal/godoc/server_test.go
index 57d61e9..152e934 100644
--- a/internal/godoc/server_test.go
+++ b/internal/godoc/server_test.go
@@ -29,11 +29,11 @@
 }
 
 func TestRedirectAndMetadata(t *testing.T) {
-	c := NewCorpus(fstest.MapFS{
+	fsys := fstest.MapFS{
 		"doc/x/index.html": {Data: []byte("Hello, x.")},
-	})
+	}
 	p := &Presentation{
-		Corpus:    c,
+		fs:        fsys,
 		GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)),
 	}
 
@@ -54,10 +54,10 @@
 
 func TestMarkdown(t *testing.T) {
 	p := &Presentation{
-		Corpus: NewCorpus(fstest.MapFS{
+		fs: fstest.MapFS{
 			"doc/test.md":  {Data: []byte("**bold**")},
 			"doc/test2.md": {Data: []byte(`{{"*template*"}}`)},
-		}),
+		},
 		GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)),
 	}
 
diff --git a/internal/godoc/template.go b/internal/godoc/template.go
index 5baa140..138b71f 100644
--- a/internal/godoc/template.go
+++ b/internal/godoc/template.go
@@ -50,8 +50,8 @@
 
 // contents reads and returns the content of the named file
 // (from the virtual file system, so for example /doc refers to $GOROOT/doc).
-func (c *Corpus) contents(name string) string {
-	file, err := fs.ReadFile(c.fs, toFS(name))
+func (p *Presentation) contents(name string) string {
+	file, err := fs.ReadFile(p.fs, toFS(name))
 	if err != nil {
 		log.Panic(err)
 	}
@@ -81,7 +81,7 @@
 		}
 	}()
 
-	text := p.Corpus.contents(file)
+	text := p.contents(file)
 	var command string
 	switch len(arg) {
 	case 0:
@@ -89,10 +89,10 @@
 		command = fmt.Sprintf("code %q", file)
 	case 1:
 		command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
-		text = p.Corpus.oneLine(file, text, arg[0])
+		text = p.oneLine(file, text, arg[0])
 	case 2:
 		command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
-		text = p.Corpus.multipleLines(file, text, arg[0], arg[1])
+		text = p.multipleLines(file, text, arg[0], arg[1])
 	default:
 		return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
 	}
@@ -124,8 +124,8 @@
 }
 
 // oneLine returns the single line generated by a two-argument code invocation.
-func (c *Corpus) oneLine(file, text string, arg interface{}) string {
-	lines := strings.SplitAfter(c.contents(file), "\n")
+func (p *Presentation) oneLine(file, text string, arg interface{}) string {
+	lines := strings.SplitAfter(p.contents(file), "\n")
 	line, pattern, isInt := parseArg(arg, file, len(lines))
 	if isInt {
 		return lines[line-1]
@@ -134,8 +134,8 @@
 }
 
 // multipleLines returns the text generated by a three-argument code invocation.
-func (c *Corpus) multipleLines(file, text string, arg1, arg2 interface{}) string {
-	lines := strings.SplitAfter(c.contents(file), "\n")
+func (p *Presentation) multipleLines(file, text string, arg1, arg2 interface{}) string {
+	lines := strings.SplitAfter(p.contents(file), "\n")
 	line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
 	line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
 	if !isInt1 {
diff --git a/internal/godoc/versions.go b/internal/godoc/versions.go
deleted file mode 100644
index 9f18fb4..0000000
--- a/internal/godoc/versions.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2018 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.
-
-//go:build go1.16
-// +build go1.16
-
-// This file caches information about which standard library types, methods,
-// and functions appeared in what version of Go
-
-package godoc
-
-import (
-	"log"
-
-	"golang.org/x/website/internal/api"
-)
-
-// InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
-// which API features were added in which Go releases.
-func (c *Corpus) InitVersionInfo() {
-	var err error
-	c.pkgAPIInfo, err = api.Load()
-	if err != nil {
-		// TODO: consider making this fatal, after the Go 1.11 cycle.
-		log.Printf("godoc: error parsing API version files: %v", err)
-	}
-}