git_stats 1.0.14 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ });