git_stats 1.0.14 → 1.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -41,7 +41,7 @@ module GitStats
41
41
  end
42
42
 
43
43
  def lines_count
44
- @lines_count ||= repo.run("git diff --shortstat `git hash-object -t tree /dev/null` #{self.sha} -- #{repo.tree_path}").lines.map do |line|
44
+ @lines_count ||= repo.run("git diff --shortstat --no-renames `git hash-object -t tree /dev/null` #{self.sha} -- #{repo.tree_path}").lines.map do |line|
45
45
  line[/(\d+) insertions?/, 1].to_i
46
46
  end.sum
47
47
  end
@@ -39,6 +39,10 @@ module GitStats
39
39
  @tree ||= Tree.new(repo: self, relative_path: @tree_path)
40
40
  end
41
41
 
42
+ def command_memoization
43
+ @command_memoization_map ||= {}
44
+ end
45
+
42
46
  def authors
43
47
  @authors ||= run_and_parse("git shortlog -se #{commit_range} #{tree_path}").map do |author|
44
48
  Author.new(repo: self, name: author[:name], email: author[:email])
@@ -95,6 +99,44 @@ module GitStats
95
99
  }].fill_empty_days!(aggregated: true)
96
100
  end
97
101
 
102
+ def files_by_extension_by_date
103
+ file_counts_by_date_by_extension = {}
104
+ extensions_sums = {}
105
+ commits.map do |commit|
106
+ commit.files_by_extension_count.map do |ext, count|
107
+ extensions_sums[ext] ||= 0;
108
+ extensions_sums[ext] = count;
109
+ file_counts_by_date_by_extension[ext] ||= {};
110
+ file_counts_by_date_by_extension[ext][commit.date.to_date] = extensions_sums[ext]
111
+ end
112
+ end
113
+ @multi_data_file_counts_by_date ||= file_counts_by_date_by_extension.map { |ext, data|
114
+ {
115
+ name: ext || "NO EXTENSION",
116
+ data: data.fill_empty_days!(aggregated:true)
117
+ }
118
+ }
119
+ end
120
+
121
+ def lines_by_extension_by_date
122
+ lines_by_date_by_extension = {}
123
+ extensions_sums = {}
124
+ commits.map do |commit|
125
+ commit.lines_by_extension.map do |ext, count|
126
+ extensions_sums[ext] ||= 0;
127
+ extensions_sums[ext] = count;
128
+ lines_by_date_by_extension[ext] ||= {};
129
+ lines_by_date_by_extension[ext][commit.date.to_date] = extensions_sums[ext]
130
+ end
131
+ end
132
+ @multi_data_lines_by_date ||= lines_by_date_by_extension.map { |ext, data|
133
+ {
134
+ name: ext || "NO EXTENSION",
135
+ data: data.fill_empty_days!(aggregated:true)
136
+ }
137
+ }
138
+ end
139
+
98
140
  def last_commit
99
141
  commits.last
100
142
  end
@@ -124,9 +166,14 @@ module GitStats
124
166
  end
125
167
 
126
168
  def run(command)
127
- result = command_runner.run(path, command)
128
- invoke_command_observers(command, result)
129
- result
169
+ if (command_memoization[command])
170
+ command_memoization[command]
171
+ else
172
+ result = command_runner.run(path, command)
173
+ invoke_command_observers(command, result)
174
+ command_memoization[command] = result
175
+ result
176
+ end
130
177
  end
131
178
 
132
179
  def run_and_parse(command)
@@ -19,7 +19,7 @@ module GitStats
19
19
 
20
20
  private
21
21
  def calculate_stat
22
- stat_line = commit.repo.run("git show --shortstat --oneline #{commit.sha} -- #{commit.repo.tree_path}").lines.to_a[1]
22
+ stat_line = commit.repo.run("git show --shortstat --oneline --no-renames #{commit.sha} -- #{commit.repo.tree_path}").lines.to_a[1]
23
23
  if stat_line.blank?
24
24
  @files_changed = @insertions = @deletions = 0
25
25
  else
