ProgressHelper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Helper;
  11. use Symfony\Component\Console\Output\NullOutput;
  12. use Symfony\Component\Console\Output\OutputInterface;
  13. /**
  14. * The Progress class provides helpers to display progress output.
  15. *
  16. * @author Chris Jones <leeked@gmail.com>
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. *
  19. * @deprecated since version 2.5, to be removed in 3.0
  20. * Use {@link ProgressBar} instead.
  21. */
  22. class ProgressHelper extends Helper
  23. {
  24. const FORMAT_QUIET = ' %percent%%';
  25. const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
  26. const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
  27. const FORMAT_QUIET_NOMAX = ' %current%';
  28. const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
  29. const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';
  30. // options
  31. private $barWidth = 28;
  32. private $barChar = '=';
  33. private $emptyBarChar = '-';
  34. private $progressChar = '>';
  35. private $format = null;
  36. private $redrawFreq = 1;
  37. private $lastMessagesLength;
  38. private $barCharOriginal;
  39. /**
  40. * @var OutputInterface
  41. */
  42. private $output;
  43. /**
  44. * Current step.
  45. *
  46. * @var int
  47. */
  48. private $current;
  49. /**
  50. * Maximum number of steps.
  51. *
  52. * @var int
  53. */
  54. private $max;
  55. /**
  56. * Start time of the progress bar.
  57. *
  58. * @var int
  59. */
  60. private $startTime;
  61. /**
  62. * List of formatting variables.
  63. *
  64. * @var array
  65. */
  66. private $defaultFormatVars = array(
  67. 'current',
  68. 'max',
  69. 'bar',
  70. 'percent',
  71. 'elapsed',
  72. );
  73. /**
  74. * Available formatting variables.
  75. *
  76. * @var array
  77. */
  78. private $formatVars;
  79. /**
  80. * Stored format part widths (used for padding).
  81. *
  82. * @var array
  83. */
  84. private $widths = array(
  85. 'current' => 4,
  86. 'max' => 4,
  87. 'percent' => 3,
  88. 'elapsed' => 6,
  89. );
  90. /**
  91. * Various time formats.
  92. *
  93. * @var array
  94. */
  95. private $timeFormats = array(
  96. array(0, '???'),
  97. array(2, '1 sec'),
  98. array(59, 'secs', 1),
  99. array(60, '1 min'),
  100. array(3600, 'mins', 60),
  101. array(5400, '1 hr'),
  102. array(86400, 'hrs', 3600),
  103. array(129600, '1 day'),
  104. array(604800, 'days', 86400),
  105. );
  106. public function __construct($triggerDeprecationError = true)
  107. {
  108. if ($triggerDeprecationError) {
  109. @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED);
  110. }
  111. }
  112. /**
  113. * Sets the progress bar width.
  114. *
  115. * @param int $size The progress bar size
  116. */
  117. public function setBarWidth($size)
  118. {
  119. $this->barWidth = (int) $size;
  120. }
  121. /**
  122. * Sets the bar character.
  123. *
  124. * @param string $char A character
  125. */
  126. public function setBarCharacter($char)
  127. {
  128. $this->barChar = $char;
  129. }
  130. /**
  131. * Sets the empty bar character.
  132. *
  133. * @param string $char A character
  134. */
  135. public function setEmptyBarCharacter($char)
  136. {
  137. $this->emptyBarChar = $char;
  138. }
  139. /**
  140. * Sets the progress bar character.
  141. *
  142. * @param string $char A character
  143. */
  144. public function setProgressCharacter($char)
  145. {
  146. $this->progressChar = $char;
  147. }
  148. /**
  149. * Sets the progress bar format.
  150. *
  151. * @param string $format The format
  152. */
  153. public function setFormat($format)
  154. {
  155. $this->format = $format;
  156. }
  157. /**
  158. * Sets the redraw frequency.
  159. *
  160. * @param int $freq The frequency in steps
  161. */
  162. public function setRedrawFrequency($freq)
  163. {
  164. $this->redrawFreq = (int) $freq;
  165. }
  166. /**
  167. * Starts the progress output.
  168. *
  169. * @param OutputInterface $output An Output instance
  170. * @param int|null $max Maximum steps
  171. */
  172. public function start(OutputInterface $output, $max = null)
  173. {
  174. $this->startTime = time();
  175. $this->current = 0;
  176. $this->max = (int) $max;
  177. // Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
  178. $this->output = $output->isDecorated() ? $output : new NullOutput();
  179. $this->lastMessagesLength = 0;
  180. $this->barCharOriginal = '';
  181. if (null === $this->format) {
  182. switch ($output->getVerbosity()) {
  183. case OutputInterface::VERBOSITY_QUIET:
  184. $this->format = self::FORMAT_QUIET_NOMAX;
  185. if ($this->max > 0) {
  186. $this->format = self::FORMAT_QUIET;
  187. }
  188. break;
  189. case OutputInterface::VERBOSITY_VERBOSE:
  190. case OutputInterface::VERBOSITY_VERY_VERBOSE:
  191. case OutputInterface::VERBOSITY_DEBUG:
  192. $this->format = self::FORMAT_VERBOSE_NOMAX;
  193. if ($this->max > 0) {
  194. $this->format = self::FORMAT_VERBOSE;
  195. }
  196. break;
  197. default:
  198. $this->format = self::FORMAT_NORMAL_NOMAX;
  199. if ($this->max > 0) {
  200. $this->format = self::FORMAT_NORMAL;
  201. }
  202. break;
  203. }
  204. }
  205. $this->initialize();
  206. }
  207. /**
  208. * Advances the progress output X steps.
  209. *
  210. * @param int $step Number of steps to advance
  211. * @param bool $redraw Whether to redraw or not
  212. *
  213. * @throws \LogicException
  214. */
  215. public function advance($step = 1, $redraw = false)
  216. {
  217. $this->setCurrent($this->current + $step, $redraw);
  218. }
  219. /**
  220. * Sets the current progress.
  221. *
  222. * @param int $current The current progress
  223. * @param bool $redraw Whether to redraw or not
  224. *
  225. * @throws \LogicException
  226. */
  227. public function setCurrent($current, $redraw = false)
  228. {
  229. if (null === $this->startTime) {
  230. throw new \LogicException('You must start the progress bar before calling setCurrent().');
  231. }
  232. $current = (int) $current;
  233. if ($current < $this->current) {
  234. throw new \LogicException('You can\'t regress the progress bar');
  235. }
  236. if (0 === $this->current) {
  237. $redraw = true;
  238. }
  239. $prevPeriod = (int) ($this->current / $this->redrawFreq);
  240. $this->current = $current;
  241. $currPeriod = (int) ($this->current / $this->redrawFreq);
  242. if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) {
  243. $this->display();
  244. }
  245. }
  246. /**
  247. * Outputs the current progress string.
  248. *
  249. * @param bool $finish Forces the end result
  250. *
  251. * @throws \LogicException
  252. */
  253. public function display($finish = false)
  254. {
  255. if (null === $this->startTime) {
  256. throw new \LogicException('You must start the progress bar before calling display().');
  257. }
  258. $message = $this->format;
  259. foreach ($this->generate($finish) as $name => $value) {
  260. $message = str_replace("%{$name}%", $value, $message);
  261. }
  262. $this->overwrite($this->output, $message);
  263. }
  264. /**
  265. * Removes the progress bar from the current line.
  266. *
  267. * This is useful if you wish to write some output
  268. * while a progress bar is running.
  269. * Call display() to show the progress bar again.
  270. */
  271. public function clear()
  272. {
  273. $this->overwrite($this->output, '');
  274. }
  275. /**
  276. * Finishes the progress output.
  277. */
  278. public function finish()
  279. {
  280. if (null === $this->startTime) {
  281. throw new \LogicException('You must start the progress bar before calling finish().');
  282. }
  283. if (null !== $this->startTime) {
  284. if (!$this->max) {
  285. $this->barChar = $this->barCharOriginal;
  286. $this->display(true);
  287. }
  288. $this->startTime = null;
  289. $this->output->writeln('');
  290. $this->output = null;
  291. }
  292. }
  293. /**
  294. * Initializes the progress helper.
  295. */
  296. private function initialize()
  297. {
  298. $this->formatVars = array();
  299. foreach ($this->defaultFormatVars as $var) {
  300. if (false !== strpos($this->format, "%{$var}%")) {
  301. $this->formatVars[$var] = true;
  302. }
  303. }
  304. if ($this->max > 0) {
  305. $this->widths['max'] = $this->strlen($this->max);
  306. $this->widths['current'] = $this->widths['max'];
  307. } else {
  308. $this->barCharOriginal = $this->barChar;
  309. $this->barChar = $this->emptyBarChar;
  310. }
  311. }
  312. /**
  313. * Generates the array map of format variables to values.
  314. *
  315. * @param bool $finish Forces the end result
  316. *
  317. * @return array Array of format vars and values
  318. */
  319. private function generate($finish = false)
  320. {
  321. $vars = array();
  322. $percent = 0;
  323. if ($this->max > 0) {
  324. $percent = (float) $this->current / $this->max;
  325. }
  326. if (isset($this->formatVars['bar'])) {
  327. $completeBars = 0;
  328. if ($this->max > 0) {
  329. $completeBars = floor($percent * $this->barWidth);
  330. } else {
  331. if (!$finish) {
  332. $completeBars = floor($this->current % $this->barWidth);
  333. } else {
  334. $completeBars = $this->barWidth;
  335. }
  336. }
  337. $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar);
  338. $bar = str_repeat($this->barChar, $completeBars);
  339. if ($completeBars < $this->barWidth) {
  340. $bar .= $this->progressChar;
  341. $bar .= str_repeat($this->emptyBarChar, $emptyBars);
  342. }
  343. $vars['bar'] = $bar;
  344. }
  345. if (isset($this->formatVars['elapsed'])) {
  346. $elapsed = time() - $this->startTime;
  347. $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT);
  348. }
  349. if (isset($this->formatVars['current'])) {
  350. $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT);
  351. }
  352. if (isset($this->formatVars['max'])) {
  353. $vars['max'] = $this->max;
  354. }
  355. if (isset($this->formatVars['percent'])) {
  356. $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT);
  357. }
  358. return $vars;
  359. }
  360. /**
  361. * Converts seconds into human-readable format.
  362. *
  363. * @param int $secs Number of seconds
  364. *
  365. * @return string Time in readable format
  366. */
  367. private function humaneTime($secs)
  368. {
  369. $text = '';
  370. foreach ($this->timeFormats as $format) {
  371. if ($secs < $format[0]) {
  372. if (count($format) == 2) {
  373. $text = $format[1];
  374. break;
  375. } else {
  376. $text = ceil($secs / $format[2]).' '.$format[1];
  377. break;
  378. }
  379. }
  380. }
  381. return $text;
  382. }
  383. /**
  384. * Overwrites a previous message to the output.
  385. *
  386. * @param OutputInterface $output An Output instance
  387. * @param string $message The message
  388. */
  389. private function overwrite(OutputInterface $output, $message)
  390. {
  391. $length = $this->strlen($message);
  392. // append whitespace to match the last line's length
  393. if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
  394. $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
  395. }
  396. // carriage return
  397. $output->write("\x0D");
  398. $output->write($message);
  399. $this->lastMessagesLength = $this->strlen($message);
  400. }
  401. /**
  402. * {@inheritdoc}
  403. */
  404. public function getName()
  405. {
  406. return 'progress';
  407. }
  408. }