github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/website/scripts/gencontrib/gencontrib.go (about) 1 // The gencontrib binary generates an HTML list of git repository contributers. 2 package main 3 4 import ( 5 "bufio" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "html/template" 10 "io" 11 "log" 12 "os" 13 "os/exec" 14 "sort" 15 "strconv" 16 "strings" 17 ) 18 19 var contribStr = `<h1>Contributors</h1> 20 21 <p>Camlistore contributors include:</p> 22 23 <ul> 24 {{range .}}<li>{{if .URL}}<a href="{{.URL}}">{{index .Names 0}}</a>{{else}}{{index .Names 0}}{{end}}</li> 25 {{end}} 26 </ul> 27 28 <p>Want to help? See <a href="/docs/contributing">contributing</a>.</p> 29 ` 30 31 var urlsFile = flag.String("urls", "", "email → url map file") 32 33 func addURLs(idx map[string]*author) { 34 if *urlsFile == "" { 35 return 36 } 37 38 f, err := os.Open(*urlsFile) 39 if err != nil { 40 log.Fatal("couldn't open urls file:", *urlsFile) 41 } 42 43 dec := json.NewDecoder(f) 44 var mapping map[string]interface{} 45 err = dec.Decode(&mapping) 46 if err != nil { 47 log.Fatal("couldn't parse urls file:", err) 48 } 49 50 for email, url := range mapping { 51 a := idx[email] 52 if a != nil { 53 a.URL = url.(string) 54 } else { 55 log.Printf("email %v is not a commiter", email) 56 } 57 } 58 } 59 60 type author struct { 61 Names []string 62 Emails []string 63 Commits int 64 URL string 65 } 66 67 func (a *author) add(src *author) { 68 if src == nil { 69 return 70 } 71 a.Emails = append(a.Emails, src.Emails...) 72 a.Names = append(a.Names, src.Names...) 73 a.Commits += src.Commits 74 } 75 76 type Authors []*author 77 78 func (s Authors) Len() int { return len(s) } 79 func (s Authors) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 80 func (s Authors) Less(i, j int) bool { return s[i].Commits > s[j].Commits } 81 82 func parseLine(l string) (name, email string, commits int, err error) { 83 t := strings.Split(strings.TrimSpace(l), " ") 84 if len(t) < 2 { 85 err = fmt.Errorf("line too short") 86 return 87 } 88 i := strings.LastIndex(t[1], " ") 89 email = strings.Trim(t[1][i+1:], "<>") 90 name = t[1][:i] 91 commits, err = strconv.Atoi(t[0]) 92 return 93 } 94 95 func shortlog() io.Reader { 96 gitlog := exec.Command("git", "log") 97 gitlogOut, err := gitlog.StdoutPipe() 98 if err != nil { 99 log.Fatal(err) 100 } 101 gitlog.Start() 102 if err != nil { 103 log.Fatal("couldn't run git log:", err) 104 } 105 106 shortlog := exec.Command("git", "shortlog", "-sen") 107 shortlog.Stdin = gitlogOut 108 shortlogOut, err := shortlog.StdoutPipe() 109 if err != nil { 110 log.Fatal(err) 111 } 112 shortlog.Start() 113 if err != nil { 114 log.Fatal("couldn't run git shortlog:", err) 115 } 116 117 return shortlogOut 118 } 119 120 func main() { 121 flag.Parse() 122 contribHtml, err := template.New("contrib").Parse(contribStr) 123 if err != nil { 124 log.Fatal("couldn't parse template") 125 } 126 127 byName := make(map[string]*author) 128 byEmail := make(map[string]*author) 129 authorMap := make(map[*author]bool) 130 131 sl := shortlog() 132 133 scn := bufio.NewScanner(sl) 134 for scn.Scan() { 135 name, email, commits, err := parseLine(scn.Text()) 136 if err != nil { 137 log.Fatalf("couldn't parse line \"%v\": %v", scn.Text(), err) 138 } 139 140 a := &author{ 141 Emails: []string{email}, 142 Names: []string{name}, 143 Commits: commits, 144 } 145 146 a.add(byName[name]) 147 a.add(byEmail[email]) 148 for _, n := range a.Names { 149 delete(authorMap, byName[n]) 150 byName[n] = a 151 } 152 for _, e := range a.Emails { 153 delete(authorMap, byEmail[e]) 154 byEmail[e] = a 155 } 156 authorMap[a] = true 157 } 158 if scn.Err() != nil { 159 log.Fatal(err) 160 } 161 162 addURLs(byEmail) 163 164 authors := Authors{} 165 for a, _ := range authorMap { 166 authors = append(authors, a) 167 } 168 169 sort.Sort(authors) 170 171 if err := contribHtml.Execute(os.Stdout, authors); err != nil { 172 log.Fatalf("executing template: %v", err) 173 } 174 }