@@ -3,7 +3,7 @@ module GitStats
3
3
  module StatsView
4
4
  module Charts
5
5
  class All
6
- delegate :files_by_extension, :lines_by_extension, :files_by_date, :lines_by_date, :comments_by_date, to: :repo_charts
6
+ delegate :files_by_extension, :files_by_extension_by_date, :lines_by_extension, :lines_by_extension_by_date, :files_by_date, :lines_by_date, :comments_by_date, to: :repo_charts
7
7
 
8
8
  delegate :commits_sum_by_author_by_date, :changed_lines_by_author_by_date,
9
9
  :insertions_by_author_by_date, :deletions_by_author_by_date, to: :authors_charts
@@ -27,6 +27,26 @@ module GitStats
27
27
  end
28
28
  end
29
29
 
30
+ def files_by_extension_by_date
31
+ Chart.new do |f|
32
+ f.multi_date_chart(
33
+ data: @repo.files_by_extension_by_date,
34
+ title: :files_by_extension_by_date.t,
35
+ y_text: :files.t
36
+ )
37
+ end
38
+ end
39
+
40
+ def lines_by_extension_by_date
41
+ Chart.new do |f|
42
+ f.multi_date_chart(
43
+ data: @repo.lines_by_extension_by_date,
44
+ title: :lines_by_extension_by_date.t,
45
+ y_text: :files.t
46
+ )
47
+ end
48
+ end
49
+
30
50
  def files_by_date
31
51
  Chart.new do |f|
32
52
  f.date_chart(
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module GitStats
3
- VERSION = "1.0.14"
3
+ VERSION = "1.0.15"
4
4
  end
@@ -14,7 +14,7 @@ describe GitStats::GitData::ShortStat do
14
14
  {content: '', expect: [0, 0, 0]},
15
15
  ].each do |test|
16
16
  it "#{test[:content]} parsing" do
17
- commit.repo.should_receive(:run).with("git show --shortstat --oneline abc -- .").and_return("abc some commit\n#{test[:content]}")
17
+ commit.repo.should_receive(:run).with("git show --shortstat --oneline --no-renames abc -- .").and_return("abc some commit\n#{test[:content]}")
18
18
 
19
19
 
20
20
  commit.short_stat.should be_a(GitStats::GitData::ShortStat)
