-
Notifications
You must be signed in to change notification settings - Fork 0
/
stats.go
268 lines (230 loc) · 5.41 KB
/
stats.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package main
import (
"fmt"
"sort"
"time"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
const outOfRange = 99999
const daysInLastSixMonths = 183
const weeksInLastSixMonths = 26
type column []int
// stats calculates and prints the stats.
func stats(email string) {
commits := processRepositories(email)
printCommitsStats(commits)
}
// getBeginningOfDay 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
}
// countDaysSinceDate counts how many days passed since the passed `date`
func countDaysSinceDate(date time.Time) int {
days := 0
now := getBeginningOfDay(time.Now())
for date.Before(now) {
date = date.Add(time.Hour * 24)
days++
if days > daysInLastSixMonths {
return outOfRange
}
}
return days
}
// fillCommits given a repository found in `path`, gets the commits and
// puts them in the `commits` map, returning it when completed
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
offset := calcOffset()
err = iterator.ForEach(func(c *object.Commit) error {
daysAgo := countDaysSinceDate(c.Author.When) + offset
if c.Author.Email != email {
return nil
}
if daysAgo != outOfRange {
commits[daysAgo]++
}
return nil
})
if err != nil {
panic(err)
}
return commits
}
// processRepositories given an user email, returns the
// commits made in the last 6 months
func processRepositories(email string) map[int]int {
filePath := getDotFilePath()
repos := parseFileLinesToSlice(filePath)
daysInMap := daysInLastSixMonths
commits := make(map[int]int, daysInMap)
for i := daysInMap; i > 0; i-- {
commits[i] = 0
}
for _, path := range repos {
commits = fillCommits(email, path, commits)
}
return commits
}
// calcOffset determines and returns the amount of days missing to fill
// the last row of the stats graph
func calcOffset() int {
var offset int
weekday := time.Now().Weekday()
switch weekday {
case time.Sunday:
offset = 7
case time.Monday:
offset = 6
case time.Tuesday:
offset = 5
case time.Wednesday:
offset = 4
case time.Thursday:
offset = 3
case time.Friday:
offset = 2
case time.Saturday:
offset = 1
}
return offset
}
// printCell 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)
}
// printCommitsStats prints the commits stats
func printCommitsStats(commits map[int]int) {
keys := sortMapIntoSlice(commits)
cols := buildCols(keys, commits)
printCells(cols)
}
// sortMapIntoSlice returns a slice of indexes of a map, ordered
func sortMapIntoSlice(m map[int]int) []int {
// order map
// To store the keys in slice in sorted order
var keys []int
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
return keys
}
// buildCols generates a map with rows and columns ready to be printed to screen
func buildCols(keys []int, commits map[int]int) map[int]column {
cols := make(map[int]column)
col := column{}
for _, k := range keys {
week := int(k / 7) //26,25...1
dayinweek := k % 7 // 0,1,2,3,4,5,6
if dayinweek == 0 { //reset
col = column{}
}
col = append(col, commits[k])
if dayinweek == 6 {
cols[week] = col
}
}
return cols
}
// printCells prints the cells of the graph
func printCells(cols map[int]column) {
printMonths()
for j := 6; j >= 0; j-- {
for i := weeksInLastSixMonths + 1; i >= 0; i-- {
if i == weeksInLastSixMonths+1 {
printDayCol(j)
}
if col, ok := cols[i]; ok {
//special case today
if i == 0 && j == calcOffset()-1 {
printCell(col[j], true)
continue
} else {
if len(col) > j {
printCell(col[j], false)
continue
}
}
}
printCell(0, false)
}
fmt.Printf("\n")
}
}
// printMonths prints the month names in the first line, determining when the month
// changed between switching weeks
func printMonths() {
week := getBeginningOfDay(time.Now()).Add(-(daysInLastSixMonths * time.Hour * 24))
month := week.Month()
fmt.Printf(" ")
for {
if week.Month() != month {
fmt.Printf("%s ", week.Month().String()[:3])
month = week.Month()
} else {
fmt.Printf(" ")
}
week = week.Add(7 * time.Hour * 24)
if week.After(time.Now()) {
break
}
}
fmt.Printf("\n")
}
// printDayCol given the day number (0 is Sunday) prints the day name,
// alternating the rows (prints just 2,4,6)
func printDayCol(day int) {
out := " "
switch day {
case 1:
out = " Mon "
case 3:
out = " Wed "
case 5:
out = " Fri "
}
fmt.Printf(out)
}