authors.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright (C) 2015 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build ignore
  7. // Generates the list of contributors in gui/index.html based on contents of
  8. // AUTHORS.
  9. package main
  10. import (
  11. "bytes"
  12. "fmt"
  13. "io/ioutil"
  14. "log"
  15. "math"
  16. "os"
  17. "os/exec"
  18. "regexp"
  19. "sort"
  20. "strings"
  21. )
  22. const htmlFile = "gui/default/syncthing/core/aboutModalView.html"
  23. var (
  24. nicknameRe = regexp.MustCompile(`\(([^\s]*)\)`)
  25. emailRe = regexp.MustCompile(`<([^\s]*)>`)
  26. )
  27. const authorsHeader = `# This is the official list of Syncthing authors for copyright purposes.
  28. # The format is:
  29. #
  30. # Name Name Name (nickname) <[email protected]> <[email protected]>
  31. #
  32. # The NICKS list is auto generated from this file.
  33. `
  34. const nicksHeader = `# This file maps email addresses used in commits to nicks used the changelog.
  35. # It is auto generated from the AUTHORS file by script/authors.go.
  36. `
  37. type author struct {
  38. name string
  39. nickname string
  40. emails []string
  41. commits int
  42. log10commits int
  43. }
  44. func main() {
  45. authors := getAuthors()
  46. getContributions(authors)
  47. sort.Sort(byContributions(authors))
  48. var lines []string
  49. for _, author := range authors {
  50. lines = append(lines, author.name)
  51. }
  52. replacement := strings.Join(lines, ", ")
  53. authorsRe := regexp.MustCompile(`(?s)id="contributor-list">.*?</div>`)
  54. bs := readAll(htmlFile)
  55. bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n </div>"))
  56. if err := ioutil.WriteFile(htmlFile, bs, 0644); err != nil {
  57. log.Fatal(err)
  58. }
  59. // Write AUTHORS file
  60. sort.Sort(byName(authors))
  61. out, err := os.Create("AUTHORS")
  62. if err != nil {
  63. log.Fatal(err)
  64. }
  65. fmt.Fprintf(out, "%s\n", authorsHeader)
  66. for _, author := range authors {
  67. fmt.Fprintf(out, "%s", author.name)
  68. if author.nickname != "" {
  69. fmt.Fprintf(out, " (%s)", author.nickname)
  70. }
  71. for _, email := range author.emails {
  72. fmt.Fprintf(out, " <%s>", email)
  73. }
  74. fmt.Fprintf(out, "\n")
  75. }
  76. out.Close()
  77. // Write NICKS file
  78. sort.Sort(byNick(authors))
  79. out, err = os.Create("NICKS")
  80. if err != nil {
  81. log.Fatal(err)
  82. }
  83. fmt.Fprintf(out, "%s\n", nicksHeader)
  84. for _, author := range authors {
  85. if author.nickname == "" {
  86. continue
  87. }
  88. for _, email := range author.emails {
  89. fmt.Fprintf(out, "%s <%s>\n", author.nickname, email)
  90. }
  91. }
  92. out.Close()
  93. }
  94. func getAuthors() []author {
  95. bs := readAll("AUTHORS")
  96. lines := strings.Split(string(bs), "\n")
  97. var authors []author
  98. for _, line := range lines {
  99. if len(line) == 0 || line[0] == '#' {
  100. continue
  101. }
  102. fields := strings.Fields(line)
  103. var author author
  104. for _, field := range fields {
  105. if m := nicknameRe.FindStringSubmatch(field); len(m) > 1 {
  106. author.nickname = m[1]
  107. } else if m := emailRe.FindStringSubmatch(field); len(m) > 1 {
  108. author.emails = append(author.emails, m[1])
  109. } else {
  110. if author.name == "" {
  111. author.name = field
  112. } else {
  113. author.name = author.name + " " + field
  114. }
  115. }
  116. }
  117. authors = append(authors, author)
  118. }
  119. return authors
  120. }
  121. func readAll(path string) []byte {
  122. fd, err := os.Open(path)
  123. if err != nil {
  124. log.Fatal(err)
  125. }
  126. defer fd.Close()
  127. bs, err := ioutil.ReadAll(fd)
  128. if err != nil {
  129. log.Fatal(err)
  130. }
  131. return bs
  132. }
  133. // Add number of commits per author to the author list.
  134. func getContributions(authors []author) {
  135. buf := new(bytes.Buffer)
  136. cmd := exec.Command("git", "log", "--pretty=format:%ae")
  137. cmd.Stdout = buf
  138. err := cmd.Run()
  139. if err != nil {
  140. log.Fatal(err)
  141. }
  142. next:
  143. for _, line := range strings.Split(buf.String(), "\n") {
  144. for i := range authors {
  145. for _, email := range authors[i].emails {
  146. if email == line {
  147. authors[i].commits++
  148. continue next
  149. }
  150. }
  151. }
  152. }
  153. for i := range authors {
  154. authors[i].log10commits = int(math.Log10(float64(authors[i].commits + 1)))
  155. }
  156. }
  157. type byContributions []author
  158. func (l byContributions) Len() int { return len(l) }
  159. // Sort first by log10(commits), then by name. This means that we first get
  160. // an alphabetic list of people with >= 1000 commits, then a list of people
  161. // with >= 100 commits, and so on.
  162. func (l byContributions) Less(a, b int) bool {
  163. if l[a].log10commits != l[b].log10commits {
  164. return l[a].log10commits > l[b].log10commits
  165. }
  166. return l[a].name < l[b].name
  167. }
  168. func (l byContributions) Swap(a, b int) { l[a], l[b] = l[b], l[a] }
  169. type byName []author
  170. func (l byName) Len() int { return len(l) }
  171. func (l byName) Less(a, b int) bool {
  172. aname := strings.ToLower(l[a].name)
  173. bname := strings.ToLower(l[b].name)
  174. return aname < bname
  175. }
  176. func (l byName) Swap(a, b int) { l[a], l[b] = l[b], l[a] }
  177. type byNick []author
  178. func (l byNick) Len() int { return len(l) }
  179. func (l byNick) Less(a, b int) bool {
  180. anick := strings.ToLower(l[a].nickname)
  181. bnick := strings.ToLower(l[b].nickname)
  182. return anick < bnick
  183. }
  184. func (l byNick) Swap(a, b int) { l[a], l[b] = l[b], l[a] }