3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\ProgressBar\Adapter
;
12 use Zend\Stdlib\ErrorHandler
;
13 use Zend\Stdlib\StringUtils
;
16 * Zend\ProgressBar\Adapter\Console offers a text-based progressbar for console
19 class Console
extends AbstractAdapter
22 * Percentage value of the progress
24 const ELEMENT_PERCENT
= 'ELEMENT_PERCENT';
27 * Visual value of the progress
29 const ELEMENT_BAR
= 'ELEMENT_BAR';
34 const ELEMENT_ETA
= 'ELEMENT_ETA';
37 * Text part of the progress
39 const ELEMENT_TEXT
= 'ELEMENT_TEXT';
42 * Finish action: End of Line
44 const FINISH_ACTION_EOL
= 'FINISH_ACTION_EOL';
47 * Finish action: Clear Line
49 const FINISH_ACTION_CLEAR_LINE
= 'FINISH_ACTION_CLEAR_LINE';
54 const FINISH_ACTION_NONE
= 'FINISH_ACTION_NONE';
57 * Width of the progressbar
61 protected $width = null;
68 protected $elements = [
69 self
::ELEMENT_PERCENT
,
75 * Which action to do at finish call
79 protected $finishAction = self
::FINISH_ACTION_EOL
;
82 * Width of the bar element
89 * Left character(s) within the bar
93 protected $barLeftChar = '#';
96 * Indicator character(s) within the bar
100 protected $barIndicatorChar = '';
103 * Right character(s) within the bar
107 protected $barRightChar = '-';
110 * Output-stream, when STDOUT is not defined (e.g. in CGI) or set manually
114 protected $outputStream = null;
117 * Width of the text element
121 protected $textWidth = 20;
124 * Whether the output started yet or not
128 protected $outputStarted = false;
131 * Charset of text element
135 protected $charset = 'utf-8';
138 * Defined by Zend\ProgressBar adapter
140 * @param array|\Traversable $options
142 public function __construct($options = null)
144 // Call parent constructor with options
145 parent
::__construct($options);
147 // Check if a width was set, else use auto width
148 if ($this->width
=== null) {
154 * Close local stdout, when open
156 public function __destruct()
158 if ($this->outputStream
!== null) {
159 fclose($this->outputStream
);
164 * Set a different output-stream
166 * @param string $resource
167 * @throws Exception\RuntimeException
168 * @return \Zend\ProgressBar\Adapter\Console
170 public function setOutputStream($resource)
172 ErrorHandler
::start();
173 $stream = fopen($resource, 'w');
174 $error = ErrorHandler
::stop();
176 if ($stream === false) {
177 throw new Exception\
RuntimeException('Unable to open stream', 0, $error);
180 if ($this->outputStream
!== null) {
181 fclose($this->outputStream
);
184 $this->outputStream
= $stream;
188 * Get the current output stream
192 public function getOutputStream()
194 if ($this->outputStream
=== null) {
195 if (! defined('STDOUT')) {
196 $this->outputStream
= fopen('php://stdout', 'w');
202 return $this->outputStream
;
206 * Set the width of the progressbar
209 * @return \Zend\ProgressBar\Adapter\Console
211 public function setWidth($width = null)
213 if ($width === null ||
! is_int($width)) {
214 if (substr(PHP_OS
, 0, 3) === 'WIN') {
215 // We have to default to 79 on windows, because the windows
216 // terminal always has a fixed width of 80 characters and the
217 // cursor is counted to the line, else windows would line break
218 // after every update.
221 // Set the default width of 80
224 // Try to determine the width through stty
225 ErrorHandler
::start();
226 if (preg_match('#\d+ (\d+)#', shell_exec('stty size'), $match) === 1) {
227 $this->width
= (int) $match[1];
228 } elseif (preg_match('#columns = (\d+);#', shell_exec('stty'), $match) === 1) {
229 $this->width
= (int) $match[1];
231 ErrorHandler
::stop();
234 $this->width
= (int) $width;
237 $this->_calculateBarWidth();
243 * Set the elements to display with the progressbar
245 * @param array $elements
246 * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid element is found
248 * @return \Zend\ProgressBar\Adapter\Console
250 public function setElements(array $elements)
252 $allowedElements = [self
::ELEMENT_PERCENT
,
257 if (count(array_diff($elements, $allowedElements)) > 0) {
258 throw new Exception\
InvalidArgumentException('Invalid element found in $elements array');
261 $this->elements
= $elements;
263 $this->_calculateBarWidth();
269 * Set the left-hand character for the bar
271 * @param string $char
272 * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
273 * @return \Zend\ProgressBar\Adapter\Console
275 public function setBarLeftChar($char)
278 throw new Exception\
InvalidArgumentException('Character may not be empty');
281 $this->barLeftChar
= (string) $char;
287 * Set the right-hand character for the bar
289 * @param string $char
290 * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
291 * @return \Zend\ProgressBar\Adapter\Console
293 public function setBarRightChar($char)
296 throw new Exception\
InvalidArgumentException('Character may not be empty');
299 $this->barRightChar
= (string) $char;
305 * Set the indicator character for the bar
307 * @param string $char
308 * @return \Zend\ProgressBar\Adapter\Console
310 public function setBarIndicatorChar($char)
312 $this->barIndicatorChar
= (string) $char;
318 * Set the width of the text element
321 * @return \Zend\ProgressBar\Adapter\Console
323 public function setTextWidth($width)
325 $this->textWidth
= (int) $width;
327 $this->_calculateBarWidth();
333 * Set the charset of the text element
335 * @param string $charset
337 public function setCharset($charset)
339 $this->charset
= $charset;
343 * Set the finish action
345 * @param string $action
346 * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid action is specified
347 * @return \Zend\ProgressBar\Adapter\Console
349 public function setFinishAction($action)
351 $allowedActions = [self
::FINISH_ACTION_CLEAR_LINE
,
352 self
::FINISH_ACTION_EOL
,
353 self
::FINISH_ACTION_NONE
];
355 if (! in_array($action, $allowedActions)) {
356 throw new Exception\
InvalidArgumentException('Invalid finish action specified');
359 $this->finishAction
= $action;
365 * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
367 * @param float $current Current progress value
368 * @param float $max Max progress value
369 * @param float $percent Current percent value
370 * @param int $timeTaken Taken time in seconds
371 * @param int $timeRemaining Remaining time in seconds
372 * @param string $text Status text
375 public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
377 // See if we must clear the line
378 if ($this->outputStarted
) {
379 $data = str_repeat("\x08", $this->width
);
382 $this->outputStarted
= true;
385 // Build all elements
386 $renderedElements = [];
388 foreach ($this->elements
as $element) {
390 case self
::ELEMENT_BAR
:
391 $visualWidth = $this->barWidth
- 2;
394 $indicatorWidth = strlen($this->barIndicatorChar
);
396 $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent));
397 if ($doneWidth > 0) {
399 str_repeat($this->barLeftChar
, ceil($doneWidth / strlen($this->barLeftChar
))),
405 $bar .= $this->barIndicatorChar
;
407 $leftWidth = $visualWidth - $doneWidth - $indicatorWidth;
408 if ($leftWidth > 0) {
410 str_repeat($this->barRightChar
, ceil($leftWidth / strlen($this->barRightChar
))),
418 $renderedElements[] = $bar;
421 case self
::ELEMENT_PERCENT
:
422 $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT
) . '%';
425 case self
::ELEMENT_ETA
:
426 // In the first 5 seconds we don't get accurate results,
427 // this skipping technique is found in many progressbar
429 if ($timeTaken < 5) {
430 $renderedElements[] = str_repeat(' ', 12);
434 if ($timeRemaining === null ||
$timeRemaining > 86400) {
435 $etaFormatted = '??:??:??';
437 $hours = floor($timeRemaining / 3600);
438 $minutes = floor(($timeRemaining %
3600) / 60);
439 $seconds = ($timeRemaining %
3600 %
60);
441 $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
444 $renderedElements[] = 'ETA ' . $etaFormatted;
447 case self
::ELEMENT_TEXT
:
448 $wrapper = StringUtils
::getWrapper($this->charset
);
449 $renderedElements[] = $wrapper->strPad(
450 $wrapper->substr($text, 0, $this->textWidth
),
459 $data .= implode(' ', $renderedElements);
462 $this->_outputData($data);
466 * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
470 public function finish()
472 switch ($this->finishAction
) {
473 case self
::FINISH_ACTION_EOL
:
474 $this->_outputData(PHP_EOL
);
477 case self
::FINISH_ACTION_CLEAR_LINE
:
478 if ($this->outputStarted
) {
479 $data = str_repeat("\x08", $this->width
)
480 . str_repeat(' ', $this->width
)
481 . str_repeat("\x08", $this->width
);
483 $this->_outputData($data);
487 case self
::FINISH_ACTION_NONE
:
493 * Calculate the bar width when other elements changed
497 // @codingStandardsIgnoreStart
498 protected function _calculateBarWidth()
500 // @codingStandardsIgnoreEnd
501 if (in_array(self
::ELEMENT_BAR
, $this->elements
)) {
502 $barWidth = $this->width
;
504 if (in_array(self
::ELEMENT_PERCENT
, $this->elements
)) {
508 if (in_array(self
::ELEMENT_ETA
, $this->elements
)) {
512 if (in_array(self
::ELEMENT_TEXT
, $this->elements
)) {
513 $barWidth -= $this->textWidth
;
516 $this->barWidth
= $barWidth - (count($this->elements
) - 1);
521 * Outputs given data to STDOUT.
523 * This split-off is required for unit-testing.
525 * @param string $data
528 // @codingStandardsIgnoreStart
529 protected function _outputData($data)
531 // @codingStandardsIgnoreEnd
532 fwrite($this->getOutputStream(), $data);