|
1 <?php |
|
2 /** |
|
3 * LICENSE |
|
4 * |
|
5 * This source file is subject to the new BSD license that is bundled |
|
6 * with this package in the file LICENSE.txt. |
|
7 * It is also available through the world-wide-web at this URL: |
|
8 * http://framework.zend.com/license/new-bsd |
|
9 * If you did not receive a copy of the license and are unable to |
|
10 * obtain it through the world-wide-web, please send an email |
|
11 * to license@zend.com so we can send you a copy immediately. |
|
12 * |
|
13 * @category Zend |
|
14 * @package Zend_ProgressBar |
|
15 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
16 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
17 * @version $Id: Console.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
18 */ |
|
19 |
|
20 /** |
|
21 * @see Zend_ProgressBar_Adapter |
|
22 */ |
|
23 require_once 'Zend/ProgressBar/Adapter.php'; |
|
24 |
|
25 /** |
|
26 * @see Zend_Text_MultiByte |
|
27 */ |
|
28 require_once 'Zend/Text/MultiByte.php'; |
|
29 |
|
30 /** |
|
31 * Zend_ProgressBar_Adapter_Console offers a text-based progressbar for console |
|
32 * applications |
|
33 * |
|
34 * @category Zend |
|
35 * @package Zend_ProgressBar |
|
36 * @uses Zend_ProgressBar_Adapter_Interface |
|
37 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
38 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
39 */ |
|
40 class Zend_ProgressBar_Adapter_Console extends Zend_ProgressBar_Adapter |
|
41 { |
|
42 /** |
|
43 * Percentage value of the progress |
|
44 */ |
|
45 const ELEMENT_PERCENT = 'ELEMENT_PERCENT'; |
|
46 |
|
47 /** |
|
48 * Visual value of the progress |
|
49 */ |
|
50 const ELEMENT_BAR = 'ELEMENT_BAR'; |
|
51 |
|
52 /** |
|
53 * ETA of the progress |
|
54 */ |
|
55 const ELEMENT_ETA = 'ELEMENT_ETA'; |
|
56 |
|
57 /** |
|
58 * Text part of the progress |
|
59 */ |
|
60 const ELEMENT_TEXT = 'ELEMENT_TEXT'; |
|
61 |
|
62 /** |
|
63 * Finish action: End of Line |
|
64 */ |
|
65 const FINISH_ACTION_EOL = 'FINISH_ACTION_EOL'; |
|
66 |
|
67 /** |
|
68 * Finish action: Clear Line |
|
69 */ |
|
70 const FINISH_ACTION_CLEAR_LINE = 'FINISH_ACTION_CLEAR_LINE'; |
|
71 |
|
72 /** |
|
73 * Finish action: None |
|
74 */ |
|
75 const FINISH_ACTION_NONE = 'FINISH_ACTION_NONE'; |
|
76 |
|
77 /** |
|
78 * Width of the progressbar |
|
79 * |
|
80 * @var integer |
|
81 */ |
|
82 protected $_width = null; |
|
83 |
|
84 /** |
|
85 * Elements to display |
|
86 * |
|
87 * @var array |
|
88 */ |
|
89 protected $_elements = array(self::ELEMENT_PERCENT, |
|
90 self::ELEMENT_BAR, |
|
91 self::ELEMENT_ETA); |
|
92 |
|
93 /** |
|
94 * Which action to do at finish call |
|
95 * |
|
96 * @var string |
|
97 */ |
|
98 protected $_finishAction = self::FINISH_ACTION_EOL; |
|
99 |
|
100 /** |
|
101 * Width of the bar element |
|
102 * |
|
103 * @var integer |
|
104 */ |
|
105 protected $_barWidth; |
|
106 |
|
107 /** |
|
108 * Left character(s) within the bar |
|
109 * |
|
110 * @var string |
|
111 */ |
|
112 protected $_barLeftChar = '#'; |
|
113 |
|
114 /** |
|
115 * Indicator character(s) within the bar |
|
116 * |
|
117 * @var string |
|
118 */ |
|
119 protected $_barIndicatorChar = ''; |
|
120 |
|
121 /** |
|
122 * Right character(s) within the bar |
|
123 * |
|
124 * @var string |
|
125 */ |
|
126 protected $_barRightChar = '-'; |
|
127 |
|
128 /** |
|
129 * Output-stream, when STDOUT is not defined (e.g. in CGI) or set manually |
|
130 * |
|
131 * @var resource |
|
132 */ |
|
133 protected $_outputStream = null; |
|
134 |
|
135 /** |
|
136 * Width of the text element |
|
137 * |
|
138 * @var string |
|
139 */ |
|
140 protected $_textWidth = 20; |
|
141 |
|
142 /** |
|
143 * Wether the output started yet or not |
|
144 * |
|
145 * @var boolean |
|
146 */ |
|
147 protected $_outputStarted = false; |
|
148 |
|
149 /** |
|
150 * Charset of text element |
|
151 * |
|
152 * @var string |
|
153 */ |
|
154 protected $_charset = 'utf-8'; |
|
155 |
|
156 /** |
|
157 * Defined by Zend_ProgressBar_Adapter |
|
158 * |
|
159 * @param null|array|Zend_Config $options |
|
160 */ |
|
161 public function __construct($options = null) |
|
162 { |
|
163 // Call parent constructor with options |
|
164 parent::__construct($options); |
|
165 |
|
166 // Check if a width was set, else use auto width |
|
167 if ($this->_width === null) { |
|
168 $this->setWidth(); |
|
169 } |
|
170 } |
|
171 |
|
172 /** |
|
173 * Close local stdout, when open |
|
174 */ |
|
175 public function __destruct() |
|
176 { |
|
177 if ($this->_outputStream !== null) { |
|
178 fclose($this->_outputStream); |
|
179 } |
|
180 } |
|
181 |
|
182 /** |
|
183 * Set a different output-stream |
|
184 * |
|
185 * @param string $resource |
|
186 * @return Zend_ProgressBar_Adapter_Console |
|
187 */ |
|
188 public function setOutputStream($resource) |
|
189 { |
|
190 $stream = @fopen($resource, 'w'); |
|
191 |
|
192 if ($stream === false) { |
|
193 require_once 'Zend/ProgressBar/Adapter/Exception.php'; |
|
194 throw new Zend_ProgressBar_Adapter_Exception('Unable to open stream'); |
|
195 } |
|
196 |
|
197 if ($this->_outputStream !== null) { |
|
198 fclose($this->_outputStream); |
|
199 } |
|
200 |
|
201 $this->_outputStream = $stream; |
|
202 } |
|
203 |
|
204 /** |
|
205 * Get the current output stream |
|
206 * |
|
207 * @return resource |
|
208 */ |
|
209 public function getOutputStream() |
|
210 { |
|
211 if ($this->_outputStream === null) { |
|
212 if (!defined('STDOUT')) { |
|
213 $this->_outputStream = fopen('php://stdout', 'w'); |
|
214 } else { |
|
215 return STDOUT; |
|
216 } |
|
217 } |
|
218 |
|
219 return $this->_outputStream; |
|
220 } |
|
221 |
|
222 /** |
|
223 * Set the width of the progressbar |
|
224 * |
|
225 * @param integer $width |
|
226 * @return Zend_ProgressBar_Adapter_Console |
|
227 */ |
|
228 public function setWidth($width = null) |
|
229 { |
|
230 if ($width === null || !is_integer($width)) { |
|
231 if (substr(PHP_OS, 0, 3) === 'WIN') { |
|
232 // We have to default to 79 on windows, because the windows |
|
233 // terminal always has a fixed width of 80 characters and the |
|
234 // cursor is counted to the line, else windows would line break |
|
235 // after every update. |
|
236 $this->_width = 79; |
|
237 } else { |
|
238 // Set the default width of 80 |
|
239 $this->_width = 80; |
|
240 |
|
241 // Try to determine the width through stty |
|
242 if (preg_match('#\d+ (\d+)#', @shell_exec('stty size'), $match) === 1) { |
|
243 $this->_width = (int) $match[1]; |
|
244 } else if (preg_match('#columns = (\d+);#', @shell_exec('stty'), $match) === 1) { |
|
245 $this->_width = (int) $match[1]; |
|
246 } |
|
247 } |
|
248 } else { |
|
249 $this->_width = (int) $width; |
|
250 } |
|
251 |
|
252 $this->_calculateBarWidth(); |
|
253 |
|
254 return $this; |
|
255 } |
|
256 |
|
257 /** |
|
258 * Set the elements to display with the progressbar |
|
259 * |
|
260 * @param array $elements |
|
261 * @throws Zend_ProgressBar_Adapter_Exception When an invalid element is foudn in the array |
|
262 * @return Zend_ProgressBar_Adapter_Console |
|
263 */ |
|
264 public function setElements(array $elements) |
|
265 { |
|
266 $allowedElements = array(self::ELEMENT_PERCENT, |
|
267 self::ELEMENT_BAR, |
|
268 self::ELEMENT_ETA, |
|
269 self::ELEMENT_TEXT); |
|
270 |
|
271 if (count(array_diff($elements, $allowedElements)) > 0) { |
|
272 require_once 'Zend/ProgressBar/Adapter/Exception.php'; |
|
273 throw new Zend_ProgressBar_Adapter_Exception('Invalid element found in $elements array'); |
|
274 } |
|
275 |
|
276 $this->_elements = $elements; |
|
277 |
|
278 $this->_calculateBarWidth(); |
|
279 |
|
280 return $this; |
|
281 } |
|
282 |
|
283 /** |
|
284 * Set the left-hand character for the bar |
|
285 * |
|
286 * @param string $char |
|
287 * @throws Zend_ProgressBar_Adapter_Exception When character is empty |
|
288 * @return Zend_ProgressBar_Adapter_Console |
|
289 */ |
|
290 public function setBarLeftChar($char) |
|
291 { |
|
292 if (empty($char)) { |
|
293 require_once 'Zend/ProgressBar/Adapter/Exception.php'; |
|
294 throw new Zend_ProgressBar_Adapter_Exception('Character may not be empty'); |
|
295 } |
|
296 |
|
297 $this->_barLeftChar = (string) $char; |
|
298 |
|
299 return $this; |
|
300 } |
|
301 |
|
302 /** |
|
303 * Set the right-hand character for the bar |
|
304 * |
|
305 * @param string $char |
|
306 * @throws Zend_ProgressBar_Adapter_Exception When character is empty |
|
307 * @return Zend_ProgressBar_Adapter_Console |
|
308 */ |
|
309 public function setBarRightChar($char) |
|
310 { |
|
311 if (empty($char)) { |
|
312 require_once 'Zend/ProgressBar/Adapter/Exception.php'; |
|
313 throw new Zend_ProgressBar_Adapter_Exception('Character may not be empty'); |
|
314 } |
|
315 |
|
316 $this->_barRightChar = (string) $char; |
|
317 |
|
318 return $this; |
|
319 } |
|
320 |
|
321 /** |
|
322 * Set the indicator character for the bar |
|
323 * |
|
324 * @param string $char |
|
325 * @return Zend_ProgressBar_Adapter_Console |
|
326 */ |
|
327 public function setBarIndicatorChar($char) |
|
328 { |
|
329 $this->_barIndicatorChar = (string) $char; |
|
330 |
|
331 return $this; |
|
332 } |
|
333 |
|
334 /** |
|
335 * Set the width of the text element |
|
336 * |
|
337 * @param integer $width |
|
338 * @return Zend_ProgressBar_Adapter_Console |
|
339 */ |
|
340 public function setTextWidth($width) |
|
341 { |
|
342 $this->_textWidth = (int) $width; |
|
343 |
|
344 $this->_calculateBarWidth(); |
|
345 |
|
346 return $this; |
|
347 } |
|
348 |
|
349 /** |
|
350 * Set the charset of the text element |
|
351 * |
|
352 * @param string $charset |
|
353 */ |
|
354 public function setCharset($charset) |
|
355 { |
|
356 $this->_charset = $charset; |
|
357 } |
|
358 |
|
359 /** |
|
360 * Set the finish action |
|
361 * |
|
362 * @param string $action |
|
363 * @throws Zend_ProgressBar_Adapter_Exception When an invalid action is specified |
|
364 * @return Zend_ProgressBar_Adapter_Console |
|
365 */ |
|
366 public function setFinishAction($action) |
|
367 { |
|
368 $allowedActions = array(self::FINISH_ACTION_CLEAR_LINE, |
|
369 self::FINISH_ACTION_EOL, |
|
370 self::FINISH_ACTION_NONE); |
|
371 |
|
372 if (!in_array($action, $allowedActions)) { |
|
373 require_once 'Zend/ProgressBar/Adapter/Exception.php'; |
|
374 throw new Zend_ProgressBar_Adapter_Exception('Invalid finish action specified'); |
|
375 } |
|
376 |
|
377 $this->_finishAction = $action; |
|
378 |
|
379 return $this; |
|
380 } |
|
381 |
|
382 /** |
|
383 * Defined by Zend_ProgressBar_Adapter_Interface |
|
384 * |
|
385 * @param float $current Current progress value |
|
386 * @param float $max Max progress value |
|
387 * @param float $percent Current percent value |
|
388 * @param integer $timeTaken Taken time in seconds |
|
389 * @param integer $timeRemaining Remaining time in seconds |
|
390 * @param string $text Status text |
|
391 * @return void |
|
392 */ |
|
393 public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text) |
|
394 { |
|
395 // See if we must clear the line |
|
396 if ($this->_outputStarted) { |
|
397 $data = str_repeat("\x08", $this->_width); |
|
398 } else { |
|
399 $data = ''; |
|
400 $this->_outputStarted = true; |
|
401 } |
|
402 |
|
403 // Build all elements |
|
404 $renderedElements = array(); |
|
405 |
|
406 foreach ($this->_elements as $element) { |
|
407 switch ($element) { |
|
408 case self::ELEMENT_BAR: |
|
409 $visualWidth = $this->_barWidth - 2; |
|
410 $bar = '['; |
|
411 |
|
412 $indicatorWidth = strlen($this->_barIndicatorChar); |
|
413 |
|
414 $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent)); |
|
415 if ($doneWidth > 0) { |
|
416 $bar .= substr(str_repeat($this->_barLeftChar, ceil($doneWidth / strlen($this->_barLeftChar))), 0, $doneWidth); |
|
417 } |
|
418 |
|
419 $bar .= $this->_barIndicatorChar; |
|
420 |
|
421 $leftWidth = $visualWidth - $doneWidth - $indicatorWidth; |
|
422 if ($leftWidth > 0) { |
|
423 $bar .= substr(str_repeat($this->_barRightChar, ceil($leftWidth / strlen($this->_barRightChar))), 0, $leftWidth); |
|
424 } |
|
425 |
|
426 $bar .= ']'; |
|
427 |
|
428 $renderedElements[] = $bar; |
|
429 break; |
|
430 |
|
431 case self::ELEMENT_PERCENT: |
|
432 $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT) . '%'; |
|
433 break; |
|
434 |
|
435 case self::ELEMENT_ETA: |
|
436 // In the first 5 seconds we don't get accurate results, |
|
437 // this skipping technique is found in many progressbar |
|
438 // implementations. |
|
439 if ($timeTaken < 5) { |
|
440 $renderedElements[] = str_repeat(' ', 12); |
|
441 break; |
|
442 } |
|
443 |
|
444 if ($timeRemaining === null || $timeRemaining > 86400) { |
|
445 $etaFormatted = '??:??:??'; |
|
446 } else { |
|
447 $hours = floor($timeRemaining / 3600); |
|
448 $minutes = floor(($timeRemaining % 3600) / 60); |
|
449 $seconds = ($timeRemaining % 3600 % 60); |
|
450 |
|
451 $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); |
|
452 } |
|
453 |
|
454 $renderedElements[] = 'ETA ' . $etaFormatted; |
|
455 break; |
|
456 |
|
457 case self::ELEMENT_TEXT: |
|
458 $renderedElements[] = Zend_Text_MultiByte::strPad(substr($text, 0, $this->_textWidth), $this->_textWidth, ' ', STR_PAD_RIGHT, $this->_charset); |
|
459 break; |
|
460 } |
|
461 } |
|
462 |
|
463 $data .= implode(' ', $renderedElements); |
|
464 |
|
465 // Output line data |
|
466 $this->_outputData($data); |
|
467 } |
|
468 |
|
469 /** |
|
470 * Defined by Zend_ProgressBar_Adapter_Interface |
|
471 * |
|
472 * @return void |
|
473 */ |
|
474 public function finish() |
|
475 { |
|
476 switch ($this->_finishAction) { |
|
477 case self::FINISH_ACTION_EOL: |
|
478 $this->_outputData(PHP_EOL); |
|
479 break; |
|
480 |
|
481 case self::FINISH_ACTION_CLEAR_LINE: |
|
482 if ($this->_outputStarted) { |
|
483 $data = str_repeat("\x08", $this->_width) |
|
484 . str_repeat(' ', $this->_width) |
|
485 . str_repeat("\x08", $this->_width); |
|
486 |
|
487 $this->_outputData($data); |
|
488 } |
|
489 break; |
|
490 |
|
491 case self::FINISH_ACTION_NONE: |
|
492 break; |
|
493 } |
|
494 } |
|
495 |
|
496 /** |
|
497 * Calculate the bar width when other elements changed |
|
498 * |
|
499 * @return void |
|
500 */ |
|
501 protected function _calculateBarWidth() |
|
502 { |
|
503 if (in_array(self::ELEMENT_BAR, $this->_elements)) { |
|
504 $barWidth = $this->_width; |
|
505 |
|
506 if (in_array(self::ELEMENT_PERCENT, $this->_elements)) { |
|
507 $barWidth -= 4; |
|
508 } |
|
509 |
|
510 if (in_array(self::ELEMENT_ETA, $this->_elements)) { |
|
511 $barWidth -= 12; |
|
512 } |
|
513 |
|
514 if (in_array(self::ELEMENT_TEXT, $this->_elements)) { |
|
515 $barWidth -= $this->_textWidth; |
|
516 } |
|
517 |
|
518 $this->_barWidth = $barWidth - (count($this->_elements) - 1); |
|
519 } |
|
520 } |
|
521 |
|
522 /** |
|
523 * Outputs given data to STDOUT. |
|
524 * |
|
525 * This split-off is required for unit-testing. |
|
526 * |
|
527 * @param string $data |
|
528 * @return void |
|
529 */ |
|
530 protected function _outputData($data) |
|
531 { |
|
532 fwrite($this->getOutputStream(), $data); |
|
533 } |
|
534 } |