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