syncthing/script/authors.go
Jakob Borg d67c0a1eda authors: Clean up AUTHORS and NICKS files
Git didn't really understand the multiple email addresses in the NICKS
file the same way I expected it to, and this fixes that. It also makes
AUTHORS the "master" file that everything else depends on, so it
now includes all of name, nickname and email addresses.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3243
2016-06-02 08:19:12 +00:00

226 lines
4.9 KiB
Go

// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build ignore
// Generates the list of contributors in gui/index.html based on contents of
// AUTHORS.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"math"
"os"
"os/exec"
"regexp"
"sort"
"strings"
)
const htmlFile = "gui/default/syncthing/core/aboutModalView.html"
var (
nicknameRe = regexp.MustCompile(`\(([^\s]*)\)`)
emailRe = regexp.MustCompile(`<([^\s]*)>`)
)
const authorsHeader = `# This is the official list of Syncthing authors for copyright purposes.
# The format is:
#
# Name Name Name (nickname) <email1@example.com> <email2@example.com>
#
# The NICKS list is auto generated from this file.
`
const nicksHeader = `# This file maps email addresses used in commits to nicks used the changelog.
# It is auto generated from the AUTHORS file by script/authors.go.
`
type author struct {
name string
nickname string
emails []string
commits int
log10commits int
}
func main() {
authors := getAuthors()
getContributions(authors)
sort.Sort(byContributions(authors))
var lines []string
for _, author := range authors {
lines = append(lines, author.name)
}
replacement := strings.Join(lines, ", ")
authorsRe := regexp.MustCompile(`(?s)id="contributor-list">.*?</div>`)
bs := readAll(htmlFile)
bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n </div>"))
if err := ioutil.WriteFile(htmlFile, bs, 0644); err != nil {
log.Fatal(err)
}
// Write AUTHORS file
sort.Sort(byName(authors))
out, err := os.Create("AUTHORS")
if err != nil {
log.Fatal(err)
}
fmt.Fprintf(out, "%s\n", authorsHeader)
for _, author := range authors {
fmt.Fprintf(out, "%s", author.name)
if author.nickname != "" {
fmt.Fprintf(out, " (%s)", author.nickname)
}
for _, email := range author.emails {
fmt.Fprintf(out, " <%s>", email)
}
fmt.Fprintf(out, "\n")
}
out.Close()
// Write NICKS file
sort.Sort(byNick(authors))
out, err = os.Create("NICKS")
if err != nil {
log.Fatal(err)
}
fmt.Fprintf(out, "%s\n", nicksHeader)
for _, author := range authors {
if author.nickname == "" {
continue
}
for _, email := range author.emails {
fmt.Fprintf(out, "%s <%s>\n", author.nickname, email)
}
}
out.Close()
}
func getAuthors() []author {
bs := readAll("AUTHORS")
lines := strings.Split(string(bs), "\n")
var authors []author
for _, line := range lines {
if len(line) == 0 || line[0] == '#' {
continue
}
fields := strings.Fields(line)
var author author
for _, field := range fields {
if m := nicknameRe.FindStringSubmatch(field); len(m) > 1 {
author.nickname = m[1]
} else if m := emailRe.FindStringSubmatch(field); len(m) > 1 {
author.emails = append(author.emails, m[1])
} else {
if author.name == "" {
author.name = field
} else {
author.name = author.name + " " + field
}
}
}
authors = append(authors, author)
}
return authors
}
func readAll(path string) []byte {
fd, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
bs, err := ioutil.ReadAll(fd)
if err != nil {
log.Fatal(err)
}
return bs
}
// Add number of commits per author to the author list.
func getContributions(authors []author) {
buf := new(bytes.Buffer)
cmd := exec.Command("git", "log", "--pretty=format:%ae")
cmd.Stdout = buf
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
next:
for _, line := range strings.Split(buf.String(), "\n") {
for i := range authors {
for _, email := range authors[i].emails {
if email == line {
authors[i].commits++
continue next
}
}
}
}
for i := range authors {
authors[i].log10commits = int(math.Log10(float64(authors[i].commits + 1)))
}
}
type byContributions []author
func (l byContributions) Len() int { return len(l) }
// Sort first by log10(commits), then by name. This means that we first get
// an alphabetic list of people with >= 1000 commits, then a list of people
// with >= 100 commits, and so on.
func (l byContributions) Less(a, b int) bool {
if l[a].log10commits != l[b].log10commits {
return l[a].log10commits > l[b].log10commits
}
return l[a].name < l[b].name
}
func (l byContributions) Swap(a, b int) { l[a], l[b] = l[b], l[a] }
type byName []author
func (l byName) Len() int { return len(l) }
func (l byName) Less(a, b int) bool {
aname := strings.ToLower(l[a].name)
bname := strings.ToLower(l[b].name)
return aname < bname
}
func (l byName) Swap(a, b int) { l[a], l[b] = l[b], l[a] }
type byNick []author
func (l byNick) Len() int { return len(l) }
func (l byNick) Less(a, b int) bool {
anick := strings.ToLower(l[a].nickname)
bnick := strings.ToLower(l[b].nickname)
return anick < bnick
}
func (l byNick) Swap(a, b int) { l[a], l[b] = l[b], l[a] }