internal/godoc: take over HTML template loading

Move template loading from cmd/golangorg to internal/godoc,
which needs them.

Change-Id: I97cfebaa7e4bd3aa3bcc88e05a05393c1e8bbd1f
Reviewed-on: https://go-review.googlesource.com/c/website/+/317652
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/golangorg/handlers.go b/cmd/golangorg/handlers.go
index c000a97..5dc226c 100644
--- a/cmd/golangorg/handlers.go
+++ b/cmd/golangorg/handlers.go
@@ -15,7 +15,6 @@
 	"net/http"
 	pathpkg "path"
 	"strings"
-	"text/template"
 
 	"golang.org/x/website/internal/env"
 	"golang.org/x/website/internal/godoc"
@@ -98,35 +97,14 @@
 	return mux
 }
 
-func readTemplate(name string) *template.Template {
-	if pres == nil {
-		panic("no global Presentation set yet")
-	}
-	path := "lib/godoc/" + name
-
-	// use underlying file system fs to read the template file
-	// (cannot use template ParseFile functions directly)
-	data, err := fs.ReadFile(fsys, toFS(path))
-	if err != nil {
-		log.Fatal("readTemplate: ", err)
-	}
-	// be explicit with errors (for app engine use)
-	t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data))
-	if err != nil {
-		log.Fatal("readTemplate: ", err)
-	}
-	return t
-}
-
 func readTemplates(p *godoc.Presentation) {
-	codewalkHTML = readTemplate("codewalk.html")
-	codewalkdirHTML = readTemplate("codewalkdir.html")
-	p.DirlistHTML = readTemplate("dirlist.html")
-	p.ErrorHTML = readTemplate("error.html")
-	p.ExampleHTML = readTemplate("example.html")
-	p.GodocHTML = readTemplate("godoc.html")
-	p.PackageHTML = readTemplate("package.html")
-	p.PackageRootHTML = readTemplate("packageroot.html")
+	var err error
+	if codewalkHTML, err = p.ReadTemplate("codewalk.html"); err != nil {
+		log.Fatal(err)
+	}
+	if codewalkdirHTML, err = p.ReadTemplate("codewalkdir.html"); err != nil {
+		log.Fatal(err)
+	}
 }
 
 type fmtResponse struct {
diff --git a/cmd/golangorg/main.go b/cmd/golangorg/main.go
index be8205c..283f4e0 100644
--- a/cmd/golangorg/main.go
+++ b/cmd/golangorg/main.go
@@ -78,7 +78,11 @@
 	}
 	fsys = unionFS{content, os.DirFS(*goroot)}
 
-	pres = godoc.NewPresentation(fsys)
+	var err error
+	pres, err = godoc.NewPresentation(fsys)
+	if err != nil {
+		log.Fatal(err)
+	}
 	pres.GoogleCN = googleCN
 
 	readTemplates(pres)
diff --git a/cmd/golangorg/release_test.go b/cmd/golangorg/release_test.go
index 9dbbaf5..4b19884 100644
--- a/cmd/golangorg/release_test.go
+++ b/cmd/golangorg/release_test.go
@@ -27,7 +27,11 @@
 	origFS, origPres := fsys, pres
 	defer func() { fsys, pres = origFS, origPres }()
 	fsys = website.Content
-	pres = godoc.NewPresentation(fsys)
+	var err error
+	pres, err = godoc.NewPresentation(fsys)
+	if err != nil {
+		t.Fatal(err)
+	}
 	readTemplates(pres)
 	mux := registerHandlers(pres)
 
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index ff0a202..33bd631 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -22,30 +22,18 @@
 	"golang.org/x/website/internal/pkgdoc"
 )
 
