Files
wrapped/pkg/stats/analyzer.go
T
atridad fec14022cd Initial implementation of Gitea Wrapped TUI
- Gitea API client with repository and commit fetching
- Interactive credential input screen with masked token input
- Statistics analyzer for commits, languages, and activity patterns
- Multi-page report screen with ASCII charts and visualizations
- Integration of all components in main app coordinator
- Comprehensive README with usage instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-01 10:05:57 -06:00

213 lines
4.8 KiB
Go

package stats
import (
"sort"
"time"
"github.com/atridad/gitea-wrapped/pkg/gitea"
"github.com/atridad/gitea-wrapped/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) Generate(username string) *models.UserStats {
stats := &models.UserStats{
Username: username,
TotalCommits: len(a.commits),
TotalRepositories: len(a.repos),
}
stats.Languages = a.generateLanguageStats()
stats.CommitsByWeekday = a.generateWeekdayStats()
stats.CommitsByMonth = a.generateMonthStats()
stats.AverageCommitsPerDay = a.calculateAverageCommitsPerDay()
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(5)
return stats
}
// generateLanguageStats creates language statistics
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
}
// generateWeekdayStats creates weekday statistics for commits
func (a *Analyzer) generateWeekdayStats() []models.DateStats {
dayCount := make(map[time.Weekday]int)
dayNames := [7]string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
for _, commit := range a.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
}
// generateMonthStats creates monthly statistics for commits
func (a *Analyzer) generateMonthStats() []models.DateStats {
monthCount := make(map[string]int)
monthKeys := []string{}
monthMap := make(map[string]time.Time)
for _, commit := range a.commits {
monthStr := commit.Timestamp.Format("2006-01")
monthCount[monthStr]++
if _, exists := monthMap[monthStr]; !exists {
monthKeys = append(monthKeys, monthStr)
monthMap[monthStr] = commit.Timestamp
}
}
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
}
// calculateAverageCommitsPerDay calculates average commits per calendar day
func (a *Analyzer) calculateAverageCommitsPerDay() float64 {
if len(a.commits) == 0 {
return 0
}
if len(a.commits) < 2 {
return float64(len(a.commits))
}
sort.Slice(a.commits, func(i, j int) bool {
return a.commits[i].Timestamp.Before(a.commits[j].Timestamp)
})
firstDay := a.commits[0].Timestamp
lastDay := a.commits[len(a.commits)-1].Timestamp
daysDiff := lastDay.Sub(firstDay).Hours() / 24
if daysDiff == 0 {
daysDiff = 1
}
return float64(len(a.commits)) / daysDiff
}
// getTopRepositories returns the top N repositories by commit count
func (a *Analyzer) getTopRepositories(n int) []models.Repository {
repoCommitCount := make(map[string]int)
repoMap := make(map[string]models.Repository)
for _, commit := range a.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
}