1 | var util = require('util');
|
2 |
|
3 | var stream = require('stream-wrapper');
|
4 | var noansi = require('ansi-stripper');
|
5 |
|
6 | require('colors');
|
7 |
|
8 | var PROGRESS_BAR_LENGTH = 50;
|
9 | var TABLE_CELL_PADDING = 2;
|
10 | var OUTPUT_PADDING = 10;
|
11 |
|
12 | var error = function(err) {
|
13 | console.error((err.message || err).red);
|
14 |
|
15 | if(err.patch) {
|
16 | var patch = err.patch;
|
17 |
|
18 | console.error(JSON.stringify({
|
19 | modified: patch.modified,
|
20 | before: patch.before,
|
21 | after: patch.after,
|
22 | modifier: patch.modifier,
|
23 | diff: patch.diff
|
24 | }, null, 4));
|
25 | }
|
26 | if(err.stack) {
|
27 | console.error(err.stack);
|
28 | }
|
29 | };
|
30 |
|
31 | var bar = function(percent) {
|
32 | percent = Math.min(percent, 100);
|
33 |
|
34 | var bar = '';
|
35 | var limit = Math.floor(percent / 100 * PROGRESS_BAR_LENGTH);
|
36 | var i;
|
37 |
|
38 | for(i = 0; i < limit; i++) {
|
39 | bar += '=';
|
40 | }
|
41 | if(limit < PROGRESS_BAR_LENGTH) {
|
42 | bar += '>';
|
43 | limit++;
|
44 | }
|
45 | for(i = limit; i < PROGRESS_BAR_LENGTH; i++) {
|
46 | bar += ' ';
|
47 | }
|
48 |
|
49 | return '[' + bar.bold.cyan + ']';
|
50 | };
|
51 |
|
52 | var table = function(table) {
|
53 | var columnLengths = [];
|
54 |
|
55 | var each = function(fn) {
|
56 | for(var i = 0; i < table.length; i++) {
|
57 | var row = table[i];
|
58 |
|
59 | for(var j = 0; j < row.length; j++) {
|
60 | fn(i, j, row[j]);
|
61 | }
|
62 | }
|
63 | };
|
64 | var padding = function(padding) {
|
65 | var result = '';
|
66 |
|
67 | for(var i = 0; i < padding; i++) {
|
68 | result += ' ';
|
69 | }
|
70 |
|
71 | return result;
|
72 | };
|
73 | var length = function(obj) {
|
74 | return noansi(obj.toString()).length;
|
75 | };
|
76 |
|
77 | each(function(i, j, value) {
|
78 | columnLengths[j] = Math.max(columnLengths[j] || 0, length(value));
|
79 | });
|
80 |
|
81 | each(function(i, j, value) {
|
82 | table[i][j] = value + padding(columnLengths[j] - length(value) + TABLE_CELL_PADDING);
|
83 | });
|
84 |
|
85 | return table.map(function(row) {
|
86 | return row.join(padding(TABLE_CELL_PADDING * 2));
|
87 | });
|
88 | };
|
89 |
|
90 | var time = function(time) {
|
91 | var hours = Math.floor(time / 3600);
|
92 | var minutes = Math.floor((time - (hours * 3600)) / 60);
|
93 | var seconds = Math.floor(time - (hours * 3600) - (minutes * 60));
|
94 |
|
95 | var pad = function(n) {
|
96 | if(n < 10) {
|
97 | return '0' + n;
|
98 | }
|
99 |
|
100 | return n;
|
101 | };
|
102 |
|
103 | return pad(hours) + 'h ' + pad(minutes) + 'm ' + pad(seconds) + 's';
|
104 | };
|
105 |
|
106 | var sign = function(number) {
|
107 | if(!number) {
|
108 | return ' 0';
|
109 | }
|
110 |
|
111 | return number < 0 ? number.toString() : ('+' + number);
|
112 | };
|
113 |
|
114 | var capture = function(delta) {
|
115 | var current = Number.MIN_VALUE;
|
116 |
|
117 | return function(v) {
|
118 | if(Math.abs(current - v) > delta) {
|
119 | current = v;
|
120 | return v;
|
121 | }
|
122 |
|
123 | return current;
|
124 | };
|
125 | };
|
126 |
|
127 | var progress = function(options) {
|
128 | var output = [];
|
129 | var eta = capture(30);
|
130 | var speed = capture(10);
|
131 |
|
132 | var outputProgress = function(output, count, total, progress) {
|
133 | output.push('Progress: '.grey + bar(progress) + ' ' + count + '/' + total + ' ' + progress.toFixed(1) + '%');
|
134 | output.push('Patch: '.grey + options.patch);
|
135 | };
|
136 |
|
137 | var outputDiff = function(output, diff) {
|
138 | diff = Object.keys(diff || {}).map(function(key) {
|
139 | return [
|
140 | key,
|
141 | sign(diff[key].added).green,
|
142 | diff[key].updated.toString().yellow,
|
143 | sign(-diff[key].removed).red
|
144 | ];
|
145 | });
|
146 |
|
147 | diff = diff.length ? diff : [[ '(No changes)' ]];
|
148 | diff.forEach(function(row) {
|
149 | row.unshift('');
|
150 | });
|
151 |
|
152 | diff.unshift(['Diff: '.grey, '', 'added'.grey, 'updated'.grey, 'removed'.grey]);
|
153 | Array.prototype.push.apply(output, table(diff));
|
154 | };
|
155 |
|
156 | var outputStats = function(output, modified, skipped, count, t, e, s) {
|
157 | var summary = table([
|
158 | ['Summary:'.grey, 'Time', time(t)],
|
159 | ['', 'ETA', time(eta(e)), util.format('(speed %s)', Math.round(speed(s)))],
|
160 | ['', 'Modified', modified, util.format('(rest %s)', count - modified)],
|
161 | ['', 'Skipped', skipped]
|
162 | ]);
|
163 |
|
164 | Array.prototype.push.apply(output, summary);
|
165 | };
|
166 |
|
167 | var logOutput = function(output) {
|
168 | console.log(output.join('\n'));
|
169 | };
|
170 |
|
171 | outputProgress(output, 0, 0, options.total === 0 ? 100 : 0);
|
172 | logOutput(output);
|
173 |
|
174 | return stream.transform({ objectMode: true }, function(patch, enc, callback) {
|
175 | var progress = patch.progress;
|
176 |
|
177 | process.stdout.moveCursor(0, -output.length);
|
178 | output = [];
|
179 |
|
180 | outputProgress(output, progress.count, progress.total, progress.percentage);
|
181 |
|
182 | output.push('');
|
183 |
|
184 | outputStats(output, progress.modified, progress.skipped, progress.count, progress.time, progress.eta, progress.speed);
|
185 | outputDiff(output, progress.diff);
|
186 |
|
187 | if(output.length > process.stdout.rows - OUTPUT_PADDING) {
|
188 | output = output.slice(0, process.stdout.rows - OUTPUT_PADDING);
|
189 | output.push(' ...');
|
190 | }
|
191 |
|
192 | logOutput(output);
|
193 |
|
194 | callback(null, patch);
|
195 | }, function(callback) {
|
196 | console.log('\n DONE'.green);
|
197 | callback();
|
198 | });
|
199 | };
|
200 |
|
201 | progress.error = error;
|
202 |
|
203 | module.exports = progress;
|