git-stats.c (8843B)
1 #! /usr/bin/env sheepy 2 /* -./; 3 * or direct path to sheepy: #! /usr/local/bin/sheepy */ 4 /* Libsheepy documentation: https://spartatek.se/libsheepy/ */ 5 #include "libsheepyObject.h" 6 7 // git commands to get statistics: 8 // https://devtut.github.io/git/git-statistics.html 9 10 int argc; char **argv; 11 12 /* enable/disable logging */ 13 /* #undef pLog */ 14 /* #define pLog(...) */ 15 16 // terminal size type 17 typ struct {u16 rows; u16 cols;} winSizet; 18 19 /* terminal window size */ 20 #include <sys/ioctl.h> 21 #include <unistd.h> 22 23 // get terminal window size 24 winSizet wsize (void) { 25 struct winsize w; 26 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 27 28 return (winSizet){w.ws_row, w.ws_col}; 29 } 30 31 time_t earliest, latest; 32 char *firstDate = null, *lastDate = null; 33 34 void drawGraph(char *from, char *gitopts, bool setTimeRange, bool barchart) { 35 36 // Steps 37 // get git statistics 38 // sort the dates 39 // remove time, keep only the date in a slice: slDates 40 // count daily commits 41 // compute min and max daily count 42 // compute earliest and latest dates in unix time 43 // get window size 44 // aggregate daily count to window columns 45 // create a blank display array 46 // fill the display with the plot 47 // show plot 48 49 // get git statistics 50 cleanListP(dates) = execOutf("git log %s --pretty=format:\"%%ai\"", gitopts); 51 52 // sort the dates 53 sortG(&dates); 54 55 char *ld = getG(dates, unusedV, -1); 56 if (!ld) /*dates is empty, stop.*/ return; 57 ld[4] = 0; 58 if (parseInt(ld) < parseInt(from)) /* the last date is earlier than from, stop. */ return; 59 ld[4] = '-'; 60 61 int cut = 0; 62 int fromYear = parseInt(from); 63 enumerateS(dates, s, i) { 64 s[4] = 0; 65 int year = parseInt(s); 66 s[4] = '-'; 67 if (year >= fromYear) { 68 cut = i; 69 break; 70 } 71 } 72 73 if (cut) { 74 delG(&dates, 0, cut); 75 } 76 77 if (lenG(dates) < 2) /* skip plot only 1 commit */ return; 78 79 // remove time, keep only the date in a slice: slDates 80 // count daily commits 81 typ struct { 82 char *date; 83 int count; 84 } dateCountst; 85 86 sliceT(slDatest, dateCountst); 87 slDatest slDates; 88 sliceInit(&slDates); 89 90 dateCountst d = {0}; 91 forEachS(dates, s) { 92 s[11] = 0; 93 if (not d.date) { 94 // first element 95 d.date = s; 96 inc d.count; 97 } 98 else { 99 if (eqG(d.date, s)) { 100 inc d.count; 101 } 102 else { 103 sliceAppend(&slDates, d); 104 d.date = s; 105 d.count = 1; 106 } 107 } 108 } 109 // append last date 110 sliceAppend(&slDates, d); 111 112 // compute min and max daily count 113 int minCount = sliceAt(&slDates, 0).count; 114 int maxCount = 0; 115 116 sliceForEach(&slDates, date) { 117 minCount = MIN(date->count, minCount); 118 maxCount = MAX(date->count, maxCount); 119 } 120 121 // compute earliest and latest dates in unix time 122 if (setTimeRange) { 123 earliest = strToUnixTime(sliceFirst(&slDates).date, "%Y-%m-%d"); 124 latest = strToUnixTime(sliceLast (&slDates).date, "%Y-%m-%d"); 125 firstDate = strdup(sliceFirst(&slDates).date); 126 lastDate = strdup(sliceLast (&slDates).date); 127 } 128 129 //logI("%"PRIu64" %"PRIu64" = %f", earliest, latest, (f64)((latest-earliest)/86400)/365); 130 131 typ struct { 132 int col; 133 int value; 134 } pet; // plot element type 135 136 sliceT(plt, pet); 137 plt slPl; 138 sliceInit(&slPl); 139 140 // get window size 141 winSizet wz = wsize(); 142 #define graphHeight 20 143 144 145 //logI("min %d, max %d, w %d h %d, first %s, last %s", minCount, maxCount, wz.cols, wz.rows, sliceFirst(&slDates).date, sliceLast(&slDates).date); 146 147 // aggregate daily count to window columns 148 pet p = {0}; 149 150 sliceForEach(&slDates, date) { 151 time_t t = strToUnixTime(date->date, "%Y-%m-%d"); 152 int col; 153 if (barchart) 154 col = (f64)(t-earliest)/(f64)(latest-earliest) * (f64)(wz.cols-1/*-1 to make sure it writes inside the display*/); 155 else 156 col = (f64)(t-earliest)/(f64)(latest-earliest) * (f64)(wz.cols*2-1/*-1 to make sure it writes inside the display*/); 157 if (col == p.col) { 158 p.value = MAX(date->count, p.value); 159 } 160 else { 161 sliceAppend(&slPl, p); 162 p.col = col; 163 p.value = date->count; 164 } 165 } 166 sliceAppend(&slPl, p); 167 168 // create a blank display array 169 // create a blank display array 170 char *display[wz.cols][graphHeight]; 171 172 char *BARS[] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; 173 174 u32 dotDisplay[wz.cols][graphHeight]; 175 176 if (barchart) { 177 // clear display 178 range(i, wz.cols) { 179 range(j, graphHeight) { 180 display[i][j] = BARS[0]; 181 } 182 } 183 } 184 else { 185 // clear dotDisplay 186 range(i, wz.cols) { 187 range(j, graphHeight) { 188 dotDisplay[i][j] = ' '; 189 } 190 } 191 } 192 193 if (barchart) { 194 // fill the display with the plot 195 sliceForEach(&slPl, elm) { 196 int dpHeight = (f64)(graphHeight*8) - (f64)elm->value/(f64)maxCount * (f64)(graphHeight*8); 197 int bits = dpHeight & 7; 198 int cbar = 8 - bits; 199 200 // draw bar 201 int h = dpHeight / 8; 202 203 if (bits) { 204 // print BARS[cbar] 205 display[elm->col][h] = BARS[cbar]; 206 inc h; 207 } 208 209 rangeFrom(i, h, graphHeight) { 210 display[elm->col][i] = BARS[8]; 211 } 212 213 //logI("col %d, value %d", elm->col, elm->value); 214 //logI("col %3d, value %4d %3d cbar %d bits %d", elm->col, dpHeight, h, cbar, bits); 215 } 216 } 217 else { 218 // for more information about braille, see: 219 // https://en.wikipedia.org/wiki/Braille_Patterns 220 const u32 brailleOffset = 0x2800; 221 const u32 braille[4][2] = { 222 {0x01, 0x08}, 223 {0x02, 0x10}, 224 {0x04, 0x20}, 225 {0x40, 0x80}, 226 }; 227 228 void setPoint(i32 x, i32 y) { 229 dotDisplay[x/2][y/4] |= braille[y%4][x%2] + brailleOffset; 230 } 231 232 void line(i32 x0, i32 y0, i32 x1, i32 y1) { 233 // bresenham's line algorithm 234 // https://en.wikipedia.org/wiki/Bresenham's_line_algorithm 235 i32 dx = abs(x1-x0); 236 i32 sx = x0<x1 ? 1 : -1; 237 i32 dy = -abs(y1-y0); 238 i32 sy = y0<y1 ? 1 : -1; 239 // error value e_xy 240 i32 err = dx+dy; 241 loopBreakerInit; 242 forever { 243 loopBreaker(10000); 244 setPoint(x0, y0); 245 if (x0 == x1 and y0 == y1) break; 246 i32 e2 = 2*err; 247 if (e2 >= dy) { 248 // e_xy+e_x > 0 249 err += dy; 250 x0 += sx; 251 } 252 if (e2 <= dx) { 253 // e_xy+e_y < 0 254 err += dx; 255 y0 += sy; 256 } 257 } 258 } 259 260 261 // fill the display with the plot 262 sliceEnumerate(&slPl, sli, elm) { 263 if (sli > 0) { 264 int dpHeight = (f64)(graphHeight*4) - (f64)elm->value/(f64)maxCount * (f64)(graphHeight*4); 265 int prevHeight = (f64)(graphHeight*4) - (f64)sliceAt(&slPl,sli-1).value/(f64)maxCount * (f64)(graphHeight*4); 266 line(sliceAt(&slPl,sli-1).col, prevHeight, elm->col, dpHeight); 267 } 268 //logI("col %d, value %d", elm->col, elm->value); 269 } 270 } 271 272 // show plot 273 char maxC[20] = {0}; 274 snprintf(maxC, sizeof(maxC), "%d", maxCount); 275 var ln = strlen(maxC); 276 snprintf(maxC, sizeof(maxC), BLD"%d"RST, maxCount); 277 #define HORIZONTAL_DASH "┈" 278 printf(maxC); 279 rangeFrom(i, ln, wz.cols) { 280 printf(HORIZONTAL_DASH); 281 } 282 put; 283 if (barchart) { 284 range(j, graphHeight) { 285 range(i, wz.cols) { 286 printf(display[i][j]); 287 } 288 put; 289 } 290 } 291 else { 292 range(j, graphHeight) { 293 range(i, wz.cols) { 294 char dots[10] = {0}; 295 bRune2CodeUTF8(dots, dotDisplay[i][j]); 296 printf(BLD"%s"RST, dots); 297 } 298 put; 299 } 300 } 301 range(i, wz.cols) { 302 printf(HORIZONTAL_DASH); 303 } 304 put; 305 int col = strlen(firstDate); 306 printf(BLD"%s"RST, firstDate); 307 time_t halfTime = (earliest+latest)/2; 308 char hT[5]; 309 bLTimeToYMDS(hT, sizeof(hT), halfTime); 310 int spaces = (wz.cols/2-2)-col; 311 col += spaces + 4 /*halfTime string length*/; 312 range(i, spaces) printf(" "); 313 printf(BLD"%s"RST, hT); 314 spaces = wz.cols - strlen(lastDate) - col; 315 range(i, spaces) printf(" "); 316 printf(BLD"%s"RST, lastDate); 317 318 319 sliceFree(&slPl); 320 sliceFree(&slDates); 321 } 322 323 int main(int ARGC, char** ARGV) { 324 325 argc = ARGC; argv = ARGV; 326 327 initLibsheepy(ARGV[0]); 328 setLogMode(LOG_DATE); 329 //openProgLogFile(); 330 //setLogSymbols(LOG_UTF8); 331 //disableLibsheepyErrorLogs; 332 333 334 char *from = null; 335 bool barchart = no; 336 if (argc > 1) { 337 if (isInt(argv[1])) { 338 from = argv[1]; 339 barchart = argv[2] and eqG(argv[2], "bar"); 340 } 341 else 342 barchart = argv[1] and eqG(argv[1], "bar"); 343 } 344 345 drawGraph(from, "", /*setTimeRange*/ yes, barchart); 346 347 // get list of authors 348 // git shortlog -sn 349 cleanListP(authors) = execOut("git shortlog -sn"); 350 351 // get commit dates for each author 352 // git log --author="Remy" --pretty=format:"%ai" 353 forEachS(authors, l) { 354 puts(&l[7]); 355 cleanCharP(auth) = formatS("--author=\"%s\"", &l[7]); 356 drawGraph(from, auth, /*setTimeRange*/ no, barchart); 357 } 358 359 freeManyS(firstDate, lastDate); 360 } 361 // vim: set expandtab ts=2 sw=2: