ProgressHelper.php 12 KB

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