239 lines
5.1 KiB
Go
239 lines
5.1 KiB
Go
package stats
|
|
|
|
import (
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/atridad/wrapped-cli/pkg/gitea"
|
|
"github.com/atridad/wrapped-cli/pkg/models"
|
|
)
|
|
|
|
type Analyzer struct {
|
|
repos []gitea.GiteaRepo
|
|
commits []models.Commit
|
|
}
|
|
|
|
func NewAnalyzer() *Analyzer {
|
|
return &Analyzer{
|
|
repos: []gitea.GiteaRepo{},
|
|
commits: []models.Commit{},
|
|
}
|
|
}
|
|
|
|
func (a *Analyzer) AddRepos(repos []gitea.GiteaRepo) {
|
|
a.repos = repos
|
|
}
|
|
|
|
func (a *Analyzer) AddCommits(commits []models.Commit) {
|
|
a.commits = append(a.commits, commits...)
|
|
}
|
|
|
|
func (a *Analyzer) inferPrimaryEmail(username string) string {
|
|
emailCount := make(map[string]int)
|
|
|
|
for _, c := range a.commits {
|
|
if c.AuthorEmail != "" {
|
|
emailCount[c.AuthorEmail]++
|
|
}
|
|
}
|
|
|
|
var bestEmail string
|
|
max := 0
|
|
|
|
for email, count := range emailCount {
|
|
if count > max {
|
|
max = count
|
|
bestEmail = email
|
|
}
|
|
}
|
|
|
|
return bestEmail
|
|
}
|
|
|
|
func (a *Analyzer) Generate(username string) *models.UserStats {
|
|
primaryEmail := a.inferPrimaryEmail(username)
|
|
|
|
var filteredCommits []models.Commit
|
|
for _, commit := range a.commits {
|
|
if primaryEmail != "" && commit.AuthorEmail == primaryEmail {
|
|
filteredCommits = append(filteredCommits, commit)
|
|
}
|
|
}
|
|
|
|
stats := &models.UserStats{
|
|
Username: username,
|
|
TotalCommits: len(filteredCommits),
|
|
TotalRepositories: len(a.repos),
|
|
}
|
|
|
|
stats.Languages = a.generateLanguageStats()
|
|
stats.CommitsByWeekday = a.generateWeekdayStats(filteredCommits)
|
|
stats.CommitsByMonth = a.generateMonthStats(filteredCommits)
|
|
stats.AverageCommitsPerDay = a.calculateAverageCommitsPerDay(filteredCommits)
|
|
|
|
if len(stats.CommitsByMonth) > 0 {
|
|
stats.MostActiveMonth = stats.CommitsByMonth[0].Date.Format("January")
|
|
}
|
|
if len(stats.CommitsByWeekday) > 0 {
|
|
stats.MostActiveDay = stats.CommitsByWeekday[0].Day
|
|
}
|
|
|
|
stats.TopRepositories = a.getTopRepositories(filteredCommits, 5)
|
|
|
|
return stats
|
|
}
|
|
|
|
func (a *Analyzer) generateLanguageStats() []models.LanguageStats {
|
|
langCount := make(map[string]int)
|
|
|
|
for _, repo := range a.repos {
|
|
if repo.Language != "" {
|
|
langCount[repo.Language]++
|
|
}
|
|
}
|
|
|
|
var stats []models.LanguageStats
|
|
for lang, count := range langCount {
|
|
stats = append(stats, models.LanguageStats{
|
|
Language: lang,
|
|
Count: count,
|
|
})
|
|
}
|
|
|
|
sort.Slice(stats, func(i, j int) bool {
|
|
return stats[i].Count > stats[j].Count
|
|
})
|
|
|
|
total := len(a.repos)
|
|
for i := range stats {
|
|
stats[i].Percent = float64(stats[i].Count) / float64(total) * 100
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
func (a *Analyzer) generateWeekdayStats(commits []models.Commit) []models.DateStats {
|
|
dayCount := make(map[time.Weekday]int)
|
|
dayNames := [7]string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
|
|
|
|
for _, commit := range commits {
|
|
day := commit.Timestamp.Weekday()
|
|
dayCount[day]++
|
|
}
|
|
|
|
var stats []models.DateStats
|
|
for i := 0; i < 7; i++ {
|
|
day := time.Weekday(i)
|
|
stats = append(stats, models.DateStats{
|
|
Day: dayNames[i],
|
|
Count: dayCount[day],
|
|
})
|
|
}
|
|
|
|
sort.Slice(stats, func(i, j int) bool {
|
|
return stats[i].Count > stats[j].Count
|
|
})
|
|
|
|
return stats
|
|
}
|
|
|
|
func (a *Analyzer) generateMonthStats(commits []models.Commit) []models.DateStats {
|
|
monthCount := make(map[string]int)
|
|
monthKeys := []string{}
|
|
|
|
for _, commit := range commits {
|
|
monthStr := commit.Timestamp.Format("2006-01")
|
|
monthCount[monthStr]++
|
|
}
|
|
|
|
for k := range monthCount {
|
|
monthKeys = append(monthKeys, k)
|
|
}
|
|
|
|
var stats []models.DateStats
|
|
for _, monthStr := range monthKeys {
|
|
t, _ := time.Parse("2006-01", monthStr)
|
|
stats = append(stats, models.DateStats{
|
|
Date: t,
|
|
Count: monthCount[monthStr],
|
|
})
|
|
}
|
|
|
|
sort.Slice(stats, func(i, j int) bool {
|
|
return stats[i].Count > stats[j].Count
|
|
})
|
|
|
|
return stats
|
|
}
|
|
|
|
func (a *Analyzer) calculateAverageCommitsPerDay(commits []models.Commit) float64 {
|
|
if len(commits) == 0 {
|
|
return 0
|
|
}
|
|
|
|
if len(commits) < 2 {
|
|
return float64(len(commits))
|
|
}
|
|
|
|
sort.Slice(commits, func(i, j int) bool {
|
|
return commits[i].Timestamp.Before(commits[j].Timestamp)
|
|
})
|
|
|
|
firstDay := commits[0].Timestamp
|
|
lastDay := commits[len(commits)-1].Timestamp
|
|
|
|
daysDiff := lastDay.Sub(firstDay).Hours() / 24
|
|
if daysDiff == 0 {
|
|
daysDiff = 1
|
|
}
|
|
|
|
return float64(len(commits)) / daysDiff
|
|
}
|
|
|
|
func (a *Analyzer) getTopRepositories(commits []models.Commit, n int) []models.Repository {
|
|
repoCommitCount := make(map[string]int)
|
|
repoMap := make(map[string]models.Repository)
|
|
|
|
for _, commit := range commits {
|
|
repoCommitCount[commit.RepoName]++
|
|
}
|
|
|
|
for _, repo := range a.repos {
|
|
repoMap[repo.Name] = models.Repository{
|
|
ID: repo.ID,
|
|
Name: repo.Name,
|
|
FullName: repo.FullName,
|
|
HTMLURL: repo.HTMLURL,
|
|
Language: repo.Language,
|
|
Topics: repo.Topics,
|
|
}
|
|
}
|
|
|
|
type repoWithCount struct {
|
|
repo models.Repository
|
|
count int
|
|
}
|
|
|
|
var repos []repoWithCount
|
|
for name, count := range repoCommitCount {
|
|
if repo, exists := repoMap[name]; exists {
|
|
repos = append(repos, repoWithCount{repo, count})
|
|
}
|
|
}
|
|
|
|
sort.Slice(repos, func(i, j int) bool {
|
|
return repos[i].count > repos[j].count
|
|
})
|
|
|
|
if len(repos) > n {
|
|
repos = repos[:n]
|
|
}
|
|
|
|
result := make([]models.Repository, len(repos))
|
|
for i, rc := range repos {
|
|
result[i] = rc.repo
|
|
}
|
|
|
|
return result
|
|
}
|