Skip to content

Commit

Permalink
feat(stats): add repository filtering to stats command
Browse files Browse the repository at this point in the history
Add ability to filter stats for a specific repository using the stats command.
This allows users to focus on activity metrics for a single repository.

Changes:
- Update stats command to accept optional repository argument
- Add repository filtering logic in DisplayStats and related functions
- Update CLI help text and documentation with new feature
- Add comprehensive tests for repository filtering functionality

Example usage:
  streakode stats myproject

Breaking changes: none
  • Loading branch information
AccursedGalaxy committed Dec 30, 2024
1 parent a761d58 commit 7373467
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 137 deletions.
3 changes: 0 additions & 3 deletions FEATURE_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ This document outlines new and additional features that are planned or already i
---

## IDEAS
- add optional "repository" argument to stats command
-> i.e.: streakode stats streakode -> to get more detailed stats about just the streakode repository

- add optional "author" argument to the author command
-> i.e.: streakode author AccursedGalaxy -> to get detailed stats about this author (currently just shows author set from the config to verify it's setup correctly, but can easily expand this to show detailed stats abou the author as well)

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,12 @@ streakode version
# Check your Git author configuration
streakode author

# Show statistics
# Show statistics for all repositories
streakode stats

# Show statistics for a specific repository
streakode stats myproject

# Show statistics with debug output
streakode stats --debug
# or
Expand Down
211 changes: 123 additions & 88 deletions cmd/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,44 @@ import (
)

type repoInfo struct {
name string
metadata scan.RepoMetadata
lastCommit time.Time
name string
metadata scan.RepoMetadata
lastCommit time.Time
}

type CommitTrend struct {
indicator string
text string
indicator string
text string
}

type LanguageStats map[string]int
type HourStats map[int]int

const (
defaultTerminalWidth = 80
maxTableWidth = 120
hoursInDay = 24
daysInWeek = 7
defaultTerminalWidth = 80
maxTableWidth = 120
hoursInDay = 24
daysInWeek = 7
)

var calculator = &DefaultStatsCalculator{}

func (c *DefaultStatsCalculator) CalculateCommitTrend(current int, previous int) CommitTrend {
diff := current - previous
switch {
case diff > 0:
return CommitTrend{"↗️", fmt.Sprintf("up %d", diff)}
case diff < 0:
return CommitTrend{"↘️", fmt.Sprintf("down %d", -diff)}
default:
return CommitTrend{"-", ""}
}
diff := current - previous
switch {
case diff > 0:
return CommitTrend{"↗️", fmt.Sprintf("up %d", diff)}
case diff < 0:
return CommitTrend{"↘️", fmt.Sprintf("down %d", -diff)}
default:
return CommitTrend{"-", ""}
}
}

// DisplayStats - Displays stats for all active repositories in a more compact format
func DisplayStats() {
// DisplayStats - Displays stats for all active repositories or a specific repository
func DisplayStats(targetRepo string) {
// Get table width from the rendered table first
projectsSection := buildProjectsSection()
projectsSection := buildProjectsSection(targetRepo)
tableLines := strings.Split(projectsSection, "\n")
if len(tableLines) == 0 {
return
Expand All @@ -76,20 +76,22 @@ func DisplayStats() {
// Header section
if config.AppConfig.DisplayStats.ShowWelcomeMessage {
headerText := fmt.Sprintf("🚀 %s's Coding Activity", config.AppConfig.Author)
if targetRepo != "" {
headerText = fmt.Sprintf("🚀 %s's Activity in %s", config.AppConfig.Author, targetRepo)
}
padding := (tableWidth - len([]rune(headerText))) / 2
centeredHeader := fmt.Sprintf("%*s%s%*s", padding, "", headerText, padding, "")
sections = append(sections, headerStyle.Render(centeredHeader))
}


// Active projects section (table)
if config.AppConfig.DisplayStats.ShowActiveProjects && projectsSection != "" {
sections = append(sections, projectsSection)
}

// Insights section
if config.AppConfig.DisplayStats.ShowInsights {
insights := buildInsightsSection()
insights := buildInsightsSection(targetRepo)
if insights != "" {
sections = append(sections, insights)
}
Expand Down Expand Up @@ -124,11 +126,11 @@ func DisplayStats() {
}

func (c *DefaultStatsCalculator) CalculateTableWidth() int {
width, _, err := term.GetSize(0)
if err != nil {
width = defaultTerminalWidth
}
return min(width-2, maxTableWidth)
width, _, err := term.GetSize(0)
if err != nil {
width = defaultTerminalWidth
}
return min(width-2, maxTableWidth)
}

// prepareRepoData converts the cache map into a sorted slice of repository information
Expand All @@ -154,7 +156,7 @@ func prepareRepoData() []repoInfo {
// initializeTable creates and configures a new table writer with proper settings
func initializeTable(tableWidth int) table.Writer {
t := table.NewWriter()

// Configure table column widths
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, WidthMax: int(float64(tableWidth) * 0.35)}, // Repository name
Expand Down Expand Up @@ -198,28 +200,28 @@ func formatActivityIndicator(weeklyCommits int) string {
func formatStreakString(currentStreak, longestStreak int) string {
indicators := config.AppConfig.DisplayStats.ActivityIndicators
streakStr := fmt.Sprintf("%dd", currentStreak)

if currentStreak == longestStreak && currentStreak > 0 {
streakStr += indicators.StreakRecord
} else if currentStreak > 0 {
streakStr += indicators.ActiveStreak
}

return streakStr
}

// calculateWeeklyChanges calculates total additions and deletions for the week
func calculateWeeklyChanges(commitHistory []scan.CommitHistory) (int, int) {
var weeklyAdditions, weeklyDeletions int
weekStart := time.Now().AddDate(0, 0, -daysInWeek)

for _, commit := range commitHistory {
if commit.Date.After(weekStart) {
weeklyAdditions += commit.Additions
weeklyDeletions += commit.Deletions
}
}

return weeklyAdditions, weeklyDeletions
}

Expand All @@ -231,18 +233,34 @@ func formatLastActivity(lastCommit time.Time) string {
return "today"
}

// buildProjectsSection - Displays stats for all active repositories in a more compact format
func buildProjectsSection() string {
// buildProjectsSection - Displays stats for all active repositories or a specific repository
func buildProjectsSection(targetRepo string) string {
if !config.AppConfig.DisplayStats.ShowActiveProjects {
return ""
}

// Create buffer for table
buf := new(bytes.Buffer)

// Get sorted repo data
repos := prepareRepoData()


// Filter for target repository if specified
if targetRepo != "" {
filteredRepos := make([]repoInfo, 0)
for _, repo := range repos {
if repo.name == targetRepo {
filteredRepos = append(filteredRepos, repo)
break
}
}
if len(filteredRepos) == 0 {
fmt.Printf("Repository '%s' not found in cache. Run 'streakode cache reload' to update cache.\n", targetRepo)
return ""
}
repos = filteredRepos
}

// Initialize table with proper width
tableWidth := calculator.CalculateTableWidth()
t := initializeTable(tableWidth)
Expand Down Expand Up @@ -290,21 +308,21 @@ func buildProjectsSection() string {
func formatLanguages(stats map[string]int, topCount int) string {
// Language icons mapping with more descriptive emojis
languageIcons := map[string]string{
"go": config.AppConfig.LanguageSettings.LanguageDisplay.GoDisplay,
"py": config.AppConfig.LanguageSettings.LanguageDisplay.PythonDisplay,
"lua": config.AppConfig.LanguageSettings.LanguageDisplay.LuaDisplay,
"js": config.AppConfig.LanguageSettings.LanguageDisplay.JavaDisplay,
"ts": config.AppConfig.LanguageSettings.LanguageDisplay.TypeScriptDisplay,
"rust": config.AppConfig.LanguageSettings.LanguageDisplay.RustDisplay,
"cpp": config.AppConfig.LanguageSettings.LanguageDisplay.CppDisplay,
"c": config.AppConfig.LanguageSettings.LanguageDisplay.CDisplay,
"java": config.AppConfig.LanguageSettings.LanguageDisplay.JavaDisplay,
"ruby": config.AppConfig.LanguageSettings.LanguageDisplay.RubyDisplay,
"php": config.AppConfig.LanguageSettings.LanguageDisplay.PHPDisplay,
"html": config.AppConfig.LanguageSettings.LanguageDisplay.HTMLDisplay,
"css": config.AppConfig.LanguageSettings.LanguageDisplay.CSSDisplay,
"shell": config.AppConfig.LanguageSettings.LanguageDisplay.ShellDisplay,
"default": config.AppConfig.LanguageSettings.LanguageDisplay.DefaultDisplay,
"go": config.AppConfig.LanguageSettings.LanguageDisplay.GoDisplay,
"py": config.AppConfig.LanguageSettings.LanguageDisplay.PythonDisplay,
"lua": config.AppConfig.LanguageSettings.LanguageDisplay.LuaDisplay,
"js": config.AppConfig.LanguageSettings.LanguageDisplay.JavaDisplay,
"ts": config.AppConfig.LanguageSettings.LanguageDisplay.TypeScriptDisplay,
"rust": config.AppConfig.LanguageSettings.LanguageDisplay.RustDisplay,
"cpp": config.AppConfig.LanguageSettings.LanguageDisplay.CppDisplay,
"c": config.AppConfig.LanguageSettings.LanguageDisplay.CDisplay,
"java": config.AppConfig.LanguageSettings.LanguageDisplay.JavaDisplay,
"ruby": config.AppConfig.LanguageSettings.LanguageDisplay.RubyDisplay,
"php": config.AppConfig.LanguageSettings.LanguageDisplay.PHPDisplay,
"html": config.AppConfig.LanguageSettings.LanguageDisplay.HTMLDisplay,
"css": config.AppConfig.LanguageSettings.LanguageDisplay.CSSDisplay,
"shell": config.AppConfig.LanguageSettings.LanguageDisplay.ShellDisplay,
"default": config.AppConfig.LanguageSettings.LanguageDisplay.DefaultDisplay,
}

// Convert map to slice for sorting
Expand All @@ -324,16 +342,16 @@ func formatLanguages(stats map[string]int, topCount int) string {
return langs[i].lines > langs[j].lines
})

// Calculate size needed for formatted slice
size := 0
for i := 0; i < min(len(langs), topCount); i++ {
if langs[i].lines > 0 {
size++
}
}
// Calculate size needed for formatted slice
size := 0
for i := 0; i < min(len(langs), topCount); i++ {
if langs[i].lines > 0 {
size++
}
}

// Format languages with icons and better number formatting
formatted := make([]string, 0, size)
formatted := make([]string, 0, size)
for i := 0; i < min(len(langs), topCount); i++ {
if langs[i].lines > 0 {
// Retrieve icon or default if not found
Expand Down Expand Up @@ -363,29 +381,29 @@ func formatLanguages(stats map[string]int, topCount int) string {
}

func getTableStyle() table.Style {
return table.Style{
Options: table.Options{
DrawBorder: config.AppConfig.DisplayStats.TableStyle.Options.DrawBorder,
SeparateColumns: config.AppConfig.DisplayStats.TableStyle.Options.SeparateColumns,
SeparateHeader: config.AppConfig.DisplayStats.TableStyle.Options.SeparateHeader,
SeparateRows: config.AppConfig.DisplayStats.TableStyle.Options.SeparateRows,
},
Box: table.BoxStyle{
PaddingLeft: "",
PaddingRight: " ",
MiddleVertical: "",
},
}
return table.Style{
Options: table.Options{
DrawBorder: config.AppConfig.DisplayStats.TableStyle.Options.DrawBorder,
SeparateColumns: config.AppConfig.DisplayStats.TableStyle.Options.SeparateColumns,
SeparateHeader: config.AppConfig.DisplayStats.TableStyle.Options.SeparateHeader,
SeparateRows: config.AppConfig.DisplayStats.TableStyle.Options.SeparateRows,
},
Box: table.BoxStyle{
PaddingLeft: "",
PaddingRight: " ",
MiddleVertical: "",
},
}
}

func (c *DefaultStatsCalculator) ProcessLanguageStats(cache map[string]scan.RepoMetadata) map[string]int {
languageStats := make(map[string]int)
for _, repo := range cache {
for lang, lines := range repo.Languages {
languageStats[lang] += lines
}
}
return languageStats
languageStats := make(map[string]int)
for _, repo := range cache {
for lang, lines := range repo.Languages {
languageStats[lang] += lines
}
}
return languageStats
}

// calculateGlobalStats calculates overall statistics across all repositories
Expand Down Expand Up @@ -515,7 +533,7 @@ func buildSimpleInsights(repos map[string]scan.RepoMetadata) string {
}

// buildInsightsSection - Displays insights about coding activity
func buildInsightsSection() string {
func buildInsightsSection(targetRepo string) string {
if !config.AppConfig.DisplayStats.ShowInsights {
return ""
}
Expand All @@ -524,34 +542,51 @@ func buildInsightsSection() string {
tableWidth := calculator.CalculateTableWidth()
insights := config.AppConfig.DisplayStats.InsightSettings

// Filter cache for target repository if specified
var repoCache map[string]scan.RepoMetadata
repoCache = cache.Cache
if targetRepo != "" {
filteredCache := make(map[string]scan.RepoMetadata)
for path, repo := range cache.Cache {
if strings.HasSuffix(path, "/"+targetRepo) {
filteredCache[path] = repo
break
}
}
if len(filteredCache) == 0 {
return ""
}
repoCache = filteredCache
}

if config.AppConfig.DetailedStats {
t := table.NewWriter()
t.SetStyle(getTableStyle())
t.SetAllowedRowLength(tableWidth-2)
t.SetAllowedRowLength(tableWidth - 2)

// Calculate all stats
weeklyCommits, lastWeeksCommits, _, additions, deletions, hourStats := calculateGlobalStats(cache.Cache)
weeklyCommits, lastWeeksCommits, _, additions, deletions, hourStats := calculateGlobalStats(repoCache)
peakHour, peakCommits := findPeakCodingHour(hourStats)
commitTrend := calculator.CalculateCommitTrend(weeklyCommits, lastWeeksCommits)
languageStats := calculator.ProcessLanguageStats(cache.Cache)
languageStats := calculator.ProcessLanguageStats(repoCache)

// Append rows based on configuration
appendInsightRows(t, insights, insightStats{
weeklyCommits: weeklyCommits,
additions: additions,
deletions: deletions,
peakHour: peakHour,
peakCommits: peakCommits,
peakHour: peakHour,
peakCommits: peakCommits,
commitTrend: commitTrend,
languageStats: languageStats,
})

return t.Render()
}

return buildSimpleInsights(cache.Cache)
return buildSimpleInsights(repoCache)
}

func (rc *DefaultRepoCache) GetRepos() map[string]scan.RepoMetadata {
return rc.cache
return rc.cache
}
Loading

0 comments on commit 7373467

Please sign in to comment.