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 }