Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Console\Helper; Chris@0: Chris@0: use Symfony\Component\Console\Exception\InvalidArgumentException; Chris@0: use Symfony\Component\Console\Exception\LogicException; Chris@0: use Symfony\Component\Console\Output\OutputInterface; Chris@0: Chris@0: /** Chris@0: * @author Kevin Bond Chris@0: */ Chris@0: class ProgressIndicator Chris@0: { Chris@0: private $output; Chris@0: private $startTime; Chris@0: private $format; Chris@0: private $message; Chris@0: private $indicatorValues; Chris@0: private $indicatorCurrent; Chris@0: private $indicatorChangeInterval; Chris@0: private $indicatorUpdateTime; Chris@0: private $started = false; Chris@0: Chris@0: private static $formatters; Chris@0: private static $formats; Chris@0: Chris@0: /** Chris@0: * @param OutputInterface $output Chris@0: * @param string|null $format Indicator format Chris@0: * @param int $indicatorChangeInterval Change interval in milliseconds Chris@0: * @param array|null $indicatorValues Animated indicator characters Chris@0: */ Chris@0: public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null) Chris@0: { Chris@0: $this->output = $output; Chris@0: Chris@0: if (null === $format) { Chris@0: $format = $this->determineBestFormat(); Chris@0: } Chris@0: Chris@0: if (null === $indicatorValues) { Chris@17: $indicatorValues = ['-', '\\', '|', '/']; Chris@0: } Chris@0: Chris@0: $indicatorValues = array_values($indicatorValues); Chris@0: Chris@17: if (2 > \count($indicatorValues)) { Chris@0: throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); Chris@0: } Chris@0: Chris@0: $this->format = self::getFormatDefinition($format); Chris@0: $this->indicatorChangeInterval = $indicatorChangeInterval; Chris@0: $this->indicatorValues = $indicatorValues; Chris@0: $this->startTime = time(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the current indicator message. Chris@0: * Chris@0: * @param string|null $message Chris@0: */ Chris@0: public function setMessage($message) Chris@0: { Chris@0: $this->message = $message; Chris@0: Chris@0: $this->display(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Starts the indicator output. Chris@0: * Chris@0: * @param $message Chris@0: */ Chris@0: public function start($message) Chris@0: { Chris@0: if ($this->started) { Chris@0: throw new LogicException('Progress indicator already started.'); Chris@0: } Chris@0: Chris@0: $this->message = $message; Chris@0: $this->started = true; Chris@0: $this->startTime = time(); Chris@0: $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; Chris@0: $this->indicatorCurrent = 0; Chris@0: Chris@0: $this->display(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Advances the indicator. Chris@0: */ Chris@0: public function advance() Chris@0: { Chris@0: if (!$this->started) { Chris@0: throw new LogicException('Progress indicator has not yet been started.'); Chris@0: } Chris@0: Chris@0: if (!$this->output->isDecorated()) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $currentTime = $this->getCurrentTimeInMilliseconds(); Chris@0: Chris@0: if ($currentTime < $this->indicatorUpdateTime) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; Chris@0: ++$this->indicatorCurrent; Chris@0: Chris@0: $this->display(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finish the indicator with message. Chris@0: * Chris@0: * @param $message Chris@0: */ Chris@0: public function finish($message) Chris@0: { Chris@0: if (!$this->started) { Chris@0: throw new LogicException('Progress indicator has not yet been started.'); Chris@0: } Chris@0: Chris@0: $this->message = $message; Chris@0: $this->display(); Chris@0: $this->output->writeln(''); Chris@0: $this->started = false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the format for a given name. Chris@0: * Chris@0: * @param string $name The format name Chris@0: * Chris@0: * @return string|null A format string Chris@0: */ Chris@0: public static function getFormatDefinition($name) Chris@0: { Chris@0: if (!self::$formats) { Chris@0: self::$formats = self::initFormats(); Chris@0: } Chris@0: Chris@0: return isset(self::$formats[$name]) ? self::$formats[$name] : null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a placeholder formatter for a given name. Chris@0: * Chris@0: * This method also allow you to override an existing placeholder. Chris@0: * Chris@0: * @param string $name The placeholder name (including the delimiter char like %) Chris@0: * @param callable $callable A PHP callable Chris@0: */ Chris@0: public static function setPlaceholderFormatterDefinition($name, $callable) Chris@0: { Chris@0: if (!self::$formatters) { Chris@0: self::$formatters = self::initPlaceholderFormatters(); Chris@0: } Chris@0: Chris@0: self::$formatters[$name] = $callable; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the placeholder formatter for a given name. Chris@0: * Chris@0: * @param string $name The placeholder name (including the delimiter char like %) Chris@0: * Chris@0: * @return callable|null A PHP callable Chris@0: */ Chris@0: public static function getPlaceholderFormatterDefinition($name) Chris@0: { Chris@0: if (!self::$formatters) { Chris@0: self::$formatters = self::initPlaceholderFormatters(); Chris@0: } Chris@0: Chris@0: return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; Chris@0: } Chris@0: Chris@0: private function display() Chris@0: { Chris@0: if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $self = $this; Chris@0: Chris@0: $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { Chris@0: if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { Chris@17: return \call_user_func($formatter, $self); Chris@0: } Chris@0: Chris@0: return $matches[0]; Chris@0: }, $this->format)); Chris@0: } Chris@0: Chris@0: private function determineBestFormat() Chris@0: { Chris@0: switch ($this->output->getVerbosity()) { Chris@0: // OutputInterface::VERBOSITY_QUIET: display is disabled anyway Chris@0: case OutputInterface::VERBOSITY_VERBOSE: Chris@0: return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; Chris@0: case OutputInterface::VERBOSITY_VERY_VERBOSE: Chris@0: case OutputInterface::VERBOSITY_DEBUG: Chris@0: return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; Chris@0: default: Chris@0: return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Overwrites a previous message to the output. Chris@0: * Chris@0: * @param string $message The message Chris@0: */ Chris@0: private function overwrite($message) Chris@0: { Chris@0: if ($this->output->isDecorated()) { Chris@0: $this->output->write("\x0D\x1B[2K"); Chris@0: $this->output->write($message); Chris@0: } else { Chris@0: $this->output->writeln($message); Chris@0: } Chris@0: } Chris@0: Chris@0: private function getCurrentTimeInMilliseconds() Chris@0: { Chris@0: return round(microtime(true) * 1000); Chris@0: } Chris@0: Chris@0: private static function initPlaceholderFormatters() Chris@0: { Chris@17: return [ Chris@17: 'indicator' => function (self $indicator) { Chris@17: return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; Chris@0: }, Chris@17: 'message' => function (self $indicator) { Chris@0: return $indicator->message; Chris@0: }, Chris@17: 'elapsed' => function (self $indicator) { Chris@0: return Helper::formatTime(time() - $indicator->startTime); Chris@0: }, Chris@0: 'memory' => function () { Chris@0: return Helper::formatMemory(memory_get_usage(true)); Chris@0: }, Chris@17: ]; Chris@0: } Chris@0: Chris@0: private static function initFormats() Chris@0: { Chris@17: return [ Chris@0: 'normal' => ' %indicator% %message%', Chris@0: 'normal_no_ansi' => ' %message%', Chris@0: Chris@0: 'verbose' => ' %indicator% %message% (%elapsed:6s%)', Chris@0: 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', Chris@0: Chris@0: 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', Chris@0: 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', Chris@17: ]; Chris@0: } Chris@0: }