PageRenderTime 1191ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/pborreli/zf2
PHP | 511 lines | 226 code | 81 blank | 204 comment | 32 complexity | b885eb29dc831be897d9dbf97028567b 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-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. * @package Zend_ProgressBar
  9. */
  10. namespace Zend\ProgressBar\Adapter;
  11. use Zend\ProgressBar\Adapter\Exception;
  12. /**
  13. * Zend_ProgressBar_Adapter_Console offers a text-based progressbar for console
  14. * applications
  15. *
  16. * @category Zend
  17. * @package Zend_ProgressBar
  18. */
  19. class Console extends AbstractAdapter
  20. {
  21. /**
  22. * Percentage value of the progress
  23. */
  24. const ELEMENT_PERCENT = 'ELEMENT_PERCENT';
  25. /**
  26. * Visual value of the progress
  27. */
  28. const ELEMENT_BAR = 'ELEMENT_BAR';
  29. /**
  30. * ETA of the progress
  31. */
  32. const ELEMENT_ETA = 'ELEMENT_ETA';
  33. /**
  34. * Text part of the progress
  35. */
  36. const ELEMENT_TEXT = 'ELEMENT_TEXT';
  37. /**
  38. * Finish action: End of Line
  39. */
  40. const FINISH_ACTION_EOL = 'FINISH_ACTION_EOL';
  41. /**
  42. * Finish action: Clear Line
  43. */
  44. const FINISH_ACTION_CLEAR_LINE = 'FINISH_ACTION_CLEAR_LINE';
  45. /**
  46. * Finish action: None
  47. */
  48. const FINISH_ACTION_NONE = 'FINISH_ACTION_NONE';
  49. /**
  50. * Width of the progressbar
  51. *
  52. * @var integer
  53. */
  54. protected $width = null;
  55. /**
  56. * Elements to display
  57. *
  58. * @var array
  59. */
  60. protected $elements = array(self::ELEMENT_PERCENT,
  61. self::ELEMENT_BAR,
  62. self::ELEMENT_ETA);
  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 integer
  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. * Wether the output started yet or not
  107. *
  108. * @var boolean
  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. * @return \Zend\ProgressBar\Adapter\Console
  145. */
  146. public function setOutputStream($resource)
  147. {
  148. $stream = @fopen($resource, 'w');
  149. if ($stream === false) {
  150. throw new Exception\RuntimeException('Unable to open stream');
  151. }
  152. if ($this->outputStream !== null) {
  153. fclose($this->outputStream);
  154. }
  155. $this->outputStream = $stream;
  156. }
  157. /**
  158. * Get the current output stream
  159. *
  160. * @return resource
  161. */
  162. public function getOutputStream()
  163. {
  164. if ($this->outputStream === null) {
  165. if (!defined('STDOUT')) {
  166. $this->outputStream = fopen('php://stdout', 'w');
  167. } else {
  168. return STDOUT;
  169. }
  170. }
  171. return $this->outputStream;
  172. }
  173. /**
  174. * Set the width of the progressbar
  175. *
  176. * @param integer $width
  177. * @return \Zend\ProgressBar\Adapter\Console
  178. */
  179. public function setWidth($width = null)
  180. {
  181. if ($width === null || !is_integer($width)) {
  182. if (substr(PHP_OS, 0, 3) === 'WIN') {
  183. // We have to default to 79 on windows, because the windows
  184. // terminal always has a fixed width of 80 characters and the
  185. // cursor is counted to the line, else windows would line break
  186. // after every update.
  187. $this->width = 79;
  188. } else {
  189. // Set the default width of 80
  190. $this->width = 80;
  191. // Try to determine the width through stty
  192. if (preg_match('#\d+ (\d+)#', @shell_exec('stty size'), $match) === 1) {
  193. $this->width = (int) $match[1];
  194. } elseif (preg_match('#columns = (\d+);#', @shell_exec('stty'), $match) === 1) {
  195. $this->width = (int) $match[1];
  196. }
  197. }
  198. } else {
  199. $this->width = (int) $width;
  200. }
  201. $this->_calculateBarWidth();
  202. return $this;
  203. }
  204. /**
  205. * Set the elements to display with the progressbar
  206. *
  207. * @param array $elements
  208. * @throws \Zend\ProgressBar\Adapter\Exception When an invalid element is foudn in the array
  209. * @return \Zend\ProgressBar\Adapter\Console
  210. */
  211. public function setElements(array $elements)
  212. {
  213. $allowedElements = array(self::ELEMENT_PERCENT,
  214. self::ELEMENT_BAR,
  215. self::ELEMENT_ETA,
  216. self::ELEMENT_TEXT);
  217. if (count(array_diff($elements, $allowedElements)) > 0) {
  218. throw new Exception\InvalidArgumentException('Invalid element found in $elements array');
  219. }
  220. $this->elements = $elements;
  221. $this->_calculateBarWidth();
  222. return $this;
  223. }
  224. /**
  225. * Set the left-hand character for the bar
  226. *
  227. * @param string $char
  228. * @throws \Zend\ProgressBar\Adapter\Exception When character is empty
  229. * @return \Zend\ProgressBar\Adapter\Console
  230. */
  231. public function setBarLeftChar($char)
  232. {
  233. if (empty($char)) {
  234. throw new Exception\InvalidArgumentException('Character may not be empty');
  235. }
  236. $this->barLeftChar = (string) $char;
  237. return $this;
  238. }
  239. /**
  240. * Set the right-hand character for the bar
  241. *
  242. * @param string $char
  243. * @throws \Zend\ProgressBar\Adapter\Exception When character is empty
  244. * @return \Zend\ProgressBar\Adapter\Console
  245. */
  246. public function setBarRightChar($char)
  247. {
  248. if (empty($char)) {
  249. throw new Exception\InvalidArgumentException('Character may not be empty');
  250. }
  251. $this->barRightChar = (string) $char;
  252. return $this;
  253. }
  254. /**
  255. * Set the indicator character for the bar
  256. *
  257. * @param string $char
  258. * @return \Zend\ProgressBar\Adapter\Console
  259. */
  260. public function setBarIndicatorChar($char)
  261. {
  262. $this->barIndicatorChar = (string) $char;
  263. return $this;
  264. }
  265. /**
  266. * Set the width of the text element
  267. *
  268. * @param integer $width
  269. * @return \Zend\ProgressBar\Adapter\Console
  270. */
  271. public function setTextWidth($width)
  272. {
  273. $this->textWidth = (int) $width;
  274. $this->_calculateBarWidth();
  275. return $this;
  276. }
  277. /**
  278. * Set the charset of the text element
  279. *
  280. * @param string $charset
  281. */
  282. public function setCharset($charset)
  283. {
  284. $this->charset = $charset;
  285. }
  286. /**
  287. * Set the finish action
  288. *
  289. * @param string $action
  290. * @throws \Zend\ProgressBar\Adapter\Exception When an invalid action is specified
  291. * @return \Zend\ProgressBar\Adapter\Console
  292. */
  293. public function setFinishAction($action)
  294. {
  295. $allowedActions = array(self::FINISH_ACTION_CLEAR_LINE,
  296. self::FINISH_ACTION_EOL,
  297. self::FINISH_ACTION_NONE);
  298. if (!in_array($action, $allowedActions)) {
  299. throw new Exception\InvalidArgumentException('Invalid finish action specified');
  300. }
  301. $this->finishAction = $action;
  302. return $this;
  303. }
  304. /**
  305. * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
  306. *
  307. * @param float $current Current progress value
  308. * @param float $max Max progress value
  309. * @param float $percent Current percent value
  310. * @param integer $timeTaken Taken time in seconds
  311. * @param integer $timeRemaining Remaining time in seconds
  312. * @param string $text Status text
  313. * @return void
  314. */
  315. public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
  316. {
  317. // See if we must clear the line
  318. if ($this->outputStarted) {
  319. $data = str_repeat("\x08", $this->width);
  320. } else {
  321. $data = '';
  322. $this->outputStarted = true;
  323. }
  324. // Build all elements
  325. $renderedElements = array();
  326. foreach ($this->elements as $element) {
  327. switch ($element) {
  328. case self::ELEMENT_BAR:
  329. $visualWidth = $this->barWidth - 2;
  330. $bar = '[';
  331. $indicatorWidth = strlen($this->barIndicatorChar);
  332. $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent));
  333. if ($doneWidth > 0) {
  334. $bar .= substr(str_repeat($this->barLeftChar, ceil($doneWidth / strlen($this->barLeftChar))), 0, $doneWidth);
  335. }
  336. $bar .= $this->barIndicatorChar;
  337. $leftWidth = $visualWidth - $doneWidth - $indicatorWidth;
  338. if ($leftWidth > 0) {
  339. $bar .= substr(str_repeat($this->barRightChar, ceil($leftWidth / strlen($this->barRightChar))), 0, $leftWidth);
  340. }
  341. $bar .= ']';
  342. $renderedElements[] = $bar;
  343. break;
  344. case self::ELEMENT_PERCENT:
  345. $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT) . '%';
  346. break;
  347. case self::ELEMENT_ETA:
  348. // In the first 5 seconds we don't get accurate results,
  349. // this skipping technique is found in many progressbar
  350. // implementations.
  351. if ($timeTaken < 5) {
  352. $renderedElements[] = str_repeat(' ', 12);
  353. break;
  354. }
  355. if ($timeRemaining === null || $timeRemaining > 86400) {
  356. $etaFormatted = '??:??:??';
  357. } else {
  358. $hours = floor($timeRemaining / 3600);
  359. $minutes = floor(($timeRemaining % 3600) / 60);
  360. $seconds = ($timeRemaining % 3600 % 60);
  361. $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
  362. }
  363. $renderedElements[] = 'ETA ' . $etaFormatted;
  364. break;
  365. case self::ELEMENT_TEXT:
  366. $renderedElements[] = \Zend\Text\MultiByte::strPad(substr($text, 0, $this->textWidth), $this->textWidth, ' ', STR_PAD_RIGHT, $this->charset);
  367. break;
  368. }
  369. }
  370. $data .= implode(' ', $renderedElements);
  371. // Output line data
  372. $this->_outputData($data);
  373. }
  374. /**
  375. * Defined by Zend\ProgressBar\Adapter\AbstractAdapter
  376. *
  377. * @return void
  378. */
  379. public function finish()
  380. {
  381. switch ($this->finishAction) {
  382. case self::FINISH_ACTION_EOL:
  383. $this->_outputData(PHP_EOL);
  384. break;
  385. case self::FINISH_ACTION_CLEAR_LINE:
  386. if ($this->outputStarted) {
  387. $data = str_repeat("\x08", $this->width)
  388. . str_repeat(' ', $this->width)
  389. . str_repeat("\x08", $this->width);
  390. $this->_outputData($data);
  391. }
  392. break;
  393. case self::FINISH_ACTION_NONE:
  394. break;
  395. }
  396. }
  397. /**
  398. * Calculate the bar width when other elements changed
  399. *
  400. * @return void
  401. */
  402. protected function _calculateBarWidth()
  403. {
  404. if (in_array(self::ELEMENT_BAR, $this->elements)) {
  405. $barWidth = $this->width;
  406. if (in_array(self::ELEMENT_PERCENT, $this->elements)) {
  407. $barWidth -= 4;
  408. }
  409. if (in_array(self::ELEMENT_ETA, $this->elements)) {
  410. $barWidth -= 12;
  411. }
  412. if (in_array(self::ELEMENT_TEXT, $this->elements)) {
  413. $barWidth -= $this->textWidth;
  414. }
  415. $this->barWidth = $barWidth - (count($this->elements) - 1);
  416. }
  417. }
  418. /**
  419. * Outputs given data to STDOUT.
  420. *
  421. * This split-off is required for unit-testing.
  422. *
  423. * @param string $data
  424. * @return void
  425. */
  426. protected function _outputData($data)
  427. {
  428. fwrite($this->getOutputStream(), $data);
  429. }
  430. }