1 | var _readline = require('readline');
|
2 |
|
3 |
|
4 | function Bar(options){
|
5 |
|
6 | options = options || {};
|
7 |
|
8 |
|
9 | this.timer = null;
|
10 |
|
11 |
|
12 | this.value = 0;
|
13 |
|
14 |
|
15 | this.total = 100;
|
16 |
|
17 |
|
18 | this.throttleTime = 1000 / (options.fps || 10);
|
19 |
|
20 |
|
21 | this.stream = options.stream || process.stderr;
|
22 |
|
23 |
|
24 | this.clearOnComplete = options.clearOnComplete || false;
|
25 |
|
26 |
|
27 | this.lastDrawnString = null;
|
28 |
|
29 |
|
30 | this.barsize = options.barsize || 40;
|
31 |
|
32 |
|
33 | this.hideCursor = options.hideCursor || false;
|
34 |
|
35 |
|
36 | this.barCompleteString = (new Array(this.barsize + 1 ).join(options.barCompleteChar || '='));
|
37 | this.barIncompleteString = (new Array(this.barsize + 1 ).join(options.barIncompleteChar || '-'));
|
38 |
|
39 |
|
40 | this.format = options.format || 'progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}';
|
41 |
|
42 |
|
43 | this.startTime = null;
|
44 |
|
45 |
|
46 | this.lastRedraw = Date.now();
|
47 |
|
48 |
|
49 | this.etaBuffer = options.etaBuffer || 10;
|
50 |
|
51 |
|
52 | this.eta = {};
|
53 | };
|
54 |
|
55 |
|
56 | Bar.prototype.render = function(){
|
57 | this.stopTimer();
|
58 |
|
59 |
|
60 | var s = this.format;
|
61 |
|
62 |
|
63 | var progress = this.value/this.total;
|
64 |
|
65 |
|
66 | progress = Math.min(Math.max(progress, 0.0), 1.0);
|
67 |
|
68 |
|
69 | var b = this.barCompleteString.substr(0, Math.round(progress*this.barsize)) +
|
70 | this.barIncompleteString.substr(0, Math.round((1.0-progress)*this.barsize));
|
71 |
|
72 |
|
73 | b = b.substr(0, this.barsize);
|
74 |
|
75 |
|
76 | var percentage = Math.round(progress*100) + '';
|
77 |
|
78 |
|
79 | var elapsedTime = Math.round((Date.now() - this.startTime)/1000);
|
80 | var elapsedTimef = this.formatTime(elapsedTime);
|
81 |
|
82 |
|
83 | var eta = this.eta.time;
|
84 | var etaf = this.formatTime(eta, 5);
|
85 |
|
86 |
|
87 | s = s.replace(/\{bar}/gi, b)
|
88 | .replace(/\{percentage}/gi, percentage)
|
89 | .replace(/\{total}/gi, this.total + '')
|
90 | .replace(/\{value}/gi, this.value + '')
|
91 | .replace(/\{eta}/gi, eta + '')
|
92 | .replace(/\{eta_formatted}/gi, etaf + '')
|
93 | .replace(/\{duration}/gi, elapsedTime + '')
|
94 | .replace(/\{duration_formatted}/gi, elapsedTimef + '');
|
95 |
|
96 |
|
97 | if (this.lastDrawnString != s){
|
98 |
|
99 | _readline.cursorTo(this.stream, 0);
|
100 |
|
101 |
|
102 | this.stream.write(s);
|
103 |
|
104 |
|
105 | _readline.clearLine(this.stream, 1);
|
106 |
|
107 |
|
108 | this.lastDrawnString = s;
|
109 |
|
110 |
|
111 | this.lastRedraw = Date.now();
|
112 | }
|
113 |
|
114 |
|
115 | this.timer = setTimeout(this.render.bind(this), this.throttleTime*2);
|
116 | };
|
117 |
|
118 |
|
119 | Bar.prototype.formatTime = function (t, roundTo) {
|
120 | var round = function (input) {
|
121 | if (roundTo) {
|
122 | return roundTo * Math.round(input / roundTo);
|
123 | } else {
|
124 | return input
|
125 | }
|
126 | }
|
127 | if (t > 3600) {
|
128 | return Math.floor(t / 3600) + 'h' + round((t % 3600) / 60) + 'm';
|
129 | } else if (t > 60) {
|
130 | return Math.floor(t / 60) + 'm' + round((t % 60)) + 's';
|
131 | } else if (t > 10) {
|
132 | return round(t) + 's';
|
133 | } else {
|
134 | return t + 's';
|
135 | }
|
136 | }
|
137 |
|
138 |
|
139 | Bar.prototype.start = function(total, startValue){
|
140 |
|
141 | if (!this.stream.isTTY){
|
142 | return;
|
143 | }
|
144 |
|
145 |
|
146 | this.value = startValue || 0;
|
147 | this.total = total || 100;
|
148 | this.startTime = Date.now();
|
149 | this.lastDrawnString = '';
|
150 |
|
151 |
|
152 | if (this.hideCursor) {
|
153 | this.stream.write('\033[?25l');
|
154 | }
|
155 |
|
156 |
|
157 | this.stopTimer();
|
158 |
|
159 |
|
160 | this.render();
|
161 |
|
162 |
|
163 | this.eta = {
|
164 | valueBuffer: [this.value],
|
165 | timeBuffer: [this.startTime],
|
166 | time: null
|
167 | };
|
168 | };
|
169 |
|
170 |
|
171 | Bar.prototype.stop = function(){
|
172 |
|
173 | if (!this.timer) {
|
174 | return
|
175 | }
|
176 |
|
177 |
|
178 | this.render();
|
179 | this.stopTimer();
|
180 |
|
181 |
|
182 | if (this.clearOnComplete && this.stream.isTTY){
|
183 | _readline.cursorTo(this.stream, 0);
|
184 | _readline.clearLine(this.stream, 0);
|
185 | }else{
|
186 |
|
187 | this.stream.write('\n');
|
188 | }
|
189 |
|
190 |
|
191 | if (this.hideCursor) {
|
192 | this.stream.write('\033[?25h');
|
193 | }
|
194 | };
|
195 |
|
196 |
|
197 | Bar.prototype.update = function(current){
|
198 |
|
199 | this.value = current;
|
200 |
|
201 | this.eta.valueBuffer.push(current);
|
202 | this.eta.timeBuffer.push(Date.now());
|
203 |
|
204 |
|
205 | if (this.lastRedraw + this.throttleTime < Date.now()){
|
206 | this.calculateETA();
|
207 | this.render();
|
208 | }
|
209 | };
|
210 |
|
211 |
|
212 | Bar.prototype.increment = function(step){
|
213 | step = step || 1;
|
214 | this.update(this.value + step);
|
215 | };
|
216 |
|
217 |
|
218 | Bar.prototype.getTotal = function(){
|
219 | return this.total;
|
220 | };
|
221 |
|
222 |
|
223 | Bar.prototype.stopTimer = function(){
|
224 |
|
225 | if (this.timer) {
|
226 | clearTimeout(this.timer);
|
227 | }
|
228 | this.timer = null;
|
229 | };
|
230 |
|
231 |
|
232 | Bar.prototype.calculateETA = function(){
|
233 |
|
234 | var l = this.eta.valueBuffer.length;
|
235 | var buffer = Math.min(this.etaBuffer, l);
|
236 |
|
237 | var v_diff = this.eta.valueBuffer[l - 1] - this.eta.valueBuffer[l - buffer];
|
238 | var t_diff = this.eta.timeBuffer[l - 1] - this.eta.timeBuffer[l - buffer];
|
239 |
|
240 |
|
241 | var vt_rate = v_diff/t_diff;
|
242 |
|
243 |
|
244 | var remaining = this.total-this.value;
|
245 |
|
246 |
|
247 | var eta = remaining/vt_rate/1000;
|
248 |
|
249 | this.eta = {
|
250 | valueBuffer: this.eta.valueBuffer.slice(-this.etaBuffer),
|
251 | timeBuffer: this.eta.timeBuffer.slice(-this.etaBuffer),
|
252 | time: Math.ceil(eta)
|
253 | }
|
254 | };
|
255 |
|
256 | module.exports = Bar;
|