|
package main |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"sort" |
|
"time" |
|
|
|
"gopkg.in/src-d/go-git.v4" |
|
"gopkg.in/src-d/go-git.v4/plumbing/object" |
|
) |
|
|
|
// Calculates and prints the stats. |
|
func stats(email string) { |
|
commits := calcCommitsStats(email) |
|
table := fillTable(commits) |
|
printTable(table) |
|
} |
|
|
|
// Given an user email, returns the commits made in the last 6 months. |
|
func calcCommitsStats(email string) map[int]int { |
|
|
|
filePath := getReposFilePath() |
|
repos := parseRepos(filePath) |
|
totalDays := calcDaysInLastSixMonths() |
|
|
|
commits := make(map[int]int, totalDays) |
|
for i := 0; i < totalDays; i++ { |
|
commits[i] = 0 |
|
} |
|
|
|
for _, path := range repos { |
|
commits = fillCommits(email, path, commits) |
|
} |
|
|
|
return commits |
|
} |
|
|
|
func calcDaysInLastSixMonths() int { |
|
// 获取当前日期 |
|
currentDate := time.Now() |
|
currentYear, currentMonth, currentDay := currentDate.Date() |
|
|
|
// 计算总天数 |
|
totalDays := 0 |
|
for i := 0; i < 6; i++ { |
|
// 计算月份 |
|
month := int(currentMonth) - i |
|
year := currentYear |
|
|
|
// 如果月份小于1,则年份减1,月份加上12 |
|
if month < 1 { |
|
month += 12 |
|
year-- |
|
} |
|
|
|
// 计算该月的天数 |
|
daysInMonth := daysInMonth(year, int(month)) |
|
|
|
// 如果是当前月,则只计算到当前日期的天数 |
|
if i == 0 { |
|
daysInMonth = currentDay |
|
} |
|
|
|
// 累加天数 |
|
totalDays += daysInMonth |
|
} |
|
|
|
return totalDays |
|
} |
|
|
|
func daysInMonth(year int, month int) int { |
|
daysPerMonth := [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} |
|
|
|
if month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { |
|
return 29 |
|
} |
|
|
|
return daysPerMonth[month - 1] |
|
} |
|
|
|
// Counts commits by email and daysAgo in the repo. |
|
func fillCommits(email string, path string, commits map[int]int) map[int]int { |
|
// instantiate a git repo object from path |
|
repo, err := git.PlainOpen(path) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
// get the HEAD reference |
|
ref, err := repo.Head() |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
// get the commits history starting from HEAD |
|
iterator, err := repo.Log(&git.LogOptions{From: ref.Hash()}) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
// iterate the commits |
|
err = iterator.ForEach(func(c *object.Commit) error { |
|
if c.Author.Email != email { |
|
return nil |
|
} |
|
|
|
days, err := countDaysSinceDate(c.Author.When) |
|
if err == nil { |
|
commits[days]++ |
|
} |
|
|
|
return nil |
|
}) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
return commits |
|
} |
|
|
|
// Counts how many days passed since the passed `date` |
|
func countDaysSinceDate(date time.Time) (int, error) { |
|
days := 0 |
|
daysInLastSixMonths := calcDaysInLastSixMonths() |
|
nowBegin := getBeginningOfDay(time.Now()) |
|
for date.Before(nowBegin) { |
|
date = date.Add(time.Hour * 24) |
|
days++ |
|
if days >= daysInLastSixMonths { |
|
return -1, errors.New("日期超出统计范围") |
|
} |
|
} |
|
return days, nil |
|
} |
|
|
|
// Given a time.Time, calculates the start time of that day. |
|
func getBeginningOfDay(t time.Time) time.Time { |
|
year, month, day := t.Date() |
|
startOfDay := time.Date(year, month, day, 0, 0, 0, 0, t.Location()) |
|
return startOfDay |
|
} |
|
|
|
type column []int |
|
|
|
func fillTable(commits map[int]int) map[int]column { |
|
totalWeeks := calcWeeksInLastSixMonths() |
|
table := make(map[int]column, totalWeeks) |
|
for i := 0; i < totalWeeks; i++ { |
|
table[i] = make(column, 7) |
|
for j := 0; j < 7; j++ { |
|
table[i][j] = 0 |
|
} |
|
} |
|
|
|
keys := sortKeys(commits) |
|
for daysAgo := range keys { |
|
weekId := calcWeekId(daysAgo, totalWeeks) |
|
weekday := calcWeekday(daysAgo) |
|
table[weekId][weekday] = commits[daysAgo] |
|
} |
|
|
|
return table |
|
} |
|
|
|
func calcWeeksInLastSixMonths() int { |
|
currentWeekday := time.Now().Weekday() |
|
weeksInLastSixMonths := 1 // 当前日期占1周 |
|
|
|
totalDays := calcDaysInLastSixMonths() |
|
weeksInLastSixMonths += (totalDays - int(currentWeekday)) / 7 |
|
|
|
if (totalDays - int(currentWeekday)) % 7 > 0 { |
|
weeksInLastSixMonths++; |
|
} |
|
|
|
return weeksInLastSixMonths |
|
} |
|
|
|
// Returns ordered indexes of the map. |
|
func sortKeys(m map[int]int) []int { |
|
var keys []int |
|
for k := range m { |
|
keys = append(keys, k) |
|
} |
|
sort.Ints(keys) |
|
|
|
return keys |
|
} |
|
|
|
func calcWeekId(daysAgo int, totalWeeks int) int { |
|
currentWeekday := time.Now().Weekday() |
|
if daysAgo < int(currentWeekday) { |
|
return totalWeeks - 1 |
|
} else { |
|
daysAgo -= int(currentWeekday) |
|
weeksToCurrent := daysAgo / 7 |
|
if daysAgo % 7 > 0 { |
|
weeksToCurrent++ |
|
} |
|
return totalWeeks - 1 - weeksToCurrent |
|
} |
|
} |
|
|
|
func calcWeekday(daysAgo int) int { |
|
currentWeekday := time.Now().Weekday() |
|
if daysAgo <= int(currentWeekday) { |
|
return int(currentWeekday) - daysAgo |
|
} else { |
|
reverseWeekday := (daysAgo - int(currentWeekday) - 1) % 7 |
|
return 6 - reverseWeekday |
|
} |
|
} |
|
|
|
func printTable(table map[int]column) { |
|
totalWeeks := calcWeeksInLastSixMonths() |
|
currentWeekday := time.Now().Weekday() |
|
|
|
printMonths() |
|
for weekday := 6; weekday >= 0; weekday-- { |
|
printWeekdayName(weekday) |
|
for weekId := 0; weekId < totalWeeks; weekId++ { |
|
if weekday == int(currentWeekday) && weekId == totalWeeks - 1 { |
|
printCell(table[weekId][weekday], true) |
|
} else { |
|
printCell(table[weekId][weekday], false) |
|
} |
|
} |
|
fmt.Printf("\n") |
|
} |
|
} |
|
|
|
// Prints the last 6 month names in the first line |
|
func printMonths() { |
|
totalDays := calcDaysInLastSixMonths() |
|
currentWeekday := time.Now().Weekday() |
|
offset := 7 - ((totalDays - int(currentWeekday)) % 7) |
|
date := getBeginningOfDay(time.Now()).Add(-(time.Duration(totalDays + offset - 1) * time.Hour * 24)) |
|
realDate := getBeginningOfDay(time.Now()).Add(-(time.Duration(totalDays - 1) * time.Hour * 24)) |
|
month := realDate.Month() |
|
fmt.Printf(" ") |
|
fmt.Printf("%s ", date.Month().String()[:3]) |
|
date = date.Add(7 * time.Hour * 24) |
|
for { |
|
if date.Month() != month { |
|
fmt.Printf("%s ", date.Month().String()[:3]) |
|
month = date.Month() |
|
} else { |
|
fmt.Printf(" ") |
|
} |
|
|
|
date = date.Add(7 * time.Hour * 24) |
|
if date.After(time.Now()) { |
|
break |
|
} |
|
} |
|
fmt.Printf("\n") |
|
} |
|
|
|
// Given the day number (0 is Sunday), prints the day name. |
|
// alternating the rows (prints just 2,4,6) |
|
func printWeekdayName(day int) { |
|
out := " " |
|
switch day { |
|
case 1: |
|
out = " Mon " |
|
case 3: |
|
out = " Wed " |
|
case 5: |
|
out = " Fri " |
|
} |
|
|
|
fmt.Print(out) |
|
} |
|
|
|
// Given a cell value prints it with a different format |
|
// based on the value amount, and on the `today` flag. |
|
func printCell(val int, today bool) { |
|
escape := "\033[0;37;30m" |
|
switch { |
|
case val > 0 && val < 5: |
|
escape = "\033[1;30;47m" |
|
case val >= 5 && val < 10: |
|
escape = "\033[1;30;43m" |
|
case val >= 10: |
|
escape = "\033[1;30;42m" |
|
} |
|
|
|
if today { |
|
escape = "\033[1;37;45m" |
|
} |
|
|
|
if val == 0 { |
|
fmt.Printf(escape + " - " + "\033[0m") |
|
return |
|
} |
|
|
|
str := " %d " |
|
switch { |
|
case val >= 10: |
|
str = " %d " |
|
case val >= 100: |
|
str = "%d " |
|
} |
|
|
|
fmt.Printf(escape + str + "\033[0m", val) |
|
} |
go version 1.22.5