-
Notifications
You must be signed in to change notification settings - Fork 0
/
history.go
226 lines (204 loc) · 6.51 KB
/
history.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// 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 history holds the Go project release history.
package history
import (
"fmt"
"html"
"html/template"
"sort"
"strings"
"time"
)
// A Release describes a single Go release.
type Release struct {
Version Version
Date Date
Future bool // if true, the release hasn't happened yet
// Release content summary.
Security *FixSummary // Security fixes, if any.
Bug *FixSummary // Bug fixes, if any.
More template.HTML // Additional release content.
CustomSummary template.HTML // CustomSummary, if non-empty, replaces the entire release content summary with custom HTML.
}
// FixSummary summarizes fixes in a Go release, listing components and packages involved.
type FixSummary struct {
Quantifier string // Optional quantifier. Empty string for unspecified amount of fixes (typical), "a" for a single fix, "two", "three" for multiple fixes, etc.
Components []template.HTML // Components involved. For example, "cgo", "the compiler", "runtime", "the <code>go</code> command", etc.
Packages []string // Packages involved. For example, "crypto/x509", "net/http", etc.
}
// A Version is a Go release version.
//
// In contrast to Semantic Versioning 2.0.0,
// a version like Go 1.21.0 is considered a major Go release and
// a version like Go 1.21.1 is considered a minor Go release.
// Prior to 1.21.0, trailing zero components were omitted. (See proposal 57631 that changed this.)
//
// See proposal go.dev/issue/32450 for background, details,
// and a discussion of the costs involved in making a change.
type Version struct {
X int // X is the 1st component of a Go X.Y.Z version. It must be 1 or higher.
Y int // Y is the 2nd component of a Go X.Y.Z version. It must be 0 or higher.
Z int // Z is the 3rd component of a Go X.Y.Z version. It must be 0 or higher.
}
// String returns the Go release version string,
// like "1.21.0", "1.21.1", "1.21.2", and so on.
func (v Version) String() string {
switch {
// Starting with 1.21.0, trailing 0 components are no longer
// left out out of the Go version string. See proposal 57631.
// So it only needs to be done for prior historical versions.
case v.X == 1 && v.Y < 21 && v.Z == 0:
return fmt.Sprintf("%d.%d", v.X, v.Y)
case v.X == 1 && v.Y == 0 && v.Z == 0:
return fmt.Sprintf("%d", v.X)
default:
return fmt.Sprintf("%d.%d.%d", v.X, v.Y, v.Z)
}
}
// MajorPrefix returns the major version prefix of a Go release version,
// like "1", "1.20", "1.21", and so on.
//
// This prefix can be used when referring to the entire series of releases,
// including the major Go release and all of its subsequent minor releases,
// that this version belongs to.
func (v Version) MajorPrefix() string {
switch {
case v.Y != 0:
return fmt.Sprintf("%d.%d", v.X, v.Y)
default:
return fmt.Sprintf("%d", v.X)
}
}
// Before reports whether version v comes before version u.
func (v Version) Before(u Version) bool {
if v.X != u.X {
return v.X < u.X
}
if v.Y != u.Y {
return v.Y < u.Y
}
return v.Z < u.Z
}
// A Date represents the date (year, month, day) of a Go release.
//
// This type does not include location information, and
// therefore does not describe a unique 24-hour timespan.
type Date struct {
Year int // Year (e.g., 2009).
Month time.Month // Month of the year (January = 1, ...).
Day int // Day of the month, starting at 1.
}
func (d Date) String() string {
return d.Format("2006-01-02")
}
func (d Date) Format(format string) string {
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, time.UTC).Format(format)
}
// A Major describes a major Go release and its minor revisions.
type Major struct {
*Release
Minor []*Release // oldest first
}
var Majors []*Major = majors() // major versions, newest first
// majors returns a list of major versions, sorted newest first.
func majors() []*Major {
byVersion := make(map[Version]*Major)
for _, r := range Releases {
v := r.Version
v.Z = 0 // make major version
m := byVersion[v]
if m == nil {
m = new(Major)
byVersion[v] = m
}
if r.Version.Z == 0 {
m.Release = r
} else {
m.Minor = append(m.Minor, r)
}
}
var majors []*Major
for _, m := range byVersion {
majors = append(majors, m)
// minors oldest first
sort.Slice(m.Minor, func(i, j int) bool {
return m.Minor[i].Version.Before(m.Minor[j].Version)
})
}
// majors newest first
sort.Slice(majors, func(i, j int) bool {
return !majors[i].Version.Before(majors[j].Version)
})
return majors
}
// ComponentsAndPackages joins components and packages involved
// in a Go release for the purposes of being displayed on the
// release history page, keeping English grammar rules in mind.
//
// The different special cases are:
//
// c1
// c1 and c2
// c1, c2, and c3
//
// the p1 package
// the p1 and p2 packages
// the p1, p2, and p3 packages
//
// c1 and [1 package]
// c1, and [2 or more packages]
// c1, c2, and [1 or more packages]
func (f *FixSummary) ComponentsAndPackages() template.HTML {
var buf strings.Builder
// List components, if any.
for i, comp := range f.Components {
if len(f.Packages) == 0 {
// No packages, so components are joined with more rules.
switch {
case i != 0 && len(f.Components) == 2:
buf.WriteString(" and ")
case i != 0 && len(f.Components) >= 3 && i != len(f.Components)-1:
buf.WriteString(", ")
case i != 0 && len(f.Components) >= 3 && i == len(f.Components)-1:
buf.WriteString(", and ")
}
} else {
// When there are packages, all components are comma-separated.
if i != 0 {
buf.WriteString(", ")
}
}
buf.WriteString(string(comp))
}
// Join components and packages using a comma and/or "and" as needed.
if len(f.Components) > 0 && len(f.Packages) > 0 {
if len(f.Components)+len(f.Packages) >= 3 {
buf.WriteString(",")
}
buf.WriteString(" and ")
}
// List packages, if any.
if len(f.Packages) > 0 {
buf.WriteString("the ")
}
for i, pkg := range f.Packages {
switch {
case i != 0 && len(f.Packages) == 2:
buf.WriteString(" and ")
case i != 0 && len(f.Packages) >= 3 && i != len(f.Packages)-1:
buf.WriteString(", ")
case i != 0 && len(f.Packages) >= 3 && i == len(f.Packages)-1:
buf.WriteString(", and ")
}
buf.WriteString("<code>" + html.EscapeString(pkg) + "</code>")
}
switch {
case len(f.Packages) == 1:
buf.WriteString(" package")
case len(f.Packages) >= 2:
buf.WriteString(" packages")
}
return template.HTML(buf.String())
}