-// FuncMap defines template functions used in godoc templates.
-//
-// Convention: template function names ending in "_html" or "_url" produce
-//             HTML- or URL-escaped strings; all other function results may
-//             require explicit escaping in the template.
-func (p *Presentation) FuncMap() template.FuncMap {
-	p.initFuncMapOnce.Do(p.initFuncMap)
-	return p.funcMap
-}
-
-func (p *Presentation) TemplateFuncs() template.FuncMap {
-	p.initFuncMapOnce.Do(p.initFuncMap)
-	return p.templateFuncs
-}
-
 func (p *Presentation) initFuncMap() {
-	if p.fs == nil {
-		panic("nil Presentation.fs")
-	}
-	p.templateFuncs = template.FuncMap{
+	// Template function maps.
+	// Convention: template function names ending in "_html" or "_url" produce
+	//             HTML- or URL-escaped strings; all other function results may
+	//             require explicit escaping in the template.
+
+	p.DocFuncs = template.FuncMap{
 		"code":     p.code,
 		"releases": func() []*history.Major { return history.Majors },
 	}
-	p.funcMap = template.FuncMap{
+
+	p.SiteFuncs = template.FuncMap{
 		// various helpers
 		"filename": filenameFunc,
 		"since":    p.api.Func,
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
index 5ce3c22..a860023 100644
--- a/internal/godoc/pres.go
+++ b/internal/godoc/pres.go
@@ -9,9 +9,7 @@
 
 import (
 	"io/fs"
-	"log"
 	"net/http"
-	"sync"
 	"text/template"
 
 	"golang.org/x/website/internal/api"
@@ -41,16 +39,15 @@
 	// tracking ID to each page.
 	GoogleAnalytics string
 
-	initFuncMapOnce sync.Once
-	funcMap         template.FuncMap
-	templateFuncs   template.FuncMap
+	DocFuncs  template.FuncMap
+	SiteFuncs template.FuncMap
 }
 
 // NewPresentation returns a new Presentation from a file system.
-func NewPresentation(fsys fs.FS) *Presentation {
+func NewPresentation(fsys fs.FS) (*Presentation, error) {
 	apiDB, err := api.Load(fsys)
 	if err != nil {
-		log.Fatalf("NewPresentation loading api: %v", err)
+		return nil, err
 	}
 	p := &Presentation{
 		fs:         fsys,
@@ -65,7 +62,28 @@
 	p.mux.Handle("/cmd/", docs)
 	p.mux.Handle("/pkg/", docs)
 	p.mux.HandleFunc("/", p.ServeFile)
-	return p
+	p.initFuncMap()
+
+	if p.DirlistHTML, err = p.ReadTemplate("dirlist.html"); err != nil {
+		return nil, err
+	}
+	if p.ErrorHTML, err = p.ReadTemplate("error.html"); err != nil {
+		return nil, err
+	}
+	if p.ExampleHTML, err = p.ReadTemplate("example.html"); err != nil {
+		return nil, err
+	}
+	if p.GodocHTML, err = p.ReadTemplate("godoc.html"); err != nil {
+		return nil, err
+	}
+	if p.PackageHTML, err = p.ReadTemplate("package.html"); err != nil {
+		return nil, err
+	}
+	if p.PackageRootHTML, err = p.ReadTemplate("packageroot.html"); err != nil {
+		return nil, err
+	}
+
+	return p, nil
 }
 
 func (p *Presentation) FileServer() http.Handler {
@@ -79,3 +97,15 @@
 func (p *Presentation) googleCN(r *http.Request) bool {
 	return p.GoogleCN != nil && p.GoogleCN(r)
 }
+
+func (p *Presentation) ReadTemplate(name string) (*template.Template, error) {
+	data, err := fs.ReadFile(p.fs, "lib/godoc/"+name)
+	if err != nil {
+		return nil, err
+	}
+	t, err := template.New(name).Funcs(p.SiteFuncs).Parse(string(data))
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
diff --git a/internal/godoc/server.go b/internal/godoc/server.go
index 4d3a740..7b3c814 100644
--- a/internal/godoc/server.go
+++ b/internal/godoc/server.go
@@ -290,7 +290,7 @@
 
 	// evaluate as template if indicated
 	if f.Template {
-		tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
+		tmpl, err := template.New("main").Funcs(p.DocFuncs).Parse(string(src))
 		if err != nil {
 			log.Printf("parsing template %s: %v", f.Path, err)
 			p.ServeError(w, r, f.Path, err)