PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/zendframework/zendframework/library/Zend/ProgressBar/Adapter/Console.php

https://bitbucket.org/Evgenii/zf2t
PHP | 521 lines | 239 code | 81 blank | 201 comment | 32 complexity | 8b566d05cf2edf5ca054d495f440b960 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\ProgressBar\Adapter;
  10. use Zend\ProgressBar\Adapter\Exception;
  11. use Zend\Stdlib\ErrorHandler;
  12. use Zend\Stdlib\StringUtils;
  13. /**
  14. * Zend\ProgressBar\Adapter\Console offers a text-based progressbar for console
  15. * applications
  16. */
  17. class Console extends AbstractAdapter
  18. {
  19. /**
  20. * Percentage value of the progress
  21. */
  22. const ELEMENT_PERCENT = 'ELEMENT_PERCENT';
  23. /**
  24. * Visual value of the progress
  25. */
  26. const ELEMENT_BAR = 'ELEMENT_BAR';
  27. /**
  28. * ETA of the progress
  29. */
  30. const ELEMENT_ETA = 'ELEMENT_ETA';
  31. /**
  32. * Text part of the progress
  33. */
  34. const ELEMENT_TEXT = 'ELEMENT_TEXT';
  35. /**
  36. * Finish action: End of Line
  37. */
  38. const FINISH_ACTION_EOL = 'FINISH_ACTION_EOL';
  39. /**
  40. * Finish action: Clear Line
  41. */
  42. const FINISH_ACTION_CLEAR_LINE = 'FINISH_ACTION_CLEAR_LINE';
  43. /**
  44. * Finish action: None
  45. */
  46. const FINISH_ACTION_NONE = 'FINISH_ACTION_NONE';
  47. /**
  48. * Width of the progressbar
  49. *
  50. * @var int
  51. */
  52. protected $width = null;
  53. /**
  54. * Elements to display
  55. *
  56. * @var array
  57. */
  58. protected $elements = array(
  59. self::ELEMENT_PERCENT,
  60. self::ELEMENT_BAR,
  61. self::ELEMENT_ETA,
  62. );
  63. /**
  64. * Which action to do at finish call
  65. *
  66. * @var string
  67. */
  68. protected $finishAction = self::FINISH_ACTION_EOL;
  69. /**
  70. * Width of the bar element
  71. *
  72. * @var int
  73. */
  74. protected $barWidth;
  75. /**
  76. * Left character(s) within the bar
  77. *
  78. * @var string
  79. */
  80. protected $barLeftChar = '#';
  81. /**
  82. * Indicator character(s) within the bar
  83. *
  84. * @var string
  85. */
  86. protected $barIndicatorChar = '';
  87. /**
  88. * Right character(s) within the bar
  89. *
  90. * @var string
  91. */
  92. protected $barRightChar = '-';
  93. /**
  94. * Output-stream, when STDOUT is not defined (e.g. in CGI) or set manually
  95. *
  96. * @var resource
  97. */
  98. protected $outputStream = null;
  99. /**
  100. * Width of the text element
  101. *
  102. * @var string
  103. */
  104. protected $textWidth = 20;
  105. /**
  106. * Whether the output started yet or not
  107. *
  108. * @var bool
  109. */
  110. protected $outputStarted = false;
  111. /**
  112. * Charset of text element
  113. *
  114. * @var string
  115. */
  116. protected $charset = 'utf-8';
  117. /**
  118. * Defined by Zend\ProgressBar adapter
  119. *
  120. * @param array|\Traversable $options
  121. */
  122. public function __construct($options = null)
  123. {
  124. // Call parent constructor with options
  125. parent::__construct($options);
  126. // Check if a width was set, else use auto width
  127. if ($this->width === null) {
  128. $this->setWidth();
  129. }
  130. }
  131. /**
  132. * Close local stdout, when open
  133. */
  134. public function __destruct()
  135. {
  136. if ($this->outputStream !== null) {
  137. fclose($this->outputStream);
  138. }
  139. }
  140. /**
  141. * Set a different output-stream
  142. *
  143. * @param string $resource
  144. * @throws Exception\RuntimeException
  145. * @return \Zend\ProgressBar\Adapter\Console
  146. */
  147. public function setOutputStream($resource)
  148. {
  149. ErrorHandler::start();
  150. $stream = fopen($resource, 'w');
  151. $error = ErrorHandler::stop();
  152. if ($stream === false) {
  153. throw new Exception\RuntimeException('Unable to open stream', 0, $error);
  154. }
  155. if ($this->outputStream !== null) {
  156. fclose($this->outputStream);
  157. }
  158. $this->outputStream = $stream;
  159. }
  160. /**
  161. * Get the current output stream
  162. *
  163. * @return resource
  164. */
  165. public function getOutputStream()
  166. {
  167. if ($this->outputStream === null) {
  168. if (!defined('STDOUT')) {
  169. $this->outputStream = fopen('php://stdout', 'w');
  170. } else {
  171. return STDOUT;
  172. }
  173. }
  174. return $this->outputStream;
  175. }
  176. /**
  177. * Set the width of the progressbar
  178. *
  179. * @param int $width
  180. * @return \Zend\ProgressBar\Adapter\Console
  181. */
  182. public function setWidth($width = null)
  183. {
  184. if ($width === null || !is_int($width)) {
  185. if (substr(PHP_OS, 0, 3) === 'WIN') {
  186. // We have to default to 79 on windows, because the windows
  187. // terminal always has a fixed width of 80 characters and the
  188. // cursor is counted to the line, else windows would line break
  189. // after every update.
  190. $this->width = 79;
  191. } else {
  192. // Set the default width of 80
  193. $this->width = 80;
  194. // Try to determine the width through stty
  195. ErrorHandler::start();
  196. if (preg_match('#\d+ (\d+)#', shell_exec('stty size'), $match) === 1) {
  197. $this->width = (int) $match[1];
  198. } elseif (preg_match('#columns = (\d+);#', shell_exec('stty'), $match) === 1) {
  199. $this->width = (int) $match[1];
  200. }
  201. ErrorHandler::stop();
  202. }
  203. } else {
  204. $this->width = (int) $width;
  205. }
  206. $this->_calculateBarWidth();
  207. return $this;
  208. }
  209. /**
  210. * Set the elements to display with the progressbar
  211. *
  212. * @param array $elements
  213. * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid element is found in the array
  214. * @return \Zend\ProgressBar\Adapter\Console
  215. */
  216. public function setElements(array $elements)
  217. {
  218. $allowedElements = array(self::ELEMENT_PERCENT,
  219. self::ELEMENT_BAR,
  220. self::ELEMENT_ETA,
  221. self::ELEMENT_TEXT);
  222. if (count(array_diff($elements, $allowedElements)) > 0) {
  223. throw new Exception\InvalidArgumentException('Invalid element found in $elements array');
  224. }
  225. $this->elements = $elements;
  226. $this->_calculateBarWidth();
  227. return $this;
  228. }
  229. /**
  230. * Set the left-hand character for the bar
  231. *
  232. * @param string $char
  233. * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
  234. * @return \Zend\ProgressBar\Adapter\Console
  235. */
  236. public function setBarLeftChar($char)
  237. {
  238. if (empty($char)) {
  239. throw new Exception\InvalidArgumentException('Character may not be empty');
  240. }
  241. $this->barLeftChar = (string) $char;
  242. return $this;
  243. }
  244. /**
  245. * Set the right-hand character for the bar
  246. *
  247. * @param string $char
  248. * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When character is empty
  249. * @return \Zend\ProgressBar\Adapter\Console
  250. */
  251. public function setBarRightChar($char)
  252. {
  253. if (empty($char)) {
  254. throw new Exception\InvalidArgumentException('Character may not be empty');
  255. }
  256. $this->barRightChar = (string) $char;
  257. return $this;
  258. }
  259. /**
  260. * Set the indicator character for the bar
  261. *
  262. * @param string $char
  263. * @return \Zend\ProgressBar\Adapter\Console
  264. */
  265. public function setBarIndicatorChar($char)
  266. {
  267. $this->barIndicatorChar = (string) $char;
  268. return $this;
  269. }
  270. /**
  271. * Set the width of the text element
  272. *
  273. * @param int $width
  274. * @return \Zend\ProgressBar\Adapter\Console
  275. */
  276. public function setTextWidth($width)
  277. {
  278. $this->textWidth = (int) $width;
  279. $this->_calculateBarWidth();
  280. return $this;
  281. }
  282. /**
  283. * Set the charset of the text element
  284. *
  285. * @param string $charset
  286. */
  287. public function setCharset($charset)
  288. {
  289. $this->charset = $charset;
  290. }
  291. /**
  292. * Set the finish action
  293. *
  294. * @param string $action
  295. * @throws \Zend\ProgressBar\Adapter\Exception\InvalidArgumentException When an invalid action is specified
  296. * @return \Zend\ProgressBar\Adapter\Console
  297. */
  298. public function setFinishAction($action)
  299. {
  300. $allowedActions = array(self::FINISH_ACTION_CLEAR_LINE,
  301. self::FINISH_ACTION_EOL,
  302. self::FINISH_ACTION_NONE);
  303. if (!in_array($action, $allowedActions)) {
  304. throw new Exception\InvalidArgumentException('Invalid finish action specified');
  305. }
  306. $this->finishAction = $action;
  307. return $this;
  308. }
  309. /**
  310. * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
  311. *
  312. * @param float $current Current progress value
  313. * @param float $max Max progress value
  314. * @param float $percent Current percent value
  315. * @param int $timeTaken Taken time in seconds
  316. * @param int $timeRemaining Remaining time in seconds
  317. * @param string $text Status text
  318. * @return void
  319. */
  320. public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
  321. {
  322. // See if we must clear the line
  323. if ($this->outputStarted) {
  324. $data = str_repeat("\x08", $this->width);
  325. } else {
  326. $data = '';
  327. $this->outputStarted = true;
  328. }
  329. // Build all elements
  330. $renderedElements = array();
  331. foreach ($this->elements as $element) {
  332. switch ($element) {
  333. case self::ELEMENT_BAR:
  334. $visualWidth = $this->barWidth - 2;
  335. $bar = '[';
  336. $indicatorWidth = strlen($this->barIndicatorChar);
  337. $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent));
  338. if ($doneWidth > 0) {
  339. $bar .= substr(str_repeat($this->barLeftChar, ceil($doneWidth / strlen($this->barLeftChar))), 0, $doneWidth);
  340. }
  341. $bar .= $this->barIndicatorChar;
  342. $leftWidth = $visualWidth - $doneWidth - $indicatorWidth;
  343. if ($leftWidth > 0) {
  344. $bar .= substr(str_repeat($this->barRightChar, ceil($leftWidth / strlen($this->barRightChar))), 0, $leftWidth);
  345. }
  346. $bar .= ']';
  347. $renderedElements[] = $bar;
  348. break;
  349. case self::ELEMENT_PERCENT:
  350. $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT) . '%';
  351. break;
  352. case self::ELEMENT_ETA:
  353. // In the first 5 seconds we don't get accurate results,
  354. // this skipping technique is found in many progressbar
  355. // implementations.
  356. if ($timeTaken < 5) {
  357. $renderedElements[] = str_repeat(' ', 12);
  358. break;
  359. }
  360. if ($timeRemaining === null || $timeRemaining > 86400) {
  361. $etaFormatted = '??:??:??';
  362. } else {
  363. $hours = floor($timeRemaining / 3600);
  364. $minutes = floor(($timeRemaining % 3600) / 60);
  365. $seconds = ($timeRemaining % 3600 % 60);
  366. $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
  367. }
  368. $renderedElements[] = 'ETA ' . $etaFormatted;
  369. break;
  370. case self::ELEMENT_TEXT:
  371. $renderedElements[] = StringUtils::getWrapper($this->charset)->strPad(
  372. substr($text, 0, $this->textWidth),
  373. $this->textWidth,
  374. ' ',
  375. STR_PAD_RIGHT
  376. );
  377. break;
  378. }
  379. }
  380. $data .= implode(' ', $renderedElements);
  381. // Output line data
  382. $this->_outputData($data);
  383. }
  384. /**
  385. * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
  386. *
  387. * @return void
  388. */
  389. public function finish()
  390. {
  391. switch ($this->finishAction) {
  392. case self::FINISH_ACTION_EOL:
  393. $this->_outputData(PHP_EOL);
  394. break;
  395. case self::FINISH_ACTION_CLEAR_LINE:
  396. if ($this->outputStarted) {
  397. $data = str_repeat("\x08", $this->width)
  398. . str_repeat(' ', $this->width)
  399. . str_repeat("\x08", $this->width);
  400. $this->_outputData($data);
  401. }
  402. break;
  403. case self::FINISH_ACTION_NONE:
  404. break;
  405. }
  406. }
  407. /**
  408. * Calculate the bar width when other elements changed
  409. *
  410. * @return void
  411. */
  412. protected function _calculateBarWidth()
  413. {
  414. if (in_array(self::ELEMENT_BAR, $this->elements)) {
  415. $barWidth = $this->width;
  416. if (in_array(self::ELEMENT_PERCENT, $this->elements)) {
  417. $barWidth -= 4;
  418. }
  419. if (in_array(self::ELEMENT_ETA, $this->elements)) {
  420. $barWidth -= 12;
  421. }
  422. if (in_array(self::ELEMENT_TEXT, $this->elements)) {
  423. $barWidth -= $this->textWidth;
  424. }
  425. $this->barWidth = $barWidth - (count($this->elements) - 1);
  426. }
  427. }
  428. /**
  429. * Outputs given data to STDOUT.
  430. *
  431. * This split-off is required for unit-testing.
  432. *
  433. * @param string $data
  434. * @return void
  435. */
  436. protected function _outputData($data)
  437. {
  438. fwrite($this->getOutputStream(), $data);
  439. }
  440. }