* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; /** * Output decorator helpers for the Symfony Style Guide. * * @author Kevin Bond */ class SymfonyStyle extends OutputStyle { const MAX_LINE_LENGTH = 120; private $input; private $questionHelper; private $progressBar; private $lineLength; private $bufferedOutput; /** * @param InputInterface $input * @param OutputInterface $output */ public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string|null $type The block type (added in [] on first line) * @param string|null $style The style to apply to the whole block * @param string $prefix The prefix for the block * @param bool $padding Whether to add vertical padding */ public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) { $this->autoPrependBlock(); $messages = is_array($messages) ? array_values($messages) : array($messages); $indentLength = 0; $lines = array(); if (null !== $type) { $typePrefix = sprintf('[%s] ', $type); $indentLength = strlen($typePrefix); $lineIndentation = str_repeat(' ', $indentLength); } // wrap and add newlines for each element foreach ($messages as $key => $message) { $message = OutputFormatter::escape($message); $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - Helper::strlen($prefix) - $indentLength, PHP_EOL, true))); // prefix each line with a number of spaces equivalent to the type length if (null !== $type) { foreach ($lines as &$line) { $line = $lineIndentation === substr($line, 0, $indentLength) ? $line : $lineIndentation.$line; } } if (count($messages) > 1 && $key < count($messages) - 1) { $lines[] = ''; } } if (null !== $type) { $lines[0] = substr_replace($lines[0], $typePrefix, 0, $indentLength); } if ($padding && $this->isDecorated()) { array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as &$line) { $line = sprintf('%s%s', $prefix, $line); $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); if ($style) { $line = sprintf('<%s>%s', $style, $line); } } $this->writeln($lines); $this->newLine(); } /** * {@inheritdoc} */ public function title($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', $message), sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function section($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', $message), sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } /** * {@inheritdoc} */ public function text($message) { $this->autoPrependText(); $messages = is_array($message) ? array_values($message) : array($message); foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } /** * Formats a command comment. * * @param string|array $message */ public function comment($message) { $messages = is_array($message) ? array_values($message) : array($message); foreach ($messages as &$message) { $message = $this->getFormatter()->format($message); } $this->block($messages, null, null, ' // '); } /** * {@inheritdoc} */ public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } /** * {@inheritdoc} */ public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function warning($message) { $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * {@inheritdoc} */ public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } /** * {@inheritdoc} */ public function table(array $headers, array $rows) { array_walk_recursive($headers, function (&$value) { if ($value instanceof TableCell) { $value = new TableCell(sprintf('%s', $value), array( 'colspan' => $value->getColspan(), 'rowspan' => $value->getRowspan(), )); } else { $value = sprintf('%s', $value); } }); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle('symfony-style-guide'); $table->render(); $this->newLine(); } /** * {@inheritdoc} */ public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } /** * {@inheritdoc} */ public function choice($question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); $default = $values[$default]; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } /** * {@inheritdoc} */ public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } /** * {@inheritdoc} */ public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } /** * {@inheritdoc} */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } /** * {@inheritdoc} */ public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== DIRECTORY_SEPARATOR) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 } return $progressBar; } /** * @param Question $question * * @return string */ public function askQuestion(Question $question) { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } if (!$this->questionHelper) { $this->questionHelper = new SymfonyQuestionHelper(); } $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { parent::writeln($messages, $type); $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { parent::write($messages, $newline, $type); $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); } /** * {@inheritdoc} */ public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } /** * @return ProgressBar */ private function getProgressBar() { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function getTerminalWidth() { $application = new Application(); $dimensions = $application->getTerminalDimensions(); return $dimensions[0] ?: self::MAX_LINE_LENGTH; } private function autoPrependBlock() { $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { return $this->newLine(); //empty history, so we should start with a new line. } //Prepend new line for each non LF chars (This means no blank line was output before) $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); //Prepend new line if last char isn't EOL: if ("\n" !== substr($fetched, -1)) { $this->newLine(); } } private function reduceBuffer($messages) { // We need to know if the two last chars are PHP_EOL // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer return array_map(function ($value) { return substr($value, -4); }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); } }