PageRenderTime 25ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

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