releasenotes_writer.go (5677B)
1 // Copyright 2017-present The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package releaser implements a set of utilities and a wrapper around Goreleaser
15 // to help automate the Hugo release process.
16 package releaser
17
18 import (
19 "bytes"
20 "fmt"
21 "io"
22 "io/ioutil"
23 "net/http"
24 "os"
25 "path/filepath"
26 "strings"
27 "text/template"
28 )
29
30 const (
31 issueLinkTemplate = "#%d"
32 linkTemplate = "[%s](%s)"
33 releaseNotesMarkdownTemplatePatchRelease = `
34 {{ if eq (len .All) 1 }}
35 This is a bug-fix release with one important fix.
36 {{ else }}
37 This is a bug-fix release with a couple of important fixes.
38 {{ end }}
39 {{ range .All }}
40 {{- if .GitHubCommit -}}
41 * {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }}
42 {{ else -}}
43 * {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
44 {{ end -}}
45 {{- end }}
46
47
48 `
49 releaseNotesMarkdownTemplate = `
50 {{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}
51 {{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}}
52
53 This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
54
55 {{- if gt (len $contribsPerAuthor) 3 -}}
56 {{- $u1 := index $contribsPerAuthor 0 -}}
57 {{- $u2 := index $contribsPerAuthor 1 -}}
58 {{- $u3 := index $contribsPerAuthor 2 -}}
59 {{- $u4 := index $contribsPerAuthor 3 -}}
60 {{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
61 And thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his ongoing work on keeping the themes site in pristine condition.
62 {{ end }}
63 Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
64 which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**.
65 {{- if gt (len $docsContribsPerAuthor) 3 -}}
66 {{- $u1 := index $docsContribsPerAuthor 0 -}}
67 {{- $u2 := index $docsContribsPerAuthor 1 -}}
68 {{- $u3 := index $docsContribsPerAuthor 2 -}}
69 {{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site.
70 {{ end }}
71
72 Hugo now has:
73
74 {{ with .Repo -}}
75 * {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
76 * {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
77 {{- end -}}
78 {{ with .ThemeCount }}
79 * {{ . }}+ [themes](http://themes.gohugo.io/)
80 {{ end }}
81 {{ with .Notes }}
82 ## Notes
83 {{ template "change-section" . }}
84 {{- end -}}
85 {{ with .All }}
86 ## Changes
87 {{ template "change-section" . }}
88 {{ end }}
89
90 {{ define "change-section" }}
91 {{ range . }}
92 {{- if .GitHubCommit -}}
93 * {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }}
94 {{ else -}}
95 * {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
96 {{ end -}}
97 {{- end }}
98 {{ end }}
99 `
100 )
101
102 var templateFuncs = template.FuncMap{
103 "isPatch": func(c changeLog) bool {
104 return !strings.HasSuffix(c.Version, "0")
105 },
106 "issue": func(id int) string {
107 return fmt.Sprintf(issueLinkTemplate, id)
108 },
109 "commitURL": func(info gitInfo) string {
110 if info.GitHubCommit.HTMLURL == "" {
111 return ""
112 }
113 return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HTMLURL)
114 },
115 "author": func(info gitInfo) string {
116 return "@" + info.GitHubCommit.Author.Login
117 },
118 }
119
120 func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error {
121 client := newGitHubAPI("hugo")
122 changes := newChangeLog(infosMain, infosDocs)
123 changes.Version = version
124 repo, err := client.fetchRepo()
125 if err == nil {
126 changes.Repo = &repo
127 }
128 themeCount, err := fetchThemeCount()
129 if err == nil {
130 changes.ThemeCount = themeCount
131 }
132
133 mtempl := releaseNotesMarkdownTemplate
134
135 if !strings.HasSuffix(version, "0") {
136 mtempl = releaseNotesMarkdownTemplatePatchRelease
137 }
138
139 tmpl, err := template.New("").Funcs(templateFuncs).Parse(mtempl)
140 if err != nil {
141 return err
142 }
143
144 err = tmpl.Execute(to, changes)
145 if err != nil {
146 return err
147 }
148
149 return nil
150 }
151
152 func fetchThemeCount() (int, error) {
153 resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemesSiteBuilder/main/themes.txt")
154 if err != nil {
155 return 0, err
156 }
157 defer resp.Body.Close()
158
159 b, _ := ioutil.ReadAll(resp.Body)
160 return bytes.Count(b, []byte("\n")) - bytes.Count(b, []byte("#")), nil
161 }
162
163 func getReleaseNotesFilename(version string) string {
164 return filepath.FromSlash(fmt.Sprintf("temp/%s-relnotes-ready.md", version))
165 }
166
167 func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, isPatch bool, infosMain, infosDocs gitInfos) (string, error) {
168 filename := getReleaseNotesFilename(version)
169
170 var w io.WriteCloser
171
172 if !r.try {
173 f, err := os.Create(filename)
174 if err != nil {
175 return "", err
176 }
177
178 defer f.Close()
179
180 w = f
181
182 } else {
183 w = os.Stdout
184 }
185
186 if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil {
187 return "", err
188 }
189
190 return filename, nil
191 }