diff --git a/cmd/golangorg/handlers.go b/cmd/golangorg/handlers.go index d40c486a..1cafdcbd 100644 --- a/cmd/golangorg/handlers.go +++ b/cmd/golangorg/handlers.go @@ -87,6 +87,7 @@ func registerHandlers(pres *godoc.Presentation) *http.ServeMux { mux.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/")) mux.HandleFunc("/fmt", fmtHandler) mux.Handle("/doc/devel/release.html", releaseHandler{ReleaseHistory: sortReleases(history.Releases)}) + handleRootAndSubtree(mux, "/project/", projectHandler{ReleaseHistory: sortMajorReleases(history.Releases)}, pres) redirect.Register(mux) http.Handle("/", hostEnforcerHandler{mux}) @@ -94,6 +95,26 @@ func registerHandlers(pres *godoc.Presentation) *http.ServeMux { return mux } +// handleRootAndSubtree registers a handler for the given pattern in mux. +// The handler selects between root or subtree handlers to handle requests. +// +// The root handler is used for requests with URL path equal to the pattern, +// and the subtree handler is used for all other requests matched by pattern. +// +// The pattern must have a trailing slash ('/'), otherwise handleRoot panics. +func handleRootAndSubtree(mux *http.ServeMux, path string, root, subtree http.Handler) { + if !strings.HasSuffix(path, "/") { + panic("handleRootAndSubtree must be used on patterns with a trailing slash ('/')") + } + mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == path { + root.ServeHTTP(w, req) + } else { + subtree.ServeHTTP(w, req) + } + }) +} + func readTemplate(name string) *template.Template { if pres == nil { panic("no global Presentation set yet") diff --git a/cmd/golangorg/project.go b/cmd/golangorg/project.go new file mode 100644 index 00000000..dd5e8d36 --- /dev/null +++ b/cmd/golangorg/project.go @@ -0,0 +1,114 @@ +// 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 main + +import ( + "bytes" + "fmt" + "html/template" + "log" + "net/http" + "sort" + + "golang.org/x/tools/godoc" + "golang.org/x/tools/godoc/vfs" + "golang.org/x/website/internal/history" +) + +// projectHandler serves The Go Project page. +type projectHandler struct { + ReleaseHistory []MajorRelease // Pre-computed release history to display. +} + +func (h projectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + const relPath = "doc/contrib.html" + + src, err := vfs.ReadFile(fs, relPath) + if err != nil { + log.Printf("reading template %s: %v", relPath, err) + pres.ServeError(w, req, relPath, err) + return + } + + meta, src, err := extractMetadata(src) + if err != nil { + log.Printf("decoding metadata %s: %v", relPath, err) + pres.ServeError(w, req, relPath, err) + return + } + if !meta.Template { + err := fmt.Errorf("got non-template, want template") + log.Printf("unexpected metadata %s: %v", relPath, err) + pres.ServeError(w, req, relPath, err) + return + } + + page := godoc.Page{ + Title: meta.Title, + Subtitle: meta.Subtitle, + GoogleCN: googleCN(req), + } + data := projectTemplateData{ + Major: h.ReleaseHistory, + } + + // Evaluate as HTML template. + tmpl, err := template.New("").Parse(string(src)) + if err != nil { + log.Printf("parsing template %s: %v", relPath, err) + pres.ServeError(w, req, relPath, err) + return + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + log.Printf("executing template %s: %v", relPath, err) + pres.ServeError(w, req, relPath, err) + return + } + src = buf.Bytes() + + page.Body = src + pres.ServePage(w, page) +} + +// sortMajorReleases returns a sorted list of major Go releases, +// suitable to be displayed on the Go project page. +func sortMajorReleases(rs map[history.GoVer]history.Release) []MajorRelease { + var major []MajorRelease + for v, r := range rs { + if !v.IsMajor() { + continue + } + major = append(major, MajorRelease{ver: v, rel: r}) + } + sort.Slice(major, func(i, j int) bool { + if major[i].ver.X != major[j].ver.X { + return major[i].ver.X > major[j].ver.X + } + return major[i].ver.Y > major[j].ver.Y + }) + return major +} + +type projectTemplateData struct { + Major []MajorRelease +} + +// MajorRelease represents a major Go release entry as displayed on the Go project page. +type MajorRelease struct { + ver history.GoVer + rel history.Release +} + +// V returns the Go release version string, like "1.14", "1.14.1", "1.14.2", etc. +func (r MajorRelease) V() string { + return r.ver.String() +} + +// Date returns the date of the release, formatted for display on the Go project page. +func (r MajorRelease) Date() string { + d := r.rel.Date + return fmt.Sprintf("%s %d", d.Month, d.Year) +} diff --git a/cmd/golangorg/regtest_test.go b/cmd/golangorg/regtest_test.go index 58127d15..e2359905 100644 --- a/cmd/golangorg/regtest_test.go +++ b/cmd/golangorg/regtest_test.go @@ -128,6 +128,16 @@ func TestLiveServer(t *testing.T) { Path: "/doc/devel/release.html", Regexp: `go1\.14\.2\s+\(released 2020/04/08\)\s+includes\s+fixes to cgo, the go command, the runtime,`, }, + { + Message: "Go project page has an entry for Go 1.14", + Path: "/project/", + Substring: `
  • Go 1.14 (February 2020)
  • `, + }, + { + Message: "Go project subpath does not exist", + Path: "/project/notexist", + StatusCode: http.StatusNotFound, + }, } for _, tc := range substringTests { diff --git a/cmd/golangorg/release.go b/cmd/golangorg/release.go index 61def3f4..e21c4c2b 100644 --- a/cmd/golangorg/release.go +++ b/cmd/golangorg/release.go @@ -27,7 +27,7 @@ type releaseHandler struct { func (h releaseHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { const relPath = "doc/devel/release.html" - src, err := vfs.ReadFile(fs, "/doc/devel/release.html") + src, err := vfs.ReadFile(fs, relPath) if err != nil { log.Printf("reading template %s: %v", relPath, err) pres.ServeError(w, req, relPath, err) diff --git a/content/static/doc/contrib.html b/content/static/doc/contrib.html index f0a01c5f..663ea90c 100644 --- a/content/static/doc/contrib.html +++ b/content/static/doc/contrib.html @@ -1,6 +1,7 @@ @@ -34,12 +35,13 @@

    Release History

    A summary of the changes between Go releases. Notes for the major releases: