2015-01-25 20:49:49 +01:00
// Dependencies
2015-02-01 19:05:12 +01:00
var Ul = require ( "ul" )
2015-07-08 09:39:13 +02:00
, Abs = require ( "abs" )
, ReadJson = require ( "r-json" )
, WriteJson = require ( "w-json" )
2015-01-26 08:56:49 +01:00
, Moment = require ( "moment" )
2015-05-03 13:32:20 +02:00
, Couleurs = require ( "couleurs" ) ( )
2015-05-26 11:05:24 +02:00
, Gry = require ( "gry" )
2015-05-26 11:18:04 +02:00
, IsThere = require ( "is-there" )
2015-05-28 18:49:44 +02:00
, CliPie = require ( "cli-pie" )
2015-05-28 20:51:40 +02:00
, CliGhCal = require ( "cli-gh-cal" )
2015-01-25 20:49:49 +01:00
;
2015-01-27 11:44:05 +01:00
// Constants
2015-07-08 09:39:13 +02:00
const STORE _PATH = Abs ( "~/.git-stats" )
2015-01-30 13:55:46 +01:00
, DATE _FORMAT = "MMM D, YYYY"
2015-01-27 11:44:05 +01:00
;
2015-01-25 20:54:02 +01:00
2015-01-25 20:49:49 +01:00
// Constructor
var GitStats = module . exports = { } ;
2015-01-26 08:56:49 +01:00
/ * *
* record
* Records a new commit .
*
* @ name record
* @ function
* @ param { Object } data The commit data containing :
*
2015-01-26 09:47:13 +01:00
* - ` date ` ( String | Date ) : The date object or a string in a format that can be parsed .
2015-01-26 08:56:49 +01:00
* - ` url ` ( String ) : The repository remote url .
* - ` hash ` ( String ) : The commit hash .
*
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-01-26 08:56:49 +01:00
* /
2015-01-25 20:54:02 +01:00
GitStats . record = function ( data , callback ) {
2015-01-26 08:58:03 +01:00
// Validate data
2015-01-26 08:56:49 +01:00
callback = callback || function ( err ) { if ( err ) throw err ; } ;
data = Object ( data ) ;
if ( typeof data . date === "string" ) {
2015-01-26 09:47:13 +01:00
data . date = new Moment ( new Date ( data . date ) ) ;
2015-01-26 08:56:49 +01:00
}
2015-01-26 09:21:40 +01:00
if ( ! data . date || ! /^Moment|Date$/ . test ( data . date . constructor . name ) ) {
2015-02-09 14:11:47 +01:00
callback ( new Error ( "The date field should be a string or a date object." ) ) ;
return GitStats ;
2015-01-26 08:56:49 +01:00
}
if ( typeof data . hash !== "string" || ! data . hash ) {
2015-02-09 14:11:47 +01:00
callback ( new Error ( "Invalid hash." ) ) ;
return GitStats ;
2015-01-26 08:56:49 +01:00
}
if ( typeof data . url !== "string" || ! data . url ) {
2015-02-19 04:21:04 +01:00
callback ( new Error ( "Invalid url field. This commit is not recorded into the git-stats history since you haven't added the remote url. You can import the previous commits using the git-stats-importer tool." ) ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-26 08:56:49 +01:00
}
2015-01-26 08:58:03 +01:00
// Get stats
GitStats . get ( function ( err , stats ) {
2015-01-25 20:54:02 +01:00
stats = stats || { } ;
2015-01-30 13:55:46 +01:00
var day = data . date . format ( DATE _FORMAT )
2015-01-26 08:56:49 +01:00
, today = stats [ day ] = Object ( stats [ day ] )
, repo = today [ data . url ] = Object ( today [ data . url ] )
;
repo [ data . hash ] = { date : data . date } ;
2015-02-01 19:05:12 +01:00
GitStats . save ( stats , callback ) ;
2015-01-25 20:54:02 +01:00
} ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-25 20:54:02 +01:00
} ;
2015-01-26 09:05:46 +01:00
/ * *
* get
* Gets the git stats .
*
* @ name get
* @ function
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-01-26 09:05:46 +01:00
* /
2015-01-27 11:44:05 +01:00
GitStats . get = function ( callback ) {
2015-07-08 09:39:13 +02:00
ReadJson ( STORE _PATH , "utf-8" , function ( err , data ) {
2015-02-10 08:41:13 +01:00
if ( err && err . code === "ENOENT" ) {
return GitStats . save ( { } , function ( err ) {
callback ( err , { } ) ;
} ) ;
}
2015-02-01 19:05:12 +01:00
if ( err ) { return callback ( err ) ; }
callback ( null , data ) ;
} ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-27 11:44:05 +01:00
} ;
2015-02-08 19:15:25 +01:00
/ * *
* save
* Saves the provided stats .
*
* @ name save
* @ function
* @ param { Object } stats The stats to be saved .
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-02-08 19:15:25 +01:00
* /
2015-02-01 19:05:12 +01:00
GitStats . save = function ( stats , callback ) {
2015-07-08 09:39:13 +02:00
WriteJson ( STORE _PATH , stats , callback ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-02-08 19:15:25 +01:00
} ;
2015-02-01 19:05:12 +01:00
2015-02-08 19:15:25 +01:00
/ * *
* iterateDays
2015-02-15 19:20:49 +01:00
* Iterate through the days , calling the callback function on each day .
2015-02-08 19:15:25 +01:00
*
* @ name iterateDays
* @ function
* @ param { Object } data An object containing the following fields :
*
* - ` start ` ( Moment ) : A ` Moment ` date object representing the start date ( default : * an year ago * ) .
* - ` end ` ( Moment ) : A ` Moment ` date object representing the end date ( default : * now * ) .
* - ` format ` ( String ) : The format of the date ( default : ` "MMM D, YYYY" ` ) .
*
* @ param { Function } callback The callback function called with the current day formatted ( type : string ) and the ` Moment ` date object .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-02-08 19:15:25 +01:00
* /
2015-01-27 11:44:05 +01:00
GitStats . iterateDays = function ( data , callback ) {
2015-01-25 20:54:02 +01:00
if ( typeof data === "function" ) {
callback = data ;
2015-01-27 11:44:05 +01:00
data = undefined ;
}
// Merge the defaults
2015-05-28 20:32:57 +02:00
data . end = data . end || Moment ( ) ;
data . start = data . start || Moment ( ) . subtract ( 1 , "years" ) ;
data . format = data . format || DATE _FORMAT ;
2015-01-27 11:44:05 +01:00
2015-05-28 20:51:40 +02:00
var start = new Moment ( data . start . format ( DATE _FORMAT ) , DATE _FORMAT )
, end = new Moment ( data . end . format ( DATE _FORMAT ) , DATE _FORMAT )
2015-02-01 09:45:03 +01:00
, tomrrow = Moment ( end . format ( DATE _FORMAT ) , DATE _FORMAT ) . add ( 1 , "days" )
, endStr = tomrrow . format ( DATE _FORMAT )
2015-01-27 11:44:05 +01:00
, cDay = null
;
2015-02-01 09:45:03 +01:00
while ( start . format ( DATE _FORMAT ) !== endStr ) {
2015-01-27 11:44:05 +01:00
cDay = start . format ( data . format ) ;
2015-01-27 12:07:15 +01:00
callback ( cDay , start ) ;
2015-02-08 19:16:08 +01:00
start . add ( 1 , "days" ) ;
2015-01-25 20:54:02 +01:00
}
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-25 20:49:49 +01:00
} ;
2015-01-26 10:27:48 +01:00
2015-02-08 19:15:25 +01:00
/ * *
* graph
* Creates an object with the stats on the provided period ( default : * last year * ) .
*
* @ name graph
* @ function
* @ param { Object } data The object passed to the ` iterateDays ` method .
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-02-08 19:15:25 +01:00
* /
2015-01-26 10:27:48 +01:00
GitStats . graph = function ( data , callback ) {
2015-01-27 11:44:05 +01:00
if ( typeof data === "function" ) {
callback = data ;
data = undefined ;
}
// Get commits
GitStats . get ( function ( err , stats ) {
2015-01-26 10:27:48 +01:00
if ( err ) { return callback ( err ) ; }
2015-01-27 11:44:05 +01:00
var cDayObj = null
, year = { }
2015-01-26 10:27:48 +01:00
;
2015-01-27 11:44:05 +01:00
// Iterate days
GitStats . iterateDays ( data , function ( cDay ) {
2015-01-26 10:27:48 +01:00
cDayObj = year [ cDay ] = {
_ : stats [ cDay ] || { }
, c : 0
} ;
Object . keys ( cDayObj . _ ) . forEach ( function ( c ) {
cDayObj . c += Object . keys ( cDayObj . _ [ c ] ) . length ;
} ) ;
2015-01-27 11:44:05 +01:00
} ) ;
2015-01-26 10:27:48 +01:00
callback ( null , year ) ;
2015-01-27 11:44:05 +01:00
} ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-27 11:44:05 +01:00
} ;
2015-02-08 19:15:25 +01:00
/ * *
* calendar
* Creates the calendar data for the provided period ( default : * last year * ) .
*
* @ name calendar
* @ function
* @ param { Object } data The object passed to the ` graph ` method .
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-02-08 19:15:25 +01:00
* /
2015-01-27 11:44:05 +01:00
GitStats . calendar = function ( data , callback ) {
GitStats . graph ( data , function ( err , graph ) {
if ( err ) { return callback ( err ) ; }
2015-02-01 09:45:03 +01:00
2015-02-01 14:43:45 +01:00
var cal = { total : 0 , days : { } , cStreak : 0 , lStreak : 0 , max : 0 }
2015-01-27 11:44:05 +01:00
, cDay = null
, days = Object . keys ( graph )
2015-02-01 09:45:03 +01:00
, levels = null
, cLevel = 0
2015-01-27 11:44:05 +01:00
;
days . forEach ( function ( c ) {
cDay = graph [ c ] ;
cal . total += cDay . c ;
2015-02-01 14:43:45 +01:00
if ( cDay . c > cal . max ) {
cal . max = cDay . c ;
2015-01-27 11:44:05 +01:00
}
2015-01-27 12:25:25 +01:00
if ( cDay . c > 0 ) {
if ( ++ cal . cStreak > cal . lStreak ) {
cal . lStreak = cal . cStreak ;
}
} else {
cal . cStreak = 0 ;
}
2015-01-27 11:44:05 +01:00
} ) ;
2015-02-10 08:51:15 +01:00
levels = cal . max / ( LEVELS . length * 2 ) ;
2015-01-27 11:44:05 +01:00
days . forEach ( function ( c ) {
cDay = graph [ c ] ;
cal . days [ c ] = {
c : cDay . c
2015-02-01 09:45:03 +01:00
, level : ! levels
2015-02-10 08:51:15 +01:00
? 0 : ( cLevel = Math . round ( cDay . c / levels ) ) >= 4
2015-02-01 09:45:03 +01:00
? 4 : ! cLevel && cDay . c > 0 ? 1 : cLevel
2015-01-27 11:44:05 +01:00
} ;
} ) ;
callback ( null , cal ) ;
} ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-27 11:44:05 +01:00
} ;
2015-02-08 19:15:25 +01:00
/ * *
* ansiCalendar
* Creates the ANSI contributions calendar .
*
* @ name ansiCalendar
* @ function
* @ param { Object } data The object passed to the ` calendar ` method .
* @ param { Function } callback The callback function .
2015-02-09 14:11:47 +01:00
* @ return { GitStats } The ` GitStats ` object .
2015-02-08 19:15:25 +01:00
* /
2015-01-27 11:44:05 +01:00
GitStats . ansiCalendar = function ( data , callback ) {
if ( typeof data === "function" ) {
callback = data ;
data = undefined ;
}
2015-01-27 12:07:15 +01:00
2015-05-28 20:51:40 +02:00
GitStats . graph ( data , function ( err , graph ) {
var cal = [ ] ;
GitStats . iterateDays ( data , function ( cDay ) {
cDayObj = graph [ cDay ] ;
if ( ! cDayObj ) { return ; }
cal . push ( [ cDay , cDayObj . c ] ) ;
} ) ;
callback ( null , CliGhCal ( cal , {
theme : data . theme
, start : data . start
, end : data . end
} ) ) ;
} ) ;
2015-02-09 14:11:47 +01:00
return GitStats ;
2015-01-26 10:27:48 +01:00
} ;
2015-05-05 15:56:26 +02:00
2015-07-08 08:17:58 +02:00
GitStats . authors = function ( options , callback ) {
var repo = new Gry ( options . repo ) ;
repo . exec ( "shortlog -s -n --all" , function ( err , stdout ) {
if ( err ) { return callback ( err ) ; }
lines = stdout . split ( "\n" ) ;
pieData = stdout . split ( "\n" ) . map ( function ( c ) {
var splits = c . split ( "\t" ) . map ( function ( cc ) {
return cc . trim ( ) ;
} ) ;
return {
value : parseInt ( splits [ 0 ] )
, label : splits [ 1 ]
} ;
} ) ;
callback ( null , pieData ) ;
} ) ;
} ;
2015-05-05 15:56:26 +02:00
GitStats . authorsPie = function ( options , callback ) {
2015-05-26 11:18:04 +02:00
if ( typeof options === "string" ) {
options = {
repo : options
} ;
}
2015-05-28 18:56:32 +02:00
options = Ul . merge ( options , {
radius : process . stdout . rows / 2 || 20
} ) ;
2015-05-26 11:18:04 +02:00
if ( ! IsThere . sync ( options . repo ) ) {
return callback ( new Error ( "Repository is missing." ) ) ;
}
2015-05-05 15:56:26 +02:00
2015-05-28 18:49:44 +02:00
var repo = new Gry ( options . repo )
, pie = null
, pieData = [ ]
;
2015-07-08 08:17:58 +02:00
GitStats . authors ( options , function ( err , authors ) {
2015-05-26 11:18:04 +02:00
if ( err ) { return callback ( err ) ; }
2015-07-08 08:17:58 +02:00
if ( authors . length > 50 ) {
var others = {
value : authors . slice ( 50 ) . reduce ( function ( a , b ) {
debugger
return a + b . value ;
} , 0 )
, label : "Others"
2015-05-28 18:49:44 +02:00
} ;
2015-07-08 08:17:58 +02:00
debugger
authors = authors . slice ( 0 , 50 ) ;
authors . push ( others ) ;
}
2015-05-28 18:49:44 +02:00
2015-07-08 08:17:58 +02:00
pie = new CliPie ( options . radius , authors , {
2015-05-28 18:49:44 +02:00
legend : true
, flat : true
2015-05-28 18:56:32 +02:00
, no _ansi : options . no _ansi
2015-05-28 18:49:44 +02:00
} ) ;
callback ( null , pie . toString ( ) ) ;
2015-05-26 11:18:04 +02:00
} ) ;
2015-05-05 15:56:26 +02:00
} ;