@@ -0,0 +1,392 @@
1
+ /**
2
+ * A Highcharts plugin for exporting data from a rendered chart as CSV, XLS or HTML table
3
+ *
4
+ * Author: Torstein Honsi
5
+ * Licence: MIT
6
+ * Version: 1.4.7
7
+ */
8
+ /*global Highcharts, window, document, Blob */
9
+ (function (factory) {
10
+ if (typeof module === 'object' && module.exports) {
11
+ module.exports = factory;
12
+ } else {
13
+ factory(Highcharts);
14
+ }
15
+ })(function (Highcharts) {
16
+
17
+ 'use strict';
18
+
19
+ var each = Highcharts.each,
20
+ pick = Highcharts.pick,
21
+ seriesTypes = Highcharts.seriesTypes,
22
+ downloadAttrSupported = document.createElement('a').download !== undefined;
23
+
24
+ Highcharts.setOptions({
25
+ lang: {
26
+ downloadCSV: 'Download CSV',
27
+ downloadXLS: 'Download XLS',
28
+ viewData: 'View data table'
29
+ }
30
+ });
31
+
32
+
33
+ /**
34
+ * Get the data rows as a two dimensional array
35
+ */
36
+ Highcharts.Chart.prototype.getDataRows = function () {
37
+ var options = (this.options.exporting || {}).csv || {},
38
+ xAxis,
39
+ xAxes = this.xAxis,
40
+ rows = {},
41
+ rowArr = [],
42
+ dataRows,
43
+ names = [],
44
+ i,
45
+ x,
46
+ xTitle,
47
+ // Options
48
+ dateFormat = options.dateFormat || '%Y-%m-%d %H:%M:%S',
49
+ columnHeaderFormatter = options.columnHeaderFormatter || function (item, key, keyLength) {
50
+ if (item instanceof Highcharts.Axis) {
51
+ return (item.options.title && item.options.title.text) ||
52
+ (item.isDatetimeAxis ? 'DateTime' : 'Category');
53
+ }
54
+ return item ?
55
+ item.name + (keyLength > 1 ? ' ('+ key + ')' : '') :
56
+ 'Category';
57
+ },
58
+ xAxisIndices = [];
59
+
60
+ // Loop the series and index values
61
+ i = 0;
62
+ each(this.series, function (series) {
63
+ var keys = series.options.keys,
64
+ pointArrayMap = keys || series.pointArrayMap || ['y'],
65
+ valueCount = pointArrayMap.length,
66
+ requireSorting = series.requireSorting,
67
+ categoryMap = {},
68
+ xAxisIndex = Highcharts.inArray(series.xAxis, xAxes),
69
+ j;
70
+
71
+ // Map the categories for value axes
72
+ each(pointArrayMap, function (prop) {
73
+ categoryMap[prop] = (series[prop + 'Axis'] && series[prop + 'Axis'].categories) || [];
74
+ });
75
+
76
+ if (series.options.includeInCSVExport !== false && series.visible !== false) { // #55
77
+
78
+ // Build a lookup for X axis index and the position of the first
79
+ // series that belongs to that X axis. Includes -1 for non-axis
80
+ // series types like pies.
81
+ if (!Highcharts.find(xAxisIndices, function (index) {
82
+ return index[0] === xAxisIndex;
83
+ })) {
84
+ xAxisIndices.push([xAxisIndex, i]);
85
+ }
86
+
87
+ // Add the column headers, usually the same as series names
88
+ j = 0;
89
+ while (j < valueCount) {
90
+ names.push(columnHeaderFormatter(series, pointArrayMap[j], pointArrayMap.length));
91
+ j = j + 1;
92
+ }
93
+
94
+ each(series.points, function (point, pIdx) {
95
+ var key = requireSorting ? point.x : pIdx,
96
+ prop,
97
+ val;
98
+
99
+ j = 0;
100
+
101
+ if (!rows[key]) {
102
+ // Generate the row
103
+ rows[key] = [];
104
+ // Contain the X values from one or more X axes
105
+ rows[key].xValues = [];
106
+ }
107
+ rows[key].x = point.x;
108
+ rows[key].xValues[xAxisIndex] = point.x;
109
+
110
+ // Pies, funnels, geo maps etc. use point name in X row
111
+ if (!series.xAxis || series.exportKey === 'name') {
112
+ rows[key].name = point.name;
113
+ }
114
+
115
+ while (j < valueCount) {
116
+ prop = pointArrayMap[j]; // y, z etc
117
+ val = point[prop];
118
+ rows[key][i + j] = pick(categoryMap[prop][val], val); // Pick a Y axis category if present
119
+ j = j + 1;
120
+ }
121
+
122
+ });
123
+ i = i + j;
124
+ }
125
+ });
126
+
127
+ // Make a sortable array
128
+ for (x in rows) {
129
+ if (rows.hasOwnProperty(x)) {
130
+ rowArr.push(rows[x]);
131
+ }
132
+ }
133
+
134
+ var binding, xAxisIndex, column;
135
+ dataRows = [names];
136
+
137
+ i = xAxisIndices.length;
138
+ while (i--) { // Start from end to splice in
139
+ xAxisIndex = xAxisIndices[i][0];
140
+ column = xAxisIndices[i][1];
141
+ xAxis = xAxes[xAxisIndex];
142
+
143
+ // Sort it by X values
144
+ rowArr.sort(function (a, b) {
145
+ return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
146
+ });
147
+
148
+ // Add header row
149
+ xTitle = columnHeaderFormatter(xAxis);
150
+ //dataRows = [[xTitle].concat(names)];
151
+ dataRows[0].splice(column, 0, xTitle);
152
+
153
+ // Add the category column
154
+ each(rowArr, function (row) {
155
+
156
+ var category = row.name;
157
+ if (!category) {
158
+ if (xAxis.isDatetimeAxis) {
159
+ if (row.x instanceof Date) {
160
+ row.x = row.x.getTime();
161
+ }
162
+ category = Highcharts.dateFormat(dateFormat, row.x);
163
+ } else if (xAxis.categories) {
164
+ category = pick(
165
+ xAxis.names[row.x],
166
+ xAxis.categories[row.x],
167
+ row.x
168
+ )
169
+ } else {
170
+ category = row.x;
171
+ }
172
+ }
173
+
174
+ // Add the X/date/category
175
+ row.splice(column, 0, category);
176
+ });
177
+ }
178
+ dataRows = dataRows.concat(rowArr);
179
+
180
+ return dataRows;
181
+ };
182
+
183
+ /**
184
+ * Get a CSV string
185
+ */
186
+ Highcharts.Chart.prototype.getCSV = function (useLocalDecimalPoint) {
187
+ var csv = '',
188
+ rows = this.getDataRows(),
189
+ options = (this.options.exporting || {}).csv || {},
190
+ itemDelimiter = options.itemDelimiter || ',', // use ';' for direct import to Excel
191
+ lineDelimiter = options.lineDelimiter || '\n'; // '\n' isn't working with the js csv data extraction
192
+
193
+ // Transform the rows to CSV
194
+ each(rows, function (row, i) {
195
+ var val = '',
196
+ j = row.length,
197
+ n = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.';
198
+ while (j--) {
199
+ val = row[j];
200
+ if (typeof val === "string") {
201
+ val = '"' + val + '"';
202
+ }
203
+ if (typeof val === 'number') {
204
+ if (n === ',') {
205
+ val = val.toString().replace(".", ",");
206
+ }
207
+ }
208
+ row[j] = val;
209
+ }
210
+ // Add the values
211
+ csv += row.join(itemDelimiter);
212
+
213
+ // Add the line delimiter
214
+ if (i < rows.length - 1) {
215
+ csv += lineDelimiter;
216
+ }
217
+ });
218
+ return csv;
219
+ };
220
+
221
+ /**
222
+ * Build a HTML table with the data
223
+ */
224
+ Highcharts.Chart.prototype.getTable = function (useLocalDecimalPoint) {
225
+ var html = '<table><thead>',
226
+ rows = this.getDataRows();
227
+
228
+ // Transform the rows to HTML
229
+ each(rows, function (row, i) {
230
+ var tag = i ? 'td' : 'th',
231
+ val,
232
+ j,
233
+ n = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.';
234
+
235
+ html += '<tr>';
236
+ for (j = 0; j < row.length; j = j + 1) {
237
+ val = row[j];
238
+ // Add the cell
239
+ if (typeof val === 'number') {
240
+ val = val.toString();
241
+ if (n === ',') {
242
+ val = val.replace('.', n);
243
+ }
244
+ html += '<' + tag + ' class="number">' + val + '</' + tag + '>';
245
+
246
+ } else {
247
+ html += '<' + tag + '>' + (val === undefined ? '' : val) + '</' + tag + '>';
248
+ }
249
+ }
250
+
251
+ html += '</tr>';
252
+
253
+ // After the first row, end head and start body
254
+ if (!i) {
255
+ html += '</thead><tbody>';
256
+ }
257
+
258
+ });
259
+ html += '</tbody></table>';
260
+
261
+ return html;
262
+ };
263
+
264
+ function getContent(chart, href, extension, content, MIME) {
265
+ var a,
266
+ blobObject,
267
+ name,
268
+ options = (chart.options.exporting || {}).csv || {},
269
+ url = options.url || 'http://www.highcharts.com/studies/csv-export/download.php';
270
+
271
+ if (chart.options.exporting.filename) {
272
+ name = chart.options.exporting.filename;
273
+ } else if (chart.title) {
274
+ name = chart.title.textStr.replace(/ /g, '-').toLowerCase();
275
+ } else {
276
+ name = 'chart';
277
+ }
278
+
279
+ // MS specific. Check this first because of bug with Edge (#76)
280
+ if (window.Blob && window.navigator.msSaveOrOpenBlob) {
281
+ // Falls to msSaveOrOpenBlob if download attribute is not supported
282
+ blobObject = new Blob([content]);
283
+ window.navigator.msSaveOrOpenBlob(blobObject, name + '.' + extension);
284
+
285
+ // Download attribute supported
286
+ } else if (downloadAttrSupported) {
287
+ a = document.createElement('a');
288
+ a.href = href;
289
+ a.target = '_blank';
290
+ a.download = name + '.' + extension;
291
+ chart.container.append(a); // #111
292
+ a.click();
293
+ a.remove();
294
+
295
+ } else {
296
+ // Fall back to server side handling
297
+ Highcharts.post(url, {
298
+ data: content,
299
+ type: MIME,
300
+ extension: extension
301
+ });
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Call this on click of 'Download CSV' button
307
+ */
308
+ Highcharts.Chart.prototype.downloadCSV = function () {
309
+ var csv = this.getCSV(true);
310
+ getContent(
311
+ this,
312
+ 'data:text/csv,\uFEFF' + encodeURIComponent(csv),
313
+ 'csv',
314
+ csv,
315
+ 'text/csv'
316
+ );
317
+ };
318
+
319
+ /**
320
+ * Call this on click of 'Download XLS' button
321
+ */
322
+ Highcharts.Chart.prototype.downloadXLS = function () {
323
+ var uri = 'data:application/vnd.ms-excel;base64,',
324
+ template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">' +
325
+ '<head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>' +
326
+ '<x:Name>Ark1</x:Name>' +
327
+ '<x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->' +
328
+ '<style>td{border:none;font-family: Calibri, sans-serif;} .number{mso-number-format:"0.00";}</style>' +
329
+ '<meta name=ProgId content=Excel.Sheet>' +
330
+ '<meta charset=UTF-8>' +
331
+ '</head><body>' +
332
+ this.getTable(true) +
333
+ '</body></html>',
334
+ base64 = function (s) {
335
+ return window.btoa(unescape(encodeURIComponent(s))); // #50
336
+ };
337
+ getContent(
338
+ this,
339
+ uri + base64(template),
340
+ 'xls',
341
+ template,
342
+ 'application/vnd.ms-excel'
343
+ );
344
+ };
345
+
346
+ /**
347
+ * View the data in a table below the chart
348
+ */
349
+ Highcharts.Chart.prototype.viewData = function () {
350
+ if (!this.dataTableDiv) {
351
+ this.dataTableDiv = document.createElement('div');
352
+ this.dataTableDiv.className = 'highcharts-data-table';
353
+
354
+ // Insert after the chart container
355
+ this.renderTo.parentNode.insertBefore(
356
+ this.dataTableDiv,
357
+ this.renderTo.nextSibling
358
+ );
359
+ }
360
+
361
+ this.dataTableDiv.innerHTML = this.getTable();
362
+ };
363
+
364
+
365
+ // Add "Download CSV" to the exporting menu. Use download attribute if supported, else
366
+ // run a simple PHP script that returns a file. The source code for the PHP script can be viewed at
367
+ // https://raw.github.com/highslide-software/highcharts.com/master/studies/csv-export/csv.php
368
+ if (Highcharts.getOptions().exporting) {
369
+ Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
370
+ textKey: 'downloadCSV',
371
+ onclick: function () { this.downloadCSV(); }
372
+ }, {
373
+ textKey: 'downloadXLS',
374
+ onclick: function () { this.downloadXLS(); }
375
+ }, {
376
+ textKey: 'viewData',
377
+ onclick: function () { this.viewData(); }
378
+ });
379
+ }
380
+
381
+ // Series specific
382
+ if (seriesTypes.map) {
383
+ seriesTypes.map.prototype.exportKey = 'name';
384
+ }
385
+ if (seriesTypes.mapbubble) {
386
+ seriesTypes.mapbubble.prototype.exportKey = 'name';
387
+ }
388
+ if (seriesTypes.treemap) {
389
+ seriesTypes.treemap.prototype.exportKey = 'name';
390
+ }
391
+
392
+ });