|
| 1 | +// Copyright 2020 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +package main |
| 6 | + |
| 7 | +import ( |
| 8 | + "bytes" |
| 9 | + "fmt" |
| 10 | + "html" |
| 11 | + "html/template" |
| 12 | + "log" |
| 13 | + "net/http" |
| 14 | + "sort" |
| 15 | + "strings" |
| 16 | + |
| 17 | + "golang.org/x/tools/godoc" |
| 18 | + "golang.org/x/tools/godoc/vfs" |
| 19 | + "golang.org/x/website/internal/history" |
| 20 | +) |
| 21 | + |
| 22 | +// releaseHandler serves the Release History page. |
| 23 | +type releaseHandler struct { |
| 24 | + ReleaseHistory []Major // Pre-computed release history to display. |
| 25 | +} |
| 26 | + |
| 27 | +func (h releaseHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
| 28 | + const relPath = "doc/devel/release.html" |
| 29 | + |
| 30 | + src, err := vfs.ReadFile(fs, "/doc/devel/release.html") |
| 31 | + if err != nil { |
| 32 | + log.Printf("reading template %s: %v", relPath, err) |
| 33 | + pres.ServeError(w, req, relPath, err) |
| 34 | + return |
| 35 | + } |
| 36 | + |
| 37 | + meta, src, err := extractMetadata(src) |
| 38 | + if err != nil { |
| 39 | + log.Printf("decoding metadata %s: %v", relPath, err) |
| 40 | + pres.ServeError(w, req, relPath, err) |
| 41 | + return |
| 42 | + } |
| 43 | + if !meta.Template { |
| 44 | + err := fmt.Errorf("got non-template, want template") |
| 45 | + log.Printf("unexpected metadata %s: %v", relPath, err) |
| 46 | + pres.ServeError(w, req, relPath, err) |
| 47 | + return |
| 48 | + } |
| 49 | + |
| 50 | + page := godoc.Page{ |
| 51 | + Title: meta.Title, |
| 52 | + Subtitle: meta.Subtitle, |
| 53 | + GoogleCN: googleCN(req), |
| 54 | + } |
| 55 | + data := releaseTemplateData{ |
| 56 | + Major: h.ReleaseHistory, |
| 57 | + } |
| 58 | + |
| 59 | + // Evaluate as HTML template. |
| 60 | + tmpl, err := template.New("").Parse(string(src)) |
| 61 | + if err != nil { |
| 62 | + log.Printf("parsing template %s: %v", relPath, err) |
| 63 | + pres.ServeError(w, req, relPath, err) |
| 64 | + return |
| 65 | + } |
| 66 | + var buf bytes.Buffer |
| 67 | + if err := tmpl.Execute(&buf, data); err != nil { |
| 68 | + log.Printf("executing template %s: %v", relPath, err) |
| 69 | + pres.ServeError(w, req, relPath, err) |
| 70 | + return |
| 71 | + } |
| 72 | + src = buf.Bytes() |
| 73 | + |
| 74 | + page.Body = src |
| 75 | + pres.ServePage(w, page) |
| 76 | +} |
| 77 | + |
| 78 | +// sortReleases returns a sorted list of Go releases, suitable to be |
| 79 | +// displayed on the Release History page. Releases are arranged into |
| 80 | +// major releases, each with minor revisions. |
| 81 | +func sortReleases(rs map[history.Version]history.Release) []Major { |
| 82 | + var major []Major |
| 83 | + byMajorVersion := make(map[history.Version]Major) |
| 84 | + for v, r := range rs { |
| 85 | + switch { |
| 86 | + case v.IsMajor(): |
| 87 | + m := byMajorVersion[v] |
| 88 | + m.Release = Release{ver: v, rel: r} |
| 89 | + byMajorVersion[v] = m |
| 90 | + case v.IsMinor(): |
| 91 | + m := byMajorVersion[majorOf(v)] |
| 92 | + m.Minor = append(m.Minor, Release{ver: v, rel: r}) |
| 93 | + byMajorVersion[majorOf(v)] = m |
| 94 | + } |
| 95 | + } |
| 96 | + for _, m := range byMajorVersion { |
| 97 | + sort.Slice(m.Minor, func(i, j int) bool { return m.Minor[i].ver.Z < m.Minor[j].ver.Z }) |
| 98 | + major = append(major, m) |
| 99 | + } |
| 100 | + sort.Slice(major, func(i, j int) bool { |
| 101 | + if major[i].ver.X != major[j].ver.X { |
| 102 | + return major[i].ver.X > major[j].ver.X |
| 103 | + } |
| 104 | + return major[i].ver.Y > major[j].ver.Y |
| 105 | + }) |
| 106 | + return major |
| 107 | +} |
| 108 | + |
| 109 | +// majorOf takes a Go version like 1.5, 1.5.1, 1.5.2, etc., |
| 110 | +// and returns the corresponding major version like 1.5. |
| 111 | +func majorOf(v history.Version) history.Version { |
| 112 | + return history.Version{X: v.X, Y: v.Y, Z: 0} |
| 113 | +} |
| 114 | + |
| 115 | +type releaseTemplateData struct { |
| 116 | + Major []Major |
| 117 | +} |
| 118 | + |
| 119 | +// Major represents a major Go release and its minor revisions |
| 120 | +// as displayed on the release history page. |
| 121 | +type Major struct { |
| 122 | + Release |
| 123 | + Minor []Release |
| 124 | +} |
| 125 | + |
| 126 | +// Release represents a Go release entry as displayed on the release history page. |
| 127 | +type Release struct { |
| 128 | + ver history.Version |
| 129 | + rel history.Release |
| 130 | +} |
| 131 | + |
| 132 | +// V returns the Go release version string, like "1.14", "1.14.1", "1.14.2", etc. |
| 133 | +func (r Release) V() string { |
| 134 | + switch { |
| 135 | + case r.ver.Z != 0: |
| 136 | + return fmt.Sprintf("%d.%d.%d", r.ver.X, r.ver.Y, r.ver.Z) |
| 137 | + case r.ver.Y != 0: |
| 138 | + return fmt.Sprintf("%d.%d", r.ver.X, r.ver.Y) |
| 139 | + default: |
| 140 | + return fmt.Sprintf("%d", r.ver.X) |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +// Date returns the date of the release, formatted for display on the release history page. |
| 145 | +func (r Release) Date() string { |
| 146 | + d := r.rel.Date |
| 147 | + return fmt.Sprintf("%04d/%02d/%02d", d.Year, d.Month, d.Day) |
| 148 | +} |
| 149 | + |
| 150 | +// Released reports whether release r has been released. |
| 151 | +func (r Release) Released() bool { |
| 152 | + return !r.rel.Future |
| 153 | +} |
| 154 | + |
| 155 | +func (r Release) Summary() (template.HTML, error) { |
| 156 | + var buf bytes.Buffer |
| 157 | + err := releaseSummaryHTML.Execute(&buf, releaseSummaryTemplateData{ |
| 158 | + V: r.V(), |
| 159 | + Security: r.rel.Security, |
| 160 | + Released: r.Released(), |
| 161 | + Quantifier: r.rel.Quantifier, |
| 162 | + ComponentsAndPackages: joinComponentsAndPackages(r.rel), |
| 163 | + More: r.rel.More, |
| 164 | + CustomSummary: r.rel.CustomSummary, |
| 165 | + }) |
| 166 | + return template.HTML(buf.String()), err |
| 167 | +} |
| 168 | + |
| 169 | +type releaseSummaryTemplateData struct { |
| 170 | + V string // Go release version string, like "1.14", "1.14.1", "1.14.2", etc. |
| 171 | + Security bool // Security release. |
| 172 | + Released bool // Whether release has been released. |
| 173 | + Quantifier string // Optional quantifier. Empty string for unspecified amount of fixes (typical), "a" for a single fix, "two", "three" for multiple fixes, etc. |
| 174 | + ComponentsAndPackages template.HTML // Components and packages involved. |
| 175 | + More template.HTML // Additional release content. |
| 176 | + CustomSummary template.HTML // CustomSummary, if non-empty, replaces the entire release content summary with custom HTML. |
| 177 | +} |
| 178 | + |
| 179 | +var releaseSummaryHTML = template.Must(template.New("").Parse(` |
| 180 | +{{if not .CustomSummary}} |
| 181 | + {{if .Released}}includes{{else}}will include{{end}} |
| 182 | + {{.Quantifier}} |
| 183 | + {{if .Security}}security{{end}} |
| 184 | + {{if eq .Quantifier "a"}}fix{{else}}fixes{{end -}} |
| 185 | + {{with .ComponentsAndPackages}} to {{.}}{{end}}. |
| 186 | + {{.More}} |
| 187 | +
|
| 188 | + See the |
| 189 | + <a href="https://github.com/golang/go/issues?q=milestone%3AGo{{.V}}+label%3ACherryPickApproved">Go |
| 190 | + {{.V}} milestone</a> on our issue tracker for details. |
| 191 | +{{else}} |
| 192 | + {{.CustomSummary}} |
| 193 | +{{end}} |
| 194 | +`)) |
| 195 | + |
| 196 | +// joinComponentsAndPackages joins components and packages involved |
| 197 | +// in a Go release for the purposes of being displayed on the |
| 198 | +// release history page, keeping English grammar rules in mind. |
| 199 | +// |
| 200 | +// The different special cases are: |
| 201 | +// |
| 202 | +// c1 |
| 203 | +// c1 and c2 |
| 204 | +// c1, c2, and c3 |
| 205 | +// |
| 206 | +// the p1 package |
| 207 | +// the p1 and p2 packages |
| 208 | +// the p1, p2, and p3 packages |
| 209 | +// |
| 210 | +// c1 and [1 package] |
| 211 | +// c1, and [2 or more packages] |
| 212 | +// c1, c2, and [1 or more packages] |
| 213 | +// |
| 214 | +func joinComponentsAndPackages(r history.Release) template.HTML { |
| 215 | + var buf strings.Builder |
| 216 | + |
| 217 | + // List components, if any. |
| 218 | + for i, comp := range r.Components { |
| 219 | + if len(r.Packages) == 0 { |
| 220 | + // No packages, so components are joined with more rules. |
| 221 | + switch { |
| 222 | + case i != 0 && len(r.Components) == 2: |
| 223 | + buf.WriteString(" and ") |
| 224 | + case i != 0 && len(r.Components) >= 3 && i != len(r.Components)-1: |
| 225 | + buf.WriteString(", ") |
| 226 | + case i != 0 && len(r.Components) >= 3 && i == len(r.Components)-1: |
| 227 | + buf.WriteString(", and ") |
| 228 | + } |
| 229 | + } else { |
| 230 | + // When there are packages, all components are comma-separated. |
| 231 | + if i != 0 { |
| 232 | + buf.WriteString(", ") |
| 233 | + } |
| 234 | + } |
| 235 | + buf.WriteString(string(comp)) |
| 236 | + } |
| 237 | + |
| 238 | + // Join components and packages using a comma and/or "and" as needed. |
| 239 | + if len(r.Components) > 0 && len(r.Packages) > 0 { |
| 240 | + if len(r.Components)+len(r.Packages) >= 3 { |
| 241 | + buf.WriteString(",") |
| 242 | + } |
| 243 | + buf.WriteString(" and ") |
| 244 | + } |
| 245 | + |
| 246 | + // List packages, if any. |
| 247 | + if len(r.Packages) > 0 { |
| 248 | + buf.WriteString("the ") |
| 249 | + } |
| 250 | + for i, pkg := range r.Packages { |
| 251 | + switch { |
| 252 | + case i != 0 && len(r.Packages) == 2: |
| 253 | + buf.WriteString(" and ") |
| 254 | + case i != 0 && len(r.Packages) >= 3 && i != len(r.Packages)-1: |
| 255 | + buf.WriteString(", ") |
| 256 | + case i != 0 && len(r.Packages) >= 3 && i == len(r.Packages)-1: |
| 257 | + buf.WriteString(", and ") |
| 258 | + } |
| 259 | + buf.WriteString("<code>" + html.EscapeString(pkg) + "</code>") |
| 260 | + } |
| 261 | + switch { |
| 262 | + case len(r.Packages) == 1: |
| 263 | + buf.WriteString(" package") |
| 264 | + case len(r.Packages) >= 2: |
| 265 | + buf.WriteString(" packages") |
| 266 | + } |
| 267 | + |
| 268 | + return template.HTML(buf.String()) |
| 269 | +} |
0 commit comments