Drupal investigation

ProgressBar.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  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\ConsoleOutputInterface;
  12. use Symfony\Component\Console\Output\OutputInterface;
  13. use Symfony\Component\Console\Exception\LogicException;
  14. /**
  15. * The ProgressBar provides helpers to display progress output.
  16. *
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. * @author Chris Jones <leeked@gmail.com>
  19. */
  20. class ProgressBar
  21. {
  22. // options
  23. private $barWidth = 28;
  24. private $barChar;
  25. private $emptyBarChar = '-';
  26. private $progressChar = '>';
  27. private $format;
  28. private $internalFormat;
  29. private $redrawFreq = 1;
  30. /**
  31. * @var OutputInterface
  32. */
  33. private $output;
  34. private $step = 0;
  35. private $max;
  36. private $startTime;
  37. private $stepWidth;
  38. private $percent = 0.0;
  39. private $formatLineCount;
  40. private $messages = array();
  41. private $overwrite = true;
  42. private $firstRun = true;
  43. private static $formatters;
  44. private static $formats;
  45. /**
  46. * Constructor.
  47. *
  48. * @param OutputInterface $output An OutputInterface instance
  49. * @param int $max Maximum steps (0 if unknown)
  50. */
  51. public function __construct(OutputInterface $output, $max = 0)
  52. {
  53. if ($output instanceof ConsoleOutputInterface) {
  54. $output = $output->getErrorOutput();
  55. }
  56. $this->output = $output;
  57. $this->setMaxSteps($max);
  58. if (!$this->output->isDecorated()) {
  59. // disable overwrite when output does not support ANSI codes.
  60. $this->overwrite = false;
  61. // set a reasonable redraw frequency so output isn't flooded
  62. $this->setRedrawFrequency($max / 10);
  63. }
  64. $this->startTime = time();
  65. }
  66. /**
  67. * Sets a placeholder formatter for a given name.
  68. *
  69. * This method also allow you to override an existing placeholder.
  70. *
  71. * @param string $name The placeholder name (including the delimiter char like %)
  72. * @param callable $callable A PHP callable
  73. */
  74. public static function setPlaceholderFormatterDefinition($name, $callable)
  75. {
  76. if (!self::$formatters) {
  77. self::$formatters = self::initPlaceholderFormatters();
  78. }
  79. self::$formatters[$name] = $callable;
  80. }
  81. /**
  82. * Gets the placeholder formatter for a given name.
  83. *
  84. * @param string $name The placeholder name (including the delimiter char like %)
  85. *
  86. * @return callable|null A PHP callable
  87. */
  88. public static function getPlaceholderFormatterDefinition($name)
  89. {
  90. if (!self::$formatters) {
  91. self::$formatters = self::initPlaceholderFormatters();
  92. }
  93. return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
  94. }
  95. /**
  96. * Sets a format for a given name.
  97. *
  98. * This method also allow you to override an existing format.
  99. *
  100. * @param string $name The format name
  101. * @param string $format A format string
  102. */
  103. public static function setFormatDefinition($name, $format)
  104. {
  105. if (!self::$formats) {
  106. self::$formats = self::initFormats();
  107. }
  108. self::$formats[$name] = $format;
  109. }
  110. /**
  111. * Gets the format for a given name.
  112. *
  113. * @param string $name The format name
  114. *
  115. * @return string|null A format string
  116. */
  117. public static function getFormatDefinition($name)
  118. {
  119. if (!self::$formats) {
  120. self::$formats = self::initFormats();
  121. }
  122. return isset(self::$formats[$name]) ? self::$formats[$name] : null;
  123. }
  124. /**
  125. * Associates a text with a named placeholder.
  126. *
  127. * The text is displayed when the progress bar is rendered but only
  128. * when the corresponding placeholder is part of the custom format line
  129. * (by wrapping the name with %).
  130. *
  131. * @param string $message The text to associate with the placeholder
  132. * @param string $name The name of the placeholder
  133. */
  134. public function setMessage($message, $name = 'message')
  135. {
  136. $this->messages[$name] = $message;
  137. }
  138. public function getMessage($name = 'message')
  139. {
  140. return $this->messages[$name];
  141. }
  142. /**
  143. * Gets the progress bar start time.
  144. *
  145. * @return int The progress bar start time
  146. */
  147. public function getStartTime()
  148. {
  149. return $this->startTime;
  150. }
  151. /**
  152. * Gets the progress bar maximal steps.
  153. *
  154. * @return int The progress bar max steps
  155. */
  156. public function getMaxSteps()
  157. {
  158. return $this->max;
  159. }
  160. /**
  161. * Gets the progress bar step.
  162. *
  163. * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead.
  164. *
  165. * @return int The progress bar step
  166. */
  167. public function getStep()
  168. {
  169. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED);
  170. return $this->getProgress();
  171. }
  172. /**
  173. * Gets the current step position.
  174. *
  175. * @return int The progress bar step
  176. */
  177. public function getProgress()
  178. {
  179. return $this->step;
  180. }
  181. /**
  182. * Gets the progress bar step width.
  183. *
  184. * @internal This method is public for PHP 5.3 compatibility, it should not be used.
  185. *
  186. * @return int The progress bar step width
  187. */
  188. public function getStepWidth()
  189. {
  190. return $this->stepWidth;
  191. }
  192. /**
  193. * Gets the current progress bar percent.
  194. *
  195. * @return float The current progress bar percent
  196. */
  197. public function getProgressPercent()
  198. {
  199. return $this->percent;
  200. }
  201. /**
  202. * Sets the progress bar width.
  203. *
  204. * @param int $size The progress bar size
  205. */
  206. public function setBarWidth($size)
  207. {
  208. $this->barWidth = (int) $size;
  209. }
  210. /**
  211. * Gets the progress bar width.
  212. *
  213. * @return int The progress bar size
  214. */
  215. public function getBarWidth()
  216. {
  217. return $this->barWidth;
  218. }
  219. /**
  220. * Sets the bar character.
  221. *
  222. * @param string $char A character
  223. */
  224. public function setBarCharacter($char)
  225. {
  226. $this->barChar = $char;
  227. }
  228. /**
  229. * Gets the bar character.
  230. *
  231. * @return string A character
  232. */
  233. public function getBarCharacter()
  234. {
  235. if (null === $this->barChar) {
  236. return $this->max ? '=' : $this->emptyBarChar;
  237. }
  238. return $this->barChar;
  239. }
  240. /**
  241. * Sets the empty bar character.
  242. *
  243. * @param string $char A character
  244. */
  245. public function setEmptyBarCharacter($char)
  246. {
  247. $this->emptyBarChar = $char;
  248. }
  249. /**
  250. * Gets the empty bar character.
  251. *
  252. * @return string A character
  253. */
  254. public function getEmptyBarCharacter()
  255. {
  256. return $this->emptyBarChar;
  257. }
  258. /**
  259. * Sets the progress bar character.
  260. *
  261. * @param string $char A character
  262. */
  263. public function setProgressCharacter($char)
  264. {
  265. $this->progressChar = $char;
  266. }
  267. /**
  268. * Gets the progress bar character.
  269. *
  270. * @return string A character
  271. */
  272. public function getProgressCharacter()
  273. {
  274. return $this->progressChar;
  275. }
  276. /**
  277. * Sets the progress bar format.
  278. *
  279. * @param string $format The format
  280. */
  281. public function setFormat($format)
  282. {
  283. $this->format = null;
  284. $this->internalFormat = $format;
  285. }
  286. /**
  287. * Sets the redraw frequency.
  288. *
  289. * @param int|float $freq The frequency in steps
  290. */
  291. public function setRedrawFrequency($freq)
  292. {
  293. $this->redrawFreq = max((int) $freq, 1);
  294. }
  295. /**
  296. * Starts the progress output.
  297. *
  298. * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
  299. */
  300. public function start($max = null)
  301. {
  302. $this->startTime = time();
  303. $this->step = 0;
  304. $this->percent = 0.0;
  305. if (null !== $max) {
  306. $this->setMaxSteps($max);
  307. }
  308. $this->display();
  309. }
  310. /**
  311. * Advances the progress output X steps.
  312. *
  313. * @param int $step Number of steps to advance
  314. *
  315. * @throws LogicException
  316. */
  317. public function advance($step = 1)
  318. {
  319. $this->setProgress($this->step + $step);
  320. }
  321. /**
  322. * Sets the current progress.
  323. *
  324. * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead.
  325. *
  326. * @param int $step The current progress
  327. *
  328. * @throws LogicException
  329. */
  330. public function setCurrent($step)
  331. {
  332. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED);
  333. $this->setProgress($step);
  334. }
  335. /**
  336. * Sets whether to overwrite the progressbar, false for new line.
  337. *
  338. * @param bool $overwrite
  339. */
  340. public function setOverwrite($overwrite)
  341. {
  342. $this->overwrite = (bool) $overwrite;
  343. }
  344. /**
  345. * Sets the current progress.
  346. *
  347. * @param int $step The current progress
  348. *
  349. * @throws LogicException
  350. */
  351. public function setProgress($step)
  352. {
  353. $step = (int) $step;
  354. if ($step < $this->step) {
  355. throw new LogicException('You can\'t regress the progress bar.');
  356. }
  357. if ($this->max && $step > $this->max) {
  358. $this->max = $step;
  359. }
  360. $prevPeriod = (int) ($this->step / $this->redrawFreq);
  361. $currPeriod = (int) ($step / $this->redrawFreq);
  362. $this->step = $step;
  363. $this->percent = $this->max ? (float) $this->step / $this->max : 0;
  364. if ($prevPeriod !== $currPeriod || $this->max === $step) {
  365. $this->display();
  366. }
  367. }
  368. /**
  369. * Finishes the progress output.
  370. */
  371. public function finish()
  372. {
  373. if (!$this->max) {
  374. $this->max = $this->step;
  375. }
  376. if ($this->step === $this->max && !$this->overwrite) {
  377. // prevent double 100% output
  378. return;
  379. }
  380. $this->setProgress($this->max);
  381. }
  382. /**
  383. * Outputs the current progress string.
  384. */
  385. public function display()
  386. {
  387. if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
  388. return;
  389. }
  390. if (null === $this->format) {
  391. $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
  392. }
  393. // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
  394. $self = $this;
  395. $output = $this->output;
  396. $messages = $this->messages;
  397. $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) {
  398. if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
  399. $text = call_user_func($formatter, $self, $output);
  400. } elseif (isset($messages[$matches[1]])) {
  401. $text = $messages[$matches[1]];
  402. } else {
  403. return $matches[0];
  404. }
  405. if (isset($matches[2])) {
  406. $text = sprintf('%'.$matches[2], $text);
  407. }
  408. return $text;
  409. }, $this->format));
  410. }
  411. /**
  412. * Removes the progress bar from the current line.
  413. *
  414. * This is useful if you wish to write some output
  415. * while a progress bar is running.
  416. * Call display() to show the progress bar again.
  417. */
  418. public function clear()
  419. {
  420. if (!$this->overwrite) {
  421. return;
  422. }
  423. if (null === $this->format) {
  424. $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
  425. }
  426. $this->overwrite('');
  427. }
  428. /**
  429. * Sets the progress bar format.
  430. *
  431. * @param string $format The format
  432. */
  433. private function setRealFormat($format)
  434. {
  435. // try to use the _nomax variant if available
  436. if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
  437. $this->format = self::getFormatDefinition($format.'_nomax');
  438. } elseif (null !== self::getFormatDefinition($format)) {
  439. $this->format = self::getFormatDefinition($format);
  440. } else {
  441. $this->format = $format;
  442. }
  443. $this->formatLineCount = substr_count($this->format, "\n");
  444. }
  445. /**
  446. * Sets the progress bar maximal steps.
  447. *
  448. * @param int $max The progress bar max steps
  449. */
  450. private function setMaxSteps($max)
  451. {
  452. $this->max = max(0, (int) $max);
  453. $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
  454. }
  455. /**
  456. * Overwrites a previous message to the output.
  457. *
  458. * @param string $message The message
  459. */
  460. private function overwrite($message)
  461. {
  462. if ($this->overwrite) {
  463. if (!$this->firstRun) {
  464. // Move the cursor to the beginning of the line
  465. $this->output->write("\x0D");
  466. // Erase the line
  467. $this->output->write("\x1B[2K");
  468. // Erase previous lines
  469. if ($this->formatLineCount > 0) {
  470. $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
  471. }
  472. }
  473. } elseif ($this->step > 0) {
  474. $this->output->writeln('');
  475. }
  476. $this->firstRun = false;
  477. $this->output->write($message);
  478. }
  479. private function determineBestFormat()
  480. {
  481. switch ($this->output->getVerbosity()) {
  482. // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
  483. case OutputInterface::VERBOSITY_VERBOSE:
  484. return $this->max ? 'verbose' : 'verbose_nomax';
  485. case OutputInterface::VERBOSITY_VERY_VERBOSE:
  486. return $this->max ? 'very_verbose' : 'very_verbose_nomax';
  487. case OutputInterface::VERBOSITY_DEBUG:
  488. return $this->max ? 'debug' : 'debug_nomax';
  489. default:
  490. return $this->max ? 'normal' : 'normal_nomax';
  491. }
  492. }
  493. private static function initPlaceholderFormatters()
  494. {
  495. return array(
  496. 'bar' => function (ProgressBar $bar, OutputInterface $output) {
  497. $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
  498. $display = str_repeat($bar->getBarCharacter(), $completeBars);
  499. if ($completeBars < $bar->getBarWidth()) {
  500. $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
  501. $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
  502. }
  503. return $display;
  504. },
  505. 'elapsed' => function (ProgressBar $bar) {
  506. return Helper::formatTime(time() - $bar->getStartTime());
  507. },
  508. 'remaining' => function (ProgressBar $bar) {
  509. if (!$bar->getMaxSteps()) {
  510. throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
  511. }
  512. if (!$bar->getProgress()) {
  513. $remaining = 0;
  514. } else {
  515. $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
  516. }
  517. return Helper::formatTime($remaining);
  518. },
  519. 'estimated' => function (ProgressBar $bar) {
  520. if (!$bar->getMaxSteps()) {
  521. throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
  522. }
  523. if (!$bar->getProgress()) {
  524. $estimated = 0;
  525. } else {
  526. $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
  527. }
  528. return Helper::formatTime($estimated);
  529. },
  530. 'memory' => function (ProgressBar $bar) {
  531. return Helper::formatMemory(memory_get_usage(true));
  532. },
  533. 'current' => function (ProgressBar $bar) {
  534. return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
  535. },
  536. 'max' => function (ProgressBar $bar) {
  537. return $bar->getMaxSteps();
  538. },
  539. 'percent' => function (ProgressBar $bar) {
  540. return floor($bar->getProgressPercent() * 100);
  541. },
  542. );
  543. }
  544. private static function initFormats()
  545. {
  546. return array(
  547. 'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
  548. 'normal_nomax' => ' %current% [%bar%]',
  549. 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
  550. 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
  551. 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
  552. 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
  553. 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
  554. 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
  555. );
  556. }
  557. }