gitstats

Log

Files

Refs

README

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: