1
|
|
|
<?php
|
2
|
|
|
/**
|
3
|
|
|
* This class creates and maintains progress bars to be printed to the console.
|
4
|
|
|
* This file is a replica of the ezComponents console progress bar class (@link http://ezcomponents.org/docs/api/latest/ConsoleTools/ezcConsoleProgressbar.html)
|
5
|
|
|
* allows a developer to just use the console progress bar features without the rest of the classes saving all the extra files
|
6
|
|
|
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
|
7
|
|
|
* @license http://ez.no/licenses/new_bsd New BSD License
|
8
|
|
|
*/
|
9
|
|
|
|
10
|
|
|
/**
|
11
|
|
|
* Creating and maintaining progress-bars to be printed to the console.
|
12
|
|
|
* <code>
|
13
|
|
|
* // Create progress bar itself
|
14
|
|
|
* $progress = new ConsoleProgressBar(100);
|
15
|
|
|
* // Perform actions
|
16
|
|
|
* $i = 0;
|
17
|
|
|
* while ( $i++ < 100 )
|
18
|
|
|
* {
|
19
|
|
|
* // Do whatever you want to indicate progress for
|
20
|
|
|
* // Advance the progressbar by one step
|
21
|
|
|
* $progress->advance();
|
22
|
|
|
* }
|
23
|
|
|
* // Finish progress bar and jump to next line.
|
24
|
|
|
* $progress->finish();
|
25
|
|
|
* </code>
|
26
|
|
|
*/
|
27
|
|
|
class ConsoleProgressBar
|
28
|
|
|
{
|
29
|
|
|
|
30
|
|
|
/**
|
31
|
|
|
* The current step the progress bar should show.
|
32
|
|
|
* @var int
|
33
|
|
|
*/
|
34
|
|
|
protected $currentStep = 0;
|
35
|
|
|
|
36
|
|
|
/**
|
37
|
|
|
* The maximum number of steps to go.
|
38
|
|
|
* Calculated once from the settings.
|
39
|
|
|
* @var int
|
40
|
|
|
*/
|
41
|
|
|
protected $numSteps = 0;
|
42
|
|
|
|
43
|
|
|
/**
|
44
|
|
|
* Indicates if the starting point for the bar has been stored.
|
45
|
|
|
* Per default this is false to indicate that no start position has been
|
46
|
|
|
* stored, yet.
|
47
|
|
|
* @var bool
|
48
|
|
|
*/
|
49
|
|
|
protected $started = false;
|
50
|
|
|
|
51
|
|
|
/**
|
52
|
|
|
* Whether a position has been stored before, using the storePos() method.
|
53
|
|
|
* @var bool
|
54
|
|
|
*/
|
55
|
|
|
protected $positionStored = false;
|
56
|
|
|
|
57
|
|
|
/**
|
58
|
|
|
* Container to hold the options
|
59
|
|
|
* @var array(string=>mixed)
|
60
|
|
|
*/
|
61
|
|
|
protected $options = array(
|
62
|
|
|
'barChar' => "=",
|
63
|
|
|
'emptyChar' => "-",
|
64
|
|
|
'formatString' => "%act% / %max% [%bar%] %fraction%% %memory%",
|
65
|
|
|
'fractionFormat' => "%01.2f",
|
66
|
|
|
'progressChar' => ">",
|
67
|
|
|
'redrawFrequency' => 1,
|
68
|
|
|
'step' => 1,
|
69
|
|
|
'width' => 100,
|
70
|
|
|
'actFormat' => '%.0f',
|
71
|
|
|
'maxFormat' => '%.0f',
|
72
|
|
|
'max' => 100,
|
73
|
|
|
);
|
74
|
|
|
|
75
|
|
|
/**
|
76
|
|
|
* Storage for actual values to be replaced in the format string.
|
77
|
|
|
* Actual values are stored here and will be inserted into the bar
|
78
|
|
|
* before printing it.
|
79
|
|
|
* @var array(string=>string)
|
80
|
|
|
*/
|
81
|
|
|
protected $valueMap = array(
|
82
|
|
|
'bar' => '',
|
83
|
|
|
'fraction' => '',
|
84
|
|
|
'act' => '',
|
85
|
|
|
'max' => '',
|
86
|
|
|
'memory' => ''
|
87
|
|
|
);
|
88
|
|
|
|
89
|
|
|
/**
|
90
|
|
|
* Stores the bar utilization.
|
91
|
|
|
* This array saves how much space a specific part of the bar utilizes to not
|
92
|
|
|
* recalculate those on every step.
|
93
|
|
|
* @var array(string=>int)
|
94
|
|
|
*/
|
95
|
|
|
protected $measures = array(
|
96
|
|
|
'barSpace' => 0,
|
97
|
|
|
'fractionSpace' => 0,
|
98
|
|
|
'actSpace' => 0,
|
99
|
|
|
'maxSpace' => 0,
|
100
|
|
|
'fixedCharSpace' => 0,
|
101
|
|
|
);
|
102
|
|
|
|
103
|
|
|
/**
|
104
|
|
|
* Creates a new progress bar.
|
105
|
|
|
*
|
106
|
|
|
* @param int $max Maximum value, where progressbar
|
107
|
|
|
* reaches 100%.
|
108
|
|
|
* @param array(string=>string) $options Options
|
|
|
|
|
109
|
|
|
*/
|
110
|
|
|
public function __construct($max = null, $options = array())
|
111
|
|
|
{
|
112
|
|
|
if( $max )
|
|
|
|
|
113
|
|
|
{
|
114
|
|
|
$this->options['max'] = $max;
|
115
|
|
|
}
|
116
|
|
|
|
117
|
|
|
if( $options && count($options) )
|
|
|
|
|
118
|
|
|
{
|
119
|
|
|
$this->options = array_merge($this->options, $options);
|
120
|
|
|
}
|
121
|
|
|
}
|
122
|
|
|
|
123
|
|
|
/**
|
124
|
|
|
* Set new options.
|
125
|
|
|
* This method allows you to change the options of progressbar.
|
126
|
|
|
*
|
127
|
|
|
* @param $key either an array of key=>value pairs or a string of the option
|
128
|
|
|
* @param $value only needed if the key is a string
|
129
|
|
|
*/
|
130
|
|
|
public function setOptions($key, $value = null)
|
131
|
|
|
{
|
132
|
|
|
if( is_array($key) )
|
133
|
|
|
{
|
134
|
|
|
$this->options = array_merge($this->options, $key);
|
135
|
|
|
}
|
136
|
|
|
else
|
137
|
|
|
{
|
138
|
|
|
$this->__set($key, $value);
|
139
|
|
|
}
|
140
|
|
|
}
|
141
|
|
|
|
142
|
|
|
/**
|
143
|
|
|
* Returns the current options.
|
144
|
|
|
* Returns the options currently set for this progressbar.
|
145
|
|
|
* @return The current options.
|
146
|
|
|
*/
|
147
|
|
|
public function getOptions()
|
148
|
|
|
{
|
149
|
|
|
return $this->options;
|
150
|
|
|
}
|
151
|
|
|
|
152
|
|
|
/**
|
153
|
|
|
* Property read access.
|
154
|
|
|
*
|
155
|
|
|
* @param string $key Name of the property.
|
156
|
|
|
*
|
157
|
|
|
* @return mixed Value of the property or null.
|
158
|
|
|
*/
|
159
|
|
|
public function __get($key)
|
160
|
|
|
{
|
161
|
|
|
switch($key)
|
162
|
|
|
{
|
163
|
|
|
case 'options':
|
164
|
|
|
return $this->options;
|
165
|
|
|
case 'step':
|
166
|
|
|
// Step is now an option
|
167
|
|
|
return $this->options['step'];
|
168
|
|
|
case 'max':
|
169
|
|
|
return $this->options['max'];
|
170
|
|
|
default:
|
171
|
|
|
if( isset($this->options[$key]) )
|
172
|
|
|
{
|
173
|
|
|
return $this->options[$key];
|
174
|
|
|
}
|
175
|
|
|
break;
|
176
|
|
|
}
|
177
|
|
|
throw new Exception(sprintf("%s does not exists", $key));
|
178
|
|
|
}
|
179
|
|
|
|
180
|
|
|
/**
|
181
|
|
|
* Property write access.
|
182
|
|
|
*
|
183
|
|
|
* @param string $key Name of the property.
|
184
|
|
|
* @param mixed $val The value for the property.
|
185
|
|
|
*
|
186
|
|
|
* @throws Exception
|
187
|
|
|
* If a desired property could not be found.
|
188
|
|
|
*/
|
189
|
|
|
public function __set($key, $val)
|
190
|
|
|
{
|
191
|
|
|
switch($key)
|
192
|
|
|
{
|
193
|
|
|
case 'options':
|
194
|
|
|
$this->setOptions($val);
|
195
|
|
|
break;
|
196
|
|
View Code Duplication |
case 'max':
|
|
|
|
|
197
|
|
|
if( (!is_int($val) && !is_float($val)) || $val < 0 )
|
198
|
|
|
{
|
199
|
|
|
throw new Exception(sprintf("%s must be a number greater then 0. Value set: %s", $key, $val));
|
200
|
|
|
}
|
201
|
|
|
break;
|
202
|
|
View Code Duplication |
case 'step':
|
|
|
|
|
203
|
|
|
if( (!is_int($val) && !is_float($val)) || $val < 0 )
|
204
|
|
|
{
|
205
|
|
|
throw new Exception(sprintf("%s must be a number greater then 0. Value set: %s", $key, $val));
|
206
|
|
|
}
|
207
|
|
|
// Step is now an option.
|
208
|
|
|
$this->options['step'] = $val;
|
209
|
|
|
|
210
|
|
|
return;
|
211
|
|
|
default:
|
212
|
|
|
throw new Exception(sprintf("%s does not exists", $key));
|
213
|
|
|
break;
|
|
|
|
|
214
|
|
|
}
|
215
|
|
|
// Changes settings or options, need for recalculating measures
|
216
|
|
|
$this->started = false;
|
217
|
|
|
$this->options[$key] = $val;
|
218
|
|
|
}
|
219
|
|
|
|
220
|
|
|
/**
|
221
|
|
|
* Property isset access.
|
222
|
|
|
*
|
223
|
|
|
* @param string $key Name of the property.
|
224
|
|
|
*
|
225
|
|
|
* @return bool True is the property is set, otherwise false.
|
226
|
|
|
*/
|
227
|
|
|
public function __isset($key)
|
228
|
|
|
{
|
229
|
|
|
switch($key)
|
230
|
|
|
{
|
231
|
|
|
case 'options':
|
232
|
|
|
case 'max':
|
233
|
|
|
case 'step':
|
234
|
|
|
return true;
|
235
|
|
|
}
|
236
|
|
|
|
237
|
|
|
return false;
|
238
|
|
|
}
|
239
|
|
|
|
240
|
|
|
/**
|
241
|
|
|
* Start the progress bar
|
242
|
|
|
* Starts the progress bar and sticks it to the current line.
|
243
|
|
|
* No output will be done yet.
|
244
|
|
|
* to print the bar.
|
245
|
|
|
* @return void
|
246
|
|
|
*/
|
247
|
|
|
public function start()
|
248
|
|
|
{
|
249
|
|
|
$this->calculateMeasures();
|
250
|
|
|
$this->storePos();
|
251
|
|
|
$this->started = true;
|
252
|
|
|
}
|
253
|
|
|
|
254
|
|
|
/**
|
255
|
|
|
* Advance the progress bar.
|
256
|
|
|
* Advances the progress bar by $step steps. Redraws the bar by default,
|
257
|
|
|
* using the output method.
|
258
|
|
|
*
|
259
|
|
|
* @param bool $redraw Whether to redraw the bar immediately.
|
260
|
|
|
* @param int $step How many steps to advance.
|
261
|
|
|
*
|
262
|
|
|
* @return void
|
263
|
|
|
*/
|
264
|
|
|
public function advance($redraw = true, $step = 1)
|
265
|
|
|
{
|
266
|
|
|
$this->currentStep += $step;
|
267
|
|
|
if( $redraw === true && $this->currentStep % $this->options['redrawFrequency'] === 0 )
|
268
|
|
|
{
|
269
|
|
|
$this->output();
|
270
|
|
|
}
|
271
|
|
|
}
|
272
|
|
|
|
273
|
|
|
public function setValueMap($key, $value)
|
274
|
|
|
{
|
275
|
|
|
$this->valueMap[$key] = $value;
|
276
|
|
|
}
|
277
|
|
|
|
278
|
|
|
/**
|
279
|
|
|
* Finish the progress bar.
|
280
|
|
|
* Finishes the bar (jump to 100% if not happened yet,...) and jumps
|
281
|
|
|
* to the next line to allow new output. Also resets the values of the
|
282
|
|
|
* @return void
|
283
|
|
|
*/
|
284
|
|
|
public function finish()
|
285
|
|
|
{
|
286
|
|
|
$this->currentStep = $this->numSteps;
|
287
|
|
|
$this->output();
|
288
|
|
|
echo PHP_EOL;
|
289
|
|
|
}
|
290
|
|
|
|
291
|
|
|
/**
|
292
|
|
|
* Draw the progress bar.
|
293
|
|
|
* Prints the progress-bar to the screen. If start() has not been called
|
294
|
|
|
* yet, the current line is used for start
|
295
|
|
|
* @return void
|
296
|
|
|
*/
|
297
|
|
|
protected function output()
|
298
|
|
|
{
|
299
|
|
|
if( $this->started === false )
|
300
|
|
|
{
|
301
|
|
|
$this->start();
|
302
|
|
|
}
|
303
|
|
|
|
304
|
|
|
$this->restorePos();
|
305
|
|
|
if( $this->isWindows() )
|
306
|
|
|
{
|
307
|
|
|
echo str_repeat("\x8", $this->options['width']);
|
308
|
|
|
}
|
309
|
|
|
|
310
|
|
|
$this->generateValues();
|
311
|
|
|
echo $this->insertValues();
|
312
|
|
|
}
|
313
|
|
|
|
314
|
|
|
/**
|
315
|
|
|
* Stores the current cursor position.
|
316
|
|
|
* Saves the current cursor position to return to it using
|
317
|
|
|
* restorePos. Multiple calls
|
318
|
|
|
* to this method will override each other. Only the last
|
319
|
|
|
* position is saved.
|
320
|
|
|
* @return void
|
321
|
|
|
*/
|
322
|
|
|
protected function storePos()
|
323
|
|
|
{
|
324
|
|
|
if( !$this->isWindows() )
|
325
|
|
|
{
|
326
|
|
|
echo "\0337";
|
327
|
|
|
$this->positionStored = true;
|
328
|
|
|
}
|
329
|
|
|
}
|
330
|
|
|
|
331
|
|
|
/**
|
332
|
|
|
* Restores a cursor position.
|
333
|
|
|
* Restores the cursor position last saved using storePos.
|
334
|
|
|
* @throws Exception
|
335
|
|
|
* If no position is saved.
|
336
|
|
|
* @return void
|
337
|
|
|
*/
|
338
|
|
|
|
339
|
|
|
protected function restorePos()
|
340
|
|
|
{
|
341
|
|
|
if( !$this->isWindows() )
|
342
|
|
|
{
|
343
|
|
|
if( $this->positionStored === false )
|
344
|
|
|
{
|
345
|
|
|
throw new Exception("Progress Bar position was not stored.");
|
346
|
|
|
}
|
347
|
|
|
echo "\0338";
|
348
|
|
|
}
|
349
|
|
|
}
|
350
|
|
|
|
351
|
|
|
/**
|
352
|
|
|
* Generate all values to be replaced in the format string.
|
353
|
|
|
* @return void
|
354
|
|
|
*/
|
355
|
|
|
protected function generateValues()
|
356
|
|
|
{
|
357
|
|
|
// Bar
|
358
|
|
|
$barFilledSpace = ceil($this->measures['barSpace'] / $this->numSteps * $this->currentStep);
|
359
|
|
|
// Sanitize value if it gets to large by rounding
|
360
|
|
|
$barFilledSpace = $barFilledSpace > $this->measures['barSpace'] ? $this->measures['barSpace'] : $barFilledSpace;
|
361
|
|
|
$bar = $this->strPad(
|
362
|
|
|
$this->strPad(
|
363
|
|
|
$this->options['progressChar'],
|
364
|
|
|
$barFilledSpace,
|
365
|
|
|
$this->options['barChar'],
|
366
|
|
|
STR_PAD_LEFT
|
367
|
|
|
),
|
368
|
|
|
$this->measures['barSpace'],
|
369
|
|
|
$this->options['emptyChar'],
|
370
|
|
|
STR_PAD_RIGHT
|
371
|
|
|
);
|
372
|
|
|
$this->valueMap['bar'] = $bar;
|
373
|
|
|
|
374
|
|
|
// Fraction
|
375
|
|
|
$fractionVal = sprintf(
|
376
|
|
|
$this->options['fractionFormat'],
|
377
|
|
|
($fractionVal = ($this->options['step'] * $this->currentStep) / $this->options['max'] * 100) > 100 ? 100 : $fractionVal
|
378
|
|
|
);
|
379
|
|
|
$this->valueMap['fraction'] = $this->strPad(
|
380
|
|
|
$fractionVal,
|
381
|
|
|
iconv_strlen(sprintf($this->options['fractionFormat'], 100), 'UTF-8'),
|
382
|
|
|
' ',
|
383
|
|
|
STR_PAD_LEFT
|
384
|
|
|
);
|
385
|
|
|
|
386
|
|
|
// Act / max
|
387
|
|
|
$actVal = sprintf(
|
388
|
|
|
$this->options['actFormat'],
|
389
|
|
|
($actVal = $this->currentStep * $this->options['step']) > $this->options['max'] ? $this->options['max'] : $actVal
|
390
|
|
|
);
|
391
|
|
|
$this->valueMap['act'] = $this->strPad(
|
392
|
|
|
$actVal,
|
393
|
|
|
iconv_strlen(sprintf($this->options['actFormat'], $this->options['max']), 'UTF-8'),
|
394
|
|
|
' ',
|
395
|
|
|
STR_PAD_LEFT
|
396
|
|
|
);
|
397
|
|
|
$this->valueMap['max'] = sprintf($this->options['maxFormat'], $this->options['max']);
|
398
|
|
|
}
|
399
|
|
|
|
400
|
|
|
/**
|
401
|
|
|
* Insert values into bar format string.
|
402
|
|
|
* @return void
|
403
|
|
|
*/
|
404
|
|
|
protected function insertValues()
|
405
|
|
|
{
|
406
|
|
|
$bar = $this->options['formatString'];
|
407
|
|
|
foreach($this->valueMap as $name => $val)
|
408
|
|
|
{
|
409
|
|
|
$bar = str_replace("%{$name}%", $val, $bar);
|
410
|
|
|
}
|
411
|
|
|
|
412
|
|
|
return $bar;
|
413
|
|
|
}
|
414
|
|
|
|
415
|
|
|
/**
|
416
|
|
|
* Calculate several measures necessary to generate a bar.
|
417
|
|
|
* @return void
|
418
|
|
|
*/
|
419
|
|
|
protected function calculateMeasures()
|
420
|
|
|
{
|
421
|
|
|
// Calc number of steps bar goes through
|
422
|
|
|
$this->numSteps = ( int )round($this->options['max'] / $this->options['step']);
|
423
|
|
|
// Calculate measures
|
424
|
|
|
$this->measures['fixedCharSpace'] = iconv_strlen($this->stripEscapeSequences($this->insertValues()), 'UTF-8');
|
425
|
|
View Code Duplication |
if( iconv_strpos($this->options['formatString'], '%max%', 0, 'UTF-8') !== false )
|
|
|
|
|
426
|
|
|
{
|
427
|
|
|
$this->measures['maxSpace'] = iconv_strlen(sprintf($this->options['maxFormat'], $this->options['max']), 'UTF-8');
|
428
|
|
|
}
|
429
|
|
View Code Duplication |
if( iconv_strpos($this->options['formatString'], '%act%', 0, 'UTF-8') !== false )
|
|
|
|
|
430
|
|
|
{
|
431
|
|
|
$this->measures['actSpace'] = iconv_strlen(sprintf($this->options['actFormat'], $this->options['max']), 'UTF-8');
|
432
|
|
|
}
|
433
|
|
|
if( iconv_strpos($this->options['formatString'], '%fraction%', 0, 'UTF-8') !== false )
|
434
|
|
|
{
|
435
|
|
|
$this->measures['fractionSpace'] = iconv_strlen(sprintf($this->options['fractionFormat'], 100), 'UTF-8');
|
436
|
|
|
}
|
437
|
|
|
$this->measures['barSpace'] = $this->options['width'] - array_sum($this->measures);
|
438
|
|
|
}
|
439
|
|
|
|
440
|
|
|
/**
|
441
|
|
|
* Strip all escape sequences from a string to measure it's size correctly.
|
442
|
|
|
*
|
443
|
|
|
* @param mixed $str
|
444
|
|
|
*
|
445
|
|
|
* @return void
|
446
|
|
|
*/
|
447
|
|
|
protected function stripEscapeSequences($str)
|
448
|
|
|
{
|
449
|
|
|
return preg_replace('/\033\[[0-9a-f;]*m/i', '', $str);
|
450
|
|
|
}
|
451
|
|
|
|
452
|
|
|
/**
|
453
|
|
|
* Check if we currently running under windows
|
454
|
|
|
* @return bool
|
455
|
|
|
*/
|
456
|
|
|
protected function isWindows()
|
457
|
|
|
{
|
458
|
|
|
return stripos(PHP_OS, 'windows') !== false;
|
459
|
|
|
}
|
460
|
|
|
|
461
|
|
|
/**
|
462
|
|
|
* Binary safe str_pad() replacement.
|
463
|
|
|
* This method is a multi-byte encoding safe replacement for the PHP
|
464
|
|
|
* function str_pad(). It mimics exactly the behavior of str_pad(), but
|
465
|
|
|
* uses iconv_* functions with UTF-8 encoding. The parameters received by
|
466
|
|
|
* this method equal the parameters of {@link http://php.net/str_pad
|
467
|
|
|
* str_pad()}. Note: Make sure to hand only UTF-8 encoded content to this
|
468
|
|
|
* method.
|
469
|
|
|
*
|
470
|
|
|
* @param string $input
|
471
|
|
|
* @param int $padLength
|
472
|
|
|
* @param string $padString
|
473
|
|
|
* @param int $padType
|
474
|
|
|
*
|
475
|
|
|
* @return string
|
476
|
|
|
*/
|
477
|
|
|
protected function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT)
|
478
|
|
|
{
|
479
|
|
|
$input = (string)$input;
|
480
|
|
|
|
481
|
|
|
$strLen = iconv_strlen($input, 'UTF-8');
|
482
|
|
|
$padStrLen = iconv_strlen($padString, 'UTF-8');
|
483
|
|
|
|
484
|
|
|
if( $strLen >= $padLength )
|
485
|
|
|
{
|
486
|
|
|
return $input;
|
487
|
|
|
}
|
488
|
|
|
|
489
|
|
|
if( $padType === STR_PAD_BOTH )
|
490
|
|
|
{
|
491
|
|
|
return $this->strPad(
|
492
|
|
|
$this->strPad(
|
493
|
|
|
$input,
|
494
|
|
|
$strLen + ceil(($padLength - $strLen) / 2),
|
495
|
|
|
$padString
|
496
|
|
|
),
|
497
|
|
|
$padLength,
|
498
|
|
|
$padString,
|
499
|
|
|
STR_PAD_LEFT
|
500
|
|
|
);
|
501
|
|
|
}
|
502
|
|
|
|
503
|
|
|
$fullStrRepeats = (int)(($padLength - $strLen) / $padStrLen);
|
504
|
|
|
$partlyPad = iconv_substr(
|
505
|
|
|
$padString,
|
506
|
|
|
0,
|
507
|
|
|
(($padLength - $strLen) % $padStrLen)
|
508
|
|
|
);
|
509
|
|
|
|
510
|
|
|
$padding = str_repeat($padString, $fullStrRepeats).$partlyPad;
|
511
|
|
|
|
512
|
|
|
switch($padType)
|
513
|
|
|
{
|
514
|
|
|
case STR_PAD_LEFT:
|
515
|
|
|
return $padding.$input;
|
516
|
|
|
case STR_PAD_RIGHT:
|
517
|
|
|
default:
|
518
|
|
|
return $input.$padding;
|
519
|
|
|
}
|
520
|
|
|
}
|
521
|
|
|
} |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.