.
+ Do not delete this
. */}}
+
+
+{{if .Template}}
+{{.Invoke .Template .Data}}
+{{else}}
+{{.Data}}
+{{end}}
+
+
+
+
+{{if .GoogleAnalytics}}
+
+{{end}}
diff --git a/cmd/golangorg/codewalk.go b/cmd/golangorg/codewalk.go
index b5713433..d8d22841 100644
--- a/cmd/golangorg/codewalk.go
+++ b/cmd/golangorg/codewalk.go
@@ -16,10 +16,10 @@
package main
import (
- "bytes"
"encoding/xml"
"errors"
"fmt"
+ "html/template"
"io"
"io/fs"
"log"
@@ -30,14 +30,11 @@ import (
"sort"
"strconv"
"strings"
- "text/template"
"unicode/utf8"
"golang.org/x/website/internal/godoc"
)
-var codewalkHTML, codewalkdirHTML *template.Template
-
// Handler for /doc/codewalk/ and below.
func codewalk(w http.ResponseWriter, r *http.Request) {
relpath := r.URL.Path[len("/doc/codewalk/"):]
@@ -58,7 +55,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
// If file exists, serve using standard file server.
if err == nil {
- pres.ServeFile(w, r)
+ pres.ServeHTTP(w, r)
return
}
@@ -69,7 +66,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
cw, err := loadCodewalk(abspath + ".xml")
if err != nil {
log.Print(err)
- pres.ServeError(w, r, relpath, err)
+ pres.ServeError(w, r, err)
return
}
@@ -78,10 +75,11 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
return
}
- pres.ServePage(w, godoc.Page{
+ pres.ServePage(w, r, godoc.Page{
Title: "Codewalk: " + cw.Title,
- Tabtitle: cw.Title,
- Body: applyTemplate(codewalkHTML, "codewalk", cw),
+ TabTitle: cw.Title,
+ Template: "codewalk.html",
+ Data: cw,
})
}
@@ -99,14 +97,6 @@ func redir(w http.ResponseWriter, r *http.Request) (redirected bool) {
return
}
-func applyTemplate(t *template.Template, name string, data interface{}) []byte {
- var buf bytes.Buffer
- if err := t.Execute(&buf, data); err != nil {
- log.Printf("%s.Execute: %s", name, err)
- }
- return buf.Bytes()
-}
-
// A Codewalk represents a single codewalk read from an XML file.
type Codewalk struct {
Title string `xml:"title,attr"`
@@ -131,6 +121,10 @@ type Codestep struct {
Data []byte
}
+func (c *Codestep) HTML() template.HTML {
+ return template.HTML(c.XML)
+}
+
// String method for printing in template.
// Formats file address nicely.
func (st *Codestep) String() string {
@@ -217,7 +211,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
dir, err := fs.ReadDir(fsys, toFS(abspath))
if err != nil {
log.Print(err)
- pres.ServeError(w, r, relpath, err)
+ pres.ServeError(w, r, err)
return
}
var v []interface{}
@@ -234,9 +228,10 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
}
}
- pres.ServePage(w, godoc.Page{
- Title: "Codewalks",
- Body: applyTemplate(codewalkdirHTML, "codewalkdir", v),
+ pres.ServePage(w, r, godoc.Page{
+ Title: "Codewalks",
+ Template: "codewalkdir.html",
+ Data: v,
})
}
@@ -251,7 +246,7 @@ func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
data, err := fs.ReadFile(fsys, toFS(abspath))
if err != nil {
log.Print(err)
- pres.ServeError(w, r, f, err)
+ pres.ServeError(w, r, err)
return
}
lo, _ := strconv.Atoi(r.FormValue("lo"))
diff --git a/cmd/golangorg/handlers.go b/cmd/golangorg/handlers.go
index 28a3b806..1ee843da 100644
--- a/cmd/golangorg/handlers.go
+++ b/cmd/golangorg/handlers.go
@@ -11,7 +11,6 @@ import (
"encoding/json"
"go/format"
"io/fs"
- "log"
"net/http"
pathpkg "path"
"strings"
@@ -93,16 +92,6 @@ func registerHandlers(pres *godoc.Presentation) *http.ServeMux {
return mux
}
-func readTemplates(p *godoc.Presentation) {
- 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 {
Body string
Error string
diff --git a/cmd/golangorg/local.go b/cmd/golangorg/local.go
index 37353b38..ba00aaa3 100644
--- a/cmd/golangorg/local.go
+++ b/cmd/golangorg/local.go
@@ -27,7 +27,7 @@ func earlySetup() {
os.Exit(2)
}
dir := filepath.Join(file, "../../../_content")
- if _, err := os.Stat(filepath.Join(dir, "lib/godoc/godoc.html")); err != nil {
+ if _, err := os.Stat(filepath.Join(dir, "lib/godoc/site.html")); err != nil {
log.Printf("warning: cannot find template dir; using embedded copy")
return
}
diff --git a/cmd/golangorg/main.go b/cmd/golangorg/main.go
index 283f4e0a..9c20031a 100644
--- a/cmd/golangorg/main.go
+++ b/cmd/golangorg/main.go
@@ -85,7 +85,6 @@ func main() {
}
pres.GoogleCN = googleCN
- readTemplates(pres)
mux := registerHandlers(pres)
lateSetup(mux)
diff --git a/cmd/golangorg/release_test.go b/cmd/golangorg/release_test.go
index 4b198849..5c6a5746 100644
--- a/cmd/golangorg/release_test.go
+++ b/cmd/golangorg/release_test.go
@@ -32,7 +32,6 @@ func TestReleaseHistory(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- readTemplates(pres)
mux := registerHandlers(pres)
req := httptest.NewRequest(http.MethodGet, "/doc/devel/release", nil)
diff --git a/internal/api/api.go b/internal/api/api.go
index a9669f20..7fcb23da 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -51,7 +51,7 @@ type PkgDB struct {
//
// The name is the symbol name ("Server") and the pkg is the package
// ("net/http").
-func (v DB) Func(kind, receiver, name, pkg string) string {
+func (v DB) Func(pkg, kind, receiver, name string) string {
pv := v[pkg]
switch kind {
case "func":
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
index 64c88006..ced9aa46 100644
--- a/internal/api/api_test.go
+++ b/internal/api/api_test.go
@@ -137,8 +137,8 @@ func TestAPIVersion(t *testing.T) {
if tc.want != "" && !hasTag("go"+tc.want) {
continue
}
- if got := av.Func(tc.kind, tc.receiver, tc.name, tc.pkg); got != tc.want {
- t.Errorf(`sinceFunc("%s", "%s", "%s", "%s") = "%s"; want "%s"`, tc.kind, tc.receiver, tc.name, tc.pkg, got, tc.want)
+ if got := av.Func(tc.pkg, tc.kind, tc.receiver, tc.name); got != tc.want {
+ t.Errorf(`sinceFunc(%q, %q, %q, %q) = %q; want %q`, tc.pkg, tc.kind, tc.receiver, tc.name, got, tc.want)
}
}
}
diff --git a/internal/godoc/astfuncs.go b/internal/godoc/astfuncs.go
index f6cafd44..e21b52a3 100644
--- a/internal/godoc/astfuncs.go
+++ b/internal/godoc/astfuncs.go
@@ -14,6 +14,7 @@ import (
"go/doc"
"go/printer"
"go/token"
+ "html/template"
"io"
"log"
"unicode"
@@ -25,26 +26,36 @@ import (
var slashSlash = []byte("//")
-func (p *Presentation) nodeFunc(info *pkgdoc.Page, node interface{}) string {
- var buf bytes.Buffer
- p.writeNode(&buf, info, info.FSet, node)
- return buf.String()
+// Node formats the given AST node as HTML.
+// Identifiers in the rendered node
+// are turned into links to their documentation.
+func (p *Page) Node(node interface{}) template.HTML {
+ info := p.Data.(*pkgdoc.Page)
+ var buf1 bytes.Buffer
+ p.pres.writeNode(&buf1, info, info.FSet, node)
+
+ var buf2 bytes.Buffer
+ n, _ := node.(ast.Node)
+ buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
+ AST: n,
+ GoComments: true,
+ }))
+ return template.HTML(buf2.String())
}
-func (p *Presentation) node_htmlFunc(info *pkgdoc.Page, node interface{}, linkify bool) string {
+// NodeTOC formats the given AST node as HTML
+// for inclusion in the table of contents.
+func (p *Page) NodeTOC(node interface{}) template.HTML {
+ info := p.Data.(*pkgdoc.Page)
var buf1 bytes.Buffer
- p.writeNode(&buf1, info, info.FSet, node)
+ p.pres.writeNode(&buf1, info, info.FSet, node)
var buf2 bytes.Buffer
- var n ast.Node
- if linkify {
- n, _ = node.(ast.Node)
- }
buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
- AST: n,
GoComments: true,
}))
- return buf2.String()
+
+ return sanitize(template.HTML(buf2.String()))
}
const TabWidth = 4
@@ -140,18 +151,19 @@ func firstIdent(x []byte) string {
return string(x[:i])
}
-func comment_htmlFunc(comment string) string {
+// Comment formats the given documentation comment as HTML.
+func (p *Page) Comment(comment string) template.HTML {
var buf bytes.Buffer
// TODO(gri) Provide list of words (e.g. function parameters)
// to be emphasized by ToHTML.
doc.ToHTML(&buf, comment, nil) // does html-escaping
- return buf.String()
+ return template.HTML(buf.String())
}
-// sanitizeFunc sanitizes the argument src by replacing newlines with
+// sanitize sanitizes the argument src by replacing newlines with
// blanks, removing extra blanks, and by removing trailing whitespace
// and commas before closing parentheses.
-func sanitizeFunc(src string) string {
+func sanitize(src template.HTML) template.HTML {
buf := make([]byte, len(src))
j := 0 // buf index
comma := -1 // comma index if >= 0
@@ -189,5 +201,13 @@ func sanitizeFunc(src string) string {
if j > 0 && buf[j-1] == ' ' {
j--
}
- return string(buf[:j])
+ return template.HTML(buf[:j])
+}
+
+// Since reports the Go version that introduced the API feature
+// identified by kind, reeciver, name.
+// The current package is deduced from p.Data, which must be a *pkgdoc.Page.
+func (p *Page) Since(kind, receiver, name string) string {
+ pkg := p.Data.(*pkgdoc.Page).PDoc.ImportPath
+ return p.pres.api.Func(pkg, kind, receiver, name)
}
diff --git a/internal/godoc/examplefuncs.go b/internal/godoc/examplefuncs.go
index 3d10fc1c..67099ec3 100644
--- a/internal/godoc/examplefuncs.go
+++ b/internal/godoc/examplefuncs.go
@@ -12,6 +12,7 @@ import (
"go/ast"
"go/format"
"go/printer"
+ "html/template"
"log"
"regexp"
"strings"
@@ -20,7 +21,10 @@ import (
"golang.org/x/website/internal/pkgdoc"
)
-func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) string {
+// Example renders the examples for the given function name as HTML.
+// The current package is deduced from p.Data, which must be a *pkgdoc.Page.
+func (p *Page) Example(funcName string) template.HTML {
+ info := p.Data.(*pkgdoc.Page)
var buf bytes.Buffer
for _, eg := range info.Examples {
name := pkgdoc.TrimExampleSuffix(eg.Name)
@@ -31,7 +35,7 @@ func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) stri
// print code
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
- code := p.node_htmlFunc(info, cnode, true)
+ code := string(p.Node(cnode))
out := eg.Output
wholeFile := true
@@ -66,20 +70,23 @@ func (p *Presentation) example_htmlFunc(info *pkgdoc.Page, funcName string) stri
out = ""
}
- if p.ExampleHTML == nil {
- out = ""
+ t := p.pres.Templates.Lookup("example.html")
+ if t == nil {
return ""
}
- err := p.ExampleHTML.Execute(&buf, struct {
+ newPage := *p
+ newPage.Data = struct {
Name, Doc, Code, Play, Output string
- GoogleCN bool
- }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
+ }{
+ eg.Name, eg.Doc, code, play, out,
+ }
+ err := t.Execute(&buf, &newPage)
if err != nil {
log.Print(err)
}
}
- return buf.String()
+ return template.HTML(buf.String())
}
// replaceLeadingIndentation replaces oldIndent at the beginning of each line
@@ -190,7 +197,7 @@ func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
// example_nameFunc takes an example function name and returns its display
// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
-func (p *Presentation) example_nameFunc(s string) string {
+func example_nameFunc(s string) string {
name, suffix := pkgdoc.SplitExampleName(s)
// replace _ with . for method names
name = strings.Replace(name, "_", ".", 1)
@@ -203,7 +210,7 @@ func (p *Presentation) example_nameFunc(s string) string {
// example_suffixFunc takes an example function name and returns its suffix in
// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
-func (p *Presentation) example_suffixFunc(name string) string {
+func example_suffixFunc(name string) string {
_, suffix := pkgdoc.SplitExampleName(name)
return suffix
}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index 33bd6313..7e6f9897 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -13,67 +13,35 @@ import (
"go/ast"
"go/doc"
"go/token"
+ "html"
+ "html/template"
"path"
- "strconv"
"strings"
- "text/template"
"golang.org/x/website/internal/history"
"golang.org/x/website/internal/pkgdoc"
)
func (p *Presentation) initFuncMap() {
- // 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{
+ p.docFuncs = template.FuncMap{
"code": p.code,
"releases": func() []*history.Major { return history.Majors },
}
-
- p.SiteFuncs = template.FuncMap{
- // various helpers
- "filename": filenameFunc,
- "since": p.api.Func,
-
- // formatting of AST nodes
- "node": p.nodeFunc,
- "node_html": p.node_htmlFunc,
- "comment_html": comment_htmlFunc,
- "sanitize": sanitizeFunc,
-
- // support for URL attributes
- "pkgLink": pkgLinkFunc,
- "srcLink": srcLinkFunc,
- "posLink_url": posLink_urlFunc,
- "docLink": docLinkFunc,
- "queryLink": queryLinkFunc,
- "srcBreadcrumb": srcBreadcrumbFunc,
- "srcToPkgLink": srcToPkgLinkFunc,
-
- // formatting of Examples
- "example_html": p.example_htmlFunc,
- "example_name": p.example_nameFunc,
- "example_suffix": p.example_suffixFunc,
-
- // Number operation
- "multiply": multiply,
-
- // formatting of PageInfoMode query string
- "modeQueryString": modeQueryString,
- }
}
-func multiply(a, b int) int { return a * b }
+var siteFuncs = template.FuncMap{
+ // various helpers
+ "basename": path.Base,
-func filenameFunc(name string) string {
- _, localname := path.Split(name)
- return localname
+ // formatting of Examples
+ "example_name": example_nameFunc,
+ "example_suffix": example_suffixFunc,
+
+ // Number operation
+ "multiply": func(a, b int) int { return a * b },
}
-func pkgLinkFunc(path string) string {
+func srcToPkg(path string) string {
// because of the irregular mapping under goroot
// we need to correct certain relative paths
path = strings.TrimPrefix(path, "/")
@@ -82,26 +50,26 @@ func pkgLinkFunc(path string) string {
return "pkg/" + path
}
-// srcToPkgLinkFunc builds an
tag linking to the package
-// documentation of relpath.
-func srcToPkgLinkFunc(relpath string) string {
- relpath = pkgLinkFunc(relpath)
- relpath = path.Dir(relpath)
- if relpath == "pkg" {
+// SrcPkgLink builds an 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 `Index`
}
- return fmt.Sprintf(`
%s`, relpath, relpath[len("pkg/"):])
+ dir = html.EscapeString(dir)
+ return template.HTML(fmt.Sprintf(`
%s`, dir, dir[len("pkg/"):]))
}
-// srcBreadcrumbFun converts each segment of relpath to a HTML
.
+// SrcBreadcrumb converts each segment of p.SrcPath to a HTML .
// Each segment links to its corresponding src directories.
-func srcBreadcrumbFunc(relpath string) string {
- segments := strings.Split(relpath, "/")
+func (p *Page) SrcBreadcrumb() template.HTML {
+ segments := strings.Split(p.SrcPath, "/")
var buf bytes.Buffer
var selectedSegment string
var selectedIndex int
- if strings.HasSuffix(relpath, "/") {
+ 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
@@ -113,18 +81,22 @@ func srcBreadcrumbFunc(relpath string) string {
for i := range segments[:selectedIndex] {
buf.WriteString(fmt.Sprintf(`%s/`,
- strings.Join(segments[:i+1], "/"),
- segments[i],
+ html.EscapeString(strings.Join(segments[:i+1], "/")),
+ html.EscapeString(segments[i]),
))
}
buf.WriteString(`
`)
- buf.WriteString(selectedSegment)
+ buf.WriteString(html.EscapeString(selectedSegment))
buf.WriteString(``)
- return buf.String()
+ return template.HTML(buf.String())
}
-func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string {
+// SrcPosLink returns a link to the specific source code position containing n,
+// which must be either an ast.Node or a *doc.Note.
+// The current package is deduced from p.Data, which must be a *pkgdoc.Page.
+func (p *Page) SrcPosLink(n interface{}) template.HTML {
+ info := p.Data.(*pkgdoc.Page)
// n must be an ast.Node or a *doc.Note
var pos, end token.Pos
@@ -136,7 +108,7 @@ func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string {
pos = n.Pos
end = n.End
default:
- panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
+ panic(fmt.Sprintf("wrong type for SrcPosLink template formatter: %T", n))
}
var relpath string
@@ -156,8 +128,11 @@ func posLink_urlFunc(info *pkgdoc.Page, n interface{}) string {
return srcPosLinkFunc(relpath, line, low, high)
}
-func srcPosLinkFunc(s string, line, low, high int) string {
- s = srcLinkFunc(s)
+func srcPosLinkFunc(s string, line, low, high int) template.HTML {
+ s = path.Clean("/" + s)
+ if !strings.HasPrefix(s, "/src/") {
+ s = "/src" + s
+ }
var buf bytes.Buffer
template.HTMLEscape(&buf, []byte(s))
// selection ranges are of form "s=low:high"
@@ -175,30 +150,5 @@ func srcPosLinkFunc(s string, line, low, high int) string {
if line > 0 {
fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
}
- return buf.String()
-}
-
-func srcLinkFunc(s string) string {
- s = path.Clean("/" + s)
- if !strings.HasPrefix(s, "/src/") {
- s = "/src" + s
- }
- return s
-}
-
-// queryLinkFunc returns a URL for a line in a source file with a highlighted
-// query term.
-// s is expected to be a path to a source file.
-// query is expected to be a string that has already been appropriately escaped
-// for use in a URL query.
-func queryLinkFunc(s, query string, line int) string {
- url := path.Clean("/"+s) + "?h=" + query
- if line > 0 {
- url += "#L" + strconv.Itoa(line)
- }
- return url
-}
-
-func docLinkFunc(s string, ident string) string {
- return path.Clean("/pkg/"+s) + "/#" + ident
+ return template.HTML(buf.String())
}
diff --git a/internal/godoc/godoc_test.go b/internal/godoc/godoc_test.go
index 74e472cd..eba1bd03 100644
--- a/internal/godoc/godoc_test.go
+++ b/internal/godoc/godoc_test.go
@@ -12,13 +12,14 @@ import (
"fmt"
"go/parser"
"go/token"
+ "html/template"
"strings"
"testing"
"golang.org/x/website/internal/pkgdoc"
)
-func TestPkgLinkFunc(t *testing.T) {
+func TestSrcToPkg(t *testing.T) {
for _, tc := range []struct {
path string
want string
@@ -27,9 +28,11 @@ func TestPkgLinkFunc(t *testing.T) {
{"src/fmt", "pkg/fmt"},
{"/fmt", "pkg/fmt"},
{"fmt", "pkg/fmt"},
+ {"src/pkg/fmt", "pkg/fmt"},
+ {"/src/pkg/fmt", "pkg/fmt"},
} {
- if got := pkgLinkFunc(tc.path); got != tc.want {
- t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
+ if got := srcToPkg(tc.path); got != tc.want {
+ t.Errorf("srcToPkg(%v) = %v; want %v", tc.path, got, tc.want)
}
}
}
@@ -40,7 +43,7 @@ func TestSrcPosLinkFunc(t *testing.T) {
line int
low int
high int
- want string
+ want template.HTML
}{
{"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"},
{"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
@@ -51,64 +54,15 @@ func TestSrcPosLinkFunc(t *testing.T) {
{"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
} {
if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want {
- t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want)
+ t.Errorf("srcPosLink(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want)
}
}
}
-func TestSrcLinkFunc(t *testing.T) {
+func TestSanitize(t *testing.T) {
for _, tc := range []struct {
- src string
- want string
- }{
- {"/src/fmt/print.go", "/src/fmt/print.go"},
- {"src/fmt/print.go", "/src/fmt/print.go"},
- {"/fmt/print.go", "/src/fmt/print.go"},
- {"fmt/print.go", "/src/fmt/print.go"},
- } {
- if got := srcLinkFunc(tc.src); got != tc.want {
- t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want)
- }
- }
-}
-
-func TestQueryLinkFunc(t *testing.T) {
- for _, tc := range []struct {
- src string
- query string
- line int
- want string
- }{
- {"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"},
- {"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"},
- {"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"},
- {"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"},
- } {
- if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want {
- t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want)
- }
- }
-}
-
-func TestDocLinkFunc(t *testing.T) {
- for _, tc := range []struct {
- src string
- ident string
- want string
- }{
- {"fmt", "Sprintf", "/pkg/fmt/#Sprintf"},
- {"fmt", "EOF", "/pkg/fmt/#EOF"},
- } {
- if got := docLinkFunc(tc.src, tc.ident); got != tc.want {
- t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want)
- }
- }
-}
-
-func TestSanitizeFunc(t *testing.T) {
- for _, tc := range []struct {
- src string
- want string
+ src template.HTML
+ want template.HTML
}{
{},
{"foo", "foo"},
@@ -121,8 +75,8 @@ func TestSanitizeFunc(t *testing.T) {
{"{ a, b}", "{a, b}"},
{"[ a, b]", "[a, b]"},
} {
- if got := sanitizeFunc(tc.src); got != tc.want {
- t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want)
+ if got := sanitize(tc.src); got != tc.want {
+ t.Errorf("sanitize(%v) = %v; want %v", tc.src, got, tc.want)
}
}
}
@@ -241,11 +195,15 @@ func linkifySource(t *testing.T, src []byte) string {
pi := &pkgdoc.Page{
FSet: fset,
}
+ pg := &Page{
+ pres: p,
+ Data: pi,
+ }
sep := ""
for _, decl := range af.Decls {
buf.WriteString(sep)
sep = "\n"
- buf.WriteString(p.node_htmlFunc(pi, decl, true))
+ buf.WriteString(string(pg.Node(decl)))
}
return buf.String()
}
@@ -279,29 +237,29 @@ func TestReplaceLeadingIndentation(t *testing.T) {
func TestSrcBreadcrumbFunc(t *testing.T) {
for _, tc := range []struct {
path string
- want string
+ want template.HTML
}{
{"src/", `
src/`},
{"src/fmt/", `
src/
fmt/`},
{"src/fmt/print.go", `
src/
fmt/
print.go`},
} {
- if got := srcBreadcrumbFunc(tc.path); got != tc.want {
+ if got := (&Page{SrcPath: tc.path}).SrcBreadcrumb(); got != tc.want {
t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want)
}
}
}
-func TestSrcToPkgLinkFunc(t *testing.T) {
+func TestSrcPkgLink(t *testing.T) {
for _, tc := range []struct {
path string
- want string
+ want template.HTML
}{
{"src/", `
Index`},
{"src/fmt/", `
fmt`},
{"pkg/", `
Index`},
{"pkg/LICENSE", `
Index`},
} {
- if got := srcToPkgLinkFunc(tc.path); got != tc.want {
+ if got := (&Page{SrcPath: tc.path}).SrcPkgLink(); got != tc.want {
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
}
}
diff --git a/internal/godoc/meta.go b/internal/godoc/meta.go
index 0c5809e6..27f4def1 100644
--- a/internal/godoc/meta.go
+++ b/internal/godoc/meta.go
@@ -17,7 +17,6 @@ import (
)
var (
- doctype = []byte("")
)
diff --git a/internal/godoc/page.go b/internal/godoc/page.go
index 6c06ba58..9426dd76 100644
--- a/internal/godoc/page.go
+++ b/internal/godoc/page.go
@@ -9,50 +9,64 @@ package godoc
import (
"net/http"
- "os"
- "path/filepath"
"runtime"
)
-// Page describes the contents of the top-level godoc webpage.
+// A Page describes the contents of a webpage to be served.
+//
+// A Page's Methods are for use by the templates rendering the page.
type Page struct {
- Title string
- Tabtitle string
- Subtitle string
- SrcPath string
- Query string
- Body []byte
- GoogleCN bool // page is being served from golang.google.cn
-
- // filled in by ServePage
- Version string
- GoogleAnalytics string
+ Title string //
+ TabTitle string // prefix in ; defaults to Title
+ Subtitle string // subtitle (date for spec, memory model)
+ SrcPath string // path to file in /src for text view
+
+ // Template and Data describe the data to be
+ // rendered into the overall site frame template.
+ // If Template is empty, then Data should be a template.HTML
+ // holding raw HTML to render into the site frame.
+ // Otherwise, Template should be the name of a template file
+ // in _content/lib/godoc (for example, "package.html"),
+ // and that template will be executed
+ // (with the *Page as its data argument) to produce HTML.
+ //
+ // The overall site template site.html is also invoked with
+ // the *Page as its data argument. It is what arranges to call Template.
+ Template string // template to apply to data (empty string when Data is raw template.HTML)
+ Data interface{} // data to be rendered into page frame
+
+ // Filled in automatically by ServePage
+ GoogleCN bool // page is being served from golang.google.cn
+ GoogleAnalytics string // Google Analytics tag
+ Version string // current Go version
+
+ pres *Presentation
}
-func (p *Presentation) ServePage(w http.ResponseWriter, page Page) {
- if page.Tabtitle == "" {
- page.Tabtitle = page.Title
+// fullPage returns a copy of page with the “automatic” fields filled in.
+func (p *Presentation) fullPage(r *http.Request, page Page) Page {
+ if page.TabTitle == "" {
+ page.TabTitle = page.Title
}
page.Version = runtime.Version()
+ page.GoogleCN = p.googleCN(r)
page.GoogleAnalytics = p.GoogleAnalytics
- applyTemplateToResponseWriter(w, p.GodocHTML, page)
+ page.pres = p
+ return page
}
-func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
+// ServePage responds to the request with the content described by page.
+func (p *Presentation) ServePage(w http.ResponseWriter, r *http.Request, page Page) {
+ page = p.fullPage(r, page)
+ applyTemplateToResponseWriter(w, p.Templates.Lookup("site.html"), &page)
+}
+
+// ServeError responds to the request with the given error.
+func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusNotFound)
- if perr, ok := err.(*os.PathError); ok {
- rel, err := filepath.Rel(runtime.GOROOT(), perr.Path)
- if err != nil {
- perr.Path = "REDACTED"
- } else {
- perr.Path = filepath.Join("$GOROOT", rel)
- }
- }
- p.ServePage(w, Page{
- Title: "File " + relpath,
- Subtitle: relpath,
- Body: applyTemplate(p.ErrorHTML, "errorHTML", err),
- GoogleCN: p.googleCN(r),
- GoogleAnalytics: p.GoogleAnalytics,
+ p.ServePage(w, r, Page{
+ Title: r.URL.Path,
+ Template: "error.html",
+ Data: err,
})
}
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
index b60e9b6c..51928c4a 100644
--- a/internal/godoc/pres.go
+++ b/internal/godoc/pres.go
@@ -8,15 +8,15 @@
package godoc
import (
+ "html/template"
"io/fs"
"net/http"
- "text/template"
"golang.org/x/website/internal/api"
"golang.org/x/website/internal/pkgdoc"
)
-// Presentation generates output from a file system.
+// Presentation is a website served from a file system.
type Presentation struct {
fs fs.FS
api api.DB
@@ -24,12 +24,7 @@ type Presentation struct {
mux *http.ServeMux
fileServer http.Handler
- DirlistHTML,
- ErrorHTML,
- ExampleHTML,
- GodocHTML,
- PackageHTML,
- PackageRootHTML *template.Template
+ Templates *template.Template
// GoogleCN reports whether this request should be marked GoogleCN.
// If the function is nil, no requests are marked GoogleCN.
@@ -39,8 +34,7 @@ type Presentation struct {
// tracking ID to each page.
GoogleAnalytics string
- DocFuncs template.FuncMap
- SiteFuncs template.FuncMap
+ docFuncs template.FuncMap
}
// NewPresentation returns a new Presentation from a file system.
@@ -61,31 +55,19 @@ func NewPresentation(fsys fs.FS) (*Presentation, error) {
}
p.mux.Handle("/cmd/", docs)
p.mux.Handle("/pkg/", docs)
- p.mux.HandleFunc("/", p.ServeFile)
+ p.mux.HandleFunc("/", p.serveFile)
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 {
+ t, err := template.New("").Funcs(siteFuncs).ParseFS(fsys, "lib/godoc/*.html")
+ if err != nil {
return nil, err
}
+ p.Templates = t
return p, nil
}
+// ServeHTTP implements http.Handler, dispatching the request appropriately.
func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.mux.ServeHTTP(w, r)
}
@@ -93,15 +75,3 @@ func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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 c10b189a..90b119f8 100644
--- a/internal/godoc/server.go
+++ b/internal/godoc/server.go
@@ -12,6 +12,7 @@ import (
"encoding/json"
"fmt"
htmlpkg "html"
+ "html/template"
"io"
"io/fs"
"log"
@@ -20,7 +21,6 @@ import (
"regexp"
"strconv"
"strings"
- "text/template"
"golang.org/x/website/internal/pkgdoc"
"golang.org/x/website/internal/spec"
@@ -47,7 +47,8 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// TODO(rsc): URL should be clean already.
- relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg/"))
+ relpath := path.Clean(strings.TrimPrefix(r.URL.Path, "/pkg"))
+ relpath = strings.TrimPrefix(relpath, "/")
abspath := path.Join("/src", relpath)
mode := pkgdoc.ParseMode(r.FormValue("m"))
@@ -60,7 +61,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
info := pkgdoc.Doc(h.d, abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
if info.Err != nil {
log.Print(info.Err)
- h.p.ServeError(w, r, relpath, info.Err)
+ h.p.ServeError(w, r, info.Err)
return
}
@@ -93,23 +94,23 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tabtitle = "Commands"
}
- info.GoogleCN = h.p.googleCN(r)
- var body []byte
+ name := "package.html"
if info.Dirname == "/src" {
- body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info)
- } else {
- body = applyTemplate(h.p.PackageHTML, "packageHTML", info)
+ name = "packageroot.html"
}
- h.p.ServePage(w, Page{
+ h.p.ServePage(w, r, Page{
Title: title,
- Tabtitle: tabtitle,
+ TabTitle: tabtitle,
Subtitle: subtitle,
- Body: body,
- GoogleCN: info.GoogleCN,
+ Template: name,
+ Data: info,
})
}
-func modeQueryString(m pkgdoc.Mode) string {
+// ModeQuery returns the "?m=..." query for the current page.
+// The page's Data must be a *pkgdoc.Page (to find the mode).
+func (p *Page) ModeQuery() string {
+ m := p.Data.(*pkgdoc.Page).Mode
s := m.String()
if s == "" {
return ""
@@ -117,12 +118,17 @@ func modeQueryString(m pkgdoc.Mode) string {
return "?m=" + s
}
-func applyTemplate(t *template.Template, name string, data interface{}) []byte {
+// 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.pres.Templates.Lookup(name)
var buf bytes.Buffer
- if err := t.Execute(&buf, data); err != nil {
- log.Printf("%s.Execute: %s", name, err)
+ p1 := *p
+ p1.Data = data
+ if err := t.Execute(&buf, &p1); err != nil {
+ log.Printf("%s.Execute: %s", t.Name(), err)
}
- return buf.Bytes()
+ return template.HTML(buf.String())
}
type writerCapturesErr struct {
@@ -202,12 +208,12 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
src, err := fs.ReadFile(p.fs, toFS(abspath))
if err != nil {
log.Printf("ReadFile: %s", err)
- p.ServeError(w, r, relpath, err)
+ p.ServeError(w, r, err)
return
}
if r.FormValue("m") == "text" {
- p.ServeText(w, src)
+ p.serveText(w, src)
return
}
@@ -229,12 +235,11 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
if strings.HasSuffix(relpath, ".go") {
title = "Source file"
}
- p.ServePage(w, Page{
+ p.ServePage(w, r, Page{
Title: title,
SrcPath: relpath,
- Tabtitle: relpath,
- Body: buf.Bytes(),
- GoogleCN: p.googleCN(r),
+ TabTitle: relpath,
+ Data: template.HTML(buf.String()),
})
}
@@ -245,7 +250,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
list, err := fs.ReadDir(p.fs, toFS(abspath))
if err != nil {
- p.ServeError(w, r, relpath, err)
+ p.ServeError(w, r, err)
return
}
@@ -257,15 +262,17 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
}
}
- p.ServePage(w, Page{
+ p.ServePage(w, r, Page{
Title: "Directory",
SrcPath: relpath,
- Tabtitle: relpath,
- Body: applyTemplate(p.DirlistHTML, "dirlistHTML", info),
- GoogleCN: p.googleCN(r),
+ TabTitle: relpath,
+ Template: "dirlist.html",
+ Data: info,
})
}
+var doctype = []byte(" %q, got %d -> %q", r.URL.Path, dir, rw.Code, loc)
@@ -53,12 +53,13 @@ func TestRedirectAndMetadata(t *testing.T) {
}
func TestMarkdown(t *testing.T) {
- p := &Presentation{
- fs: fstest.MapFS{
- "doc/test.md": {Data: []byte("**bold**")},
- "doc/test2.md": {Data: []byte(`{{"*template*"}}`)},
- },
- GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)),
+ p, err := NewPresentation(fstest.MapFS{
+ "doc/test.md": {Data: []byte("**bold**")},
+ "doc/test2.md": {Data: []byte(`{{"*template*"}}`)},
+ "lib/godoc/site.html": {Data: []byte(`{{.Data}}`)},
+ })
+ if err != nil {
+ t.Fatal(err)
}
testServeBody(t, p, "/doc/test", "bold")
diff --git a/internal/godoc/template.go b/internal/godoc/template.go
index 138b71ff..214a1591 100644
--- a/internal/godoc/template.go
+++ b/internal/godoc/template.go
@@ -37,6 +37,7 @@ package godoc
import (
"bytes"
"fmt"
+ "html/template"
"io/fs"
"log"
"regexp"
@@ -74,7 +75,7 @@ func stringFor(arg interface{}) string {
return ""
}
-func (p *Presentation) code(file string, arg ...interface{}) (s string, err error) {
+func (p *Presentation) code(file string, arg ...interface{}) (_ template.HTML, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
@@ -105,7 +106,7 @@ func (p *Presentation) code(file string, arg ...interface{}) (s string, err erro
buf.Write(texthtml.Format([]byte(text), texthtml.Config{GoComments: true}))
// Include the command as a comment.
text = fmt.Sprintf("%s
", command, buf.Bytes())
- return text, nil
+ return template.HTML(text), nil
}
// parseArg returns the integer or string value of the argument and tells which it is.
diff --git a/internal/pkgdoc/doc.go b/internal/pkgdoc/doc.go
index a5b98aca..02e7b5b3 100644
--- a/internal/pkgdoc/doc.go
+++ b/internal/pkgdoc/doc.go
@@ -44,9 +44,8 @@ func NewDocs(fsys fs.FS) *Docs {
}
type Page struct {
- Dirname string // directory containing the package
- Err error // error or nil
- GoogleCN bool // page is being served from golang.google.cn
+ Dirname string // directory containing the package
+ Err error // error or nil
Mode Mode // display metadata from query string
--
cgit v1.3
+ ERROR LOADING FILE: {{.}}
{{end}} - {{.XML}} + {{.HTML}}