diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index caa091492c..9e4fa02f2c 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -109,25 +109,11 @@ class QuestionHelper extends Helper
*/
public function doAsk(OutputInterface $output, Question $question)
{
+ $this->writePrompt($output, $question);
+
$inputStream = $this->inputStream ?: STDIN;
-
- $message = $question->getQuestion();
- if ($question instanceof ChoiceQuestion) {
- $width = max(array_map('strlen', array_keys($question->getChoices())));
-
- $messages = (array) $question->getQuestion();
- foreach ($question->getChoices() as $key => $value) {
- $messages[] = sprintf(" [%-${width}s] %s", $key, $value);
- }
-
- $output->writeln($messages);
-
- $message = $question->getPrompt();
- }
-
- $output->write($message);
-
$autocomplete = $question->getAutocompleterValues();
+
if (null === $autocomplete || !$this->hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
@@ -160,6 +146,49 @@ class QuestionHelper extends Helper
return $ret;
}
+ /**
+ * Outputs the question prompt.
+ *
+ * @param OutputInterface $output
+ * @param Question $question
+ */
+ protected function writePrompt(OutputInterface $output, Question $question)
+ {
+ $message = $question->getQuestion();
+
+ if ($question instanceof ChoiceQuestion) {
+ $width = max(array_map('strlen', array_keys($question->getChoices())));
+
+ $messages = (array) $question->getQuestion();
+ foreach ($question->getChoices() as $key => $value) {
+ $messages[] = sprintf(" [%-${width}s] %s", $key, $value);
+ }
+
+ $output->writeln($messages);
+
+ $message = $question->getPrompt();
+ }
+
+ $output->write($message);
+ }
+
+ /**
+ * Outputs an error message.
+ *
+ * @param OutputInterface $output
+ * @param \Exception $error
+ */
+ protected function writeError(OutputInterface $output, \Exception $error)
+ {
+ if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
+ $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
+ } else {
+ $message = ''.$error->getMessage().'';
+ }
+
+ $output->writeln($message);
+ }
+
/**
* Autocompletes a question.
*
@@ -355,13 +384,7 @@ class QuestionHelper extends Helper
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
- if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
- $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
- } else {
- $message = ''.$error->getMessage().'';
- }
-
- $output->writeln($message);
+ $this->writeError($output, $error);
}
try {
diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
new file mode 100644
index 0000000000..77130f9776
--- /dev/null
+++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
@@ -0,0 +1,106 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Symfony Style Guide compliant question helper.
+ *
+ * @author Kevin Bond
+ */
+class SymfonyQuestionHelper extends QuestionHelper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function ask(InputInterface $input, OutputInterface $output, Question $question)
+ {
+ $validator = $question->getValidator();
+ $question->setValidator(function ($value) use ($validator) {
+ if (null !== $validator && is_callable($validator)) {
+ $value = $validator($value);
+ }
+
+ // make required
+ if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) {
+ throw new \Exception('A value is required.');
+ }
+
+ return $value;
+ });
+
+ return parent::ask($input, $output, $question);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function writePrompt(OutputInterface $output, Question $question)
+ {
+ $text = $question->getQuestion();
+ $default = $question->getDefault();
+
+ switch (true) {
+ case null === $default:
+ $text = sprintf(' %s:', $text);
+
+ break;
+
+ case $question instanceof ConfirmationQuestion:
+ $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no');
+
+ break;
+
+ case $question instanceof ChoiceQuestion:
+ $choices = $question->getChoices();
+ $text = sprintf(' %s [%s]:', $text, $choices[$default]);
+
+ break;
+
+ default:
+ $text = sprintf(' %s [%s]:', $text, $default);
+ }
+
+ $output->writeln($text);
+
+ if ($question instanceof ChoiceQuestion) {
+ $width = max(array_map('strlen', array_keys($question->getChoices())));
+
+ foreach ($question->getChoices() as $key => $value) {
+ $output->writeln(sprintf(" [%-${width}s] %s", $key, $value));
+ }
+ }
+
+ $output->write(' > ');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function writeError(OutputInterface $output, \Exception $error)
+ {
+ if ($output instanceof SymfonyStyle) {
+ $output->newLine();
+ $output->error($error->getMessage());
+
+ return;
+ }
+
+ parent::writeError($output, $error);
+ }
+}
diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php
index 67cfbb503d..0e9612a2f2 100644
--- a/src/Symfony/Component/Console/Helper/Table.php
+++ b/src/Symfony/Component/Console/Helper/Table.php
@@ -401,10 +401,19 @@ class Table
->setCellRowContentFormat('%s')
;
+ $styleGuide = new TableStyle();
+ $styleGuide
+ ->setHorizontalBorderChar('-')
+ ->setVerticalBorderChar(' ')
+ ->setCrossingChar(' ')
+ ->setCellHeaderFormat('%s')
+ ;
+
return array(
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
+ 'symfony-style-guide' => $styleGuide,
);
}
}
diff --git a/src/Symfony/Component/Console/Style/OutputStyle.php b/src/Symfony/Component/Console/Style/OutputStyle.php
new file mode 100644
index 0000000000..13ed05b13d
--- /dev/null
+++ b/src/Symfony/Component/Console/Style/OutputStyle.php
@@ -0,0 +1,116 @@
+
+ *
+ * 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\Formatter\OutputFormatterInterface;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Decorates output to add console style guide helpers
+ *
+ * @author Kevin Bond
+ */
+abstract class OutputStyle implements OutputInterface, StyleInterface
+{
+ private $output;
+
+ /**
+ * @param OutputInterface $output
+ */
+ public function __construct(OutputInterface $output)
+ {
+ $this->output = $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function newLine($count = 1)
+ {
+ $this->output->write(str_repeat(PHP_EOL, $count));
+ }
+
+ /**
+ * @param int $max
+ *
+ * @return ProgressBar
+ */
+ public function createProgressBar($max = 0)
+ {
+ return new ProgressBar($this->output, $max);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
+ {
+ $this->output->write($messages, $newline, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function writeln($messages, $type = self::OUTPUT_NORMAL)
+ {
+ $this->output->writeln($messages, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setVerbosity($level)
+ {
+ $this->output->setVerbosity($level);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVerbosity()
+ {
+ return $this->output->getVerbosity();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDecorated($decorated)
+ {
+ $this->output->setDecorated($decorated);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isDecorated()
+ {
+ return $this->output->isDecorated();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFormatter(OutputFormatterInterface $formatter)
+ {
+ $this->output->setFormatter($formatter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormatter()
+ {
+ return $this->output->getFormatter();
+ }
+}
diff --git a/src/Symfony/Component/Console/Style/StyleInterface.php b/src/Symfony/Component/Console/Style/StyleInterface.php
new file mode 100644
index 0000000000..214782e393
--- /dev/null
+++ b/src/Symfony/Component/Console/Style/StyleInterface.php
@@ -0,0 +1,159 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Style;
+
+/**
+ * Output style helpers
+ *
+ * @author Kevin Bond
+ */
+interface StyleInterface
+{
+ /**
+ * Formats a command title.
+ *
+ * @param string $message
+ */
+ public function title($message);
+
+ /**
+ * Formats a section title.
+ *
+ * @param string $message
+ */
+ public function section($message);
+
+ /**
+ * Formats a list.
+ *
+ * @param array $elements
+ */
+ public function listing(array $elements);
+
+ /**
+ * Formats informational text.
+ *
+ * @param string|array $message
+ */
+ public function text($message);
+
+ /**
+ * Formats a success result bar.
+ *
+ * @param string|array $message
+ */
+ public function success($message);
+
+ /**
+ * Formats an error result bar.
+ *
+ * @param string|array $message
+ */
+ public function error($message);
+
+ /**
+ * Formats an warning result bar.
+ *
+ * @param string|array $message
+ */
+ public function warning($message);
+
+ /**
+ * Formats a note admonition.
+ *
+ * @param string|array $message
+ */
+ public function note($message);
+
+ /**
+ * Formats a caution admonition.
+ *
+ * @param string|array $message
+ */
+ public function caution($message);
+
+ /**
+ * Formats a table.
+ *
+ * @param array $headers
+ * @param array $rows
+ */
+ public function table(array $headers, array $rows);
+
+ /**
+ * Asks a question.
+ *
+ * @param string $question
+ * @param string|null $default
+ * @param callable|null $validator
+ *
+ * @return string
+ */
+ public function ask($question, $default = null, $validator = null);
+
+ /**
+ * Asks a question with the user input hidden.
+ *
+ * @param string $question
+ * @param callable|null $validator
+ *
+ * @return string
+ */
+ public function askHidden($question, $validator = null);
+
+ /**
+ * Asks for confirmation.
+ *
+ * @param string $question
+ * @param bool $default
+ *
+ * @return bool
+ */
+ public function confirm($question, $default = true);
+
+ /**
+ * Asks a choice question.
+ *
+ * @param string $question
+ * @param array $choices
+ * @param string|int|null $default
+ *
+ * @return string
+ */
+ public function choice($question, array $choices, $default = null);
+
+ /**
+ * Add newline(s)
+ *
+ * @param int $count The number of newlines
+ */
+ public function newLine($count = 1);
+
+ /**
+ * Starts the progress output.
+ *
+ * @param int $max Maximum steps (0 if unknown)
+ */
+ public function progressStart($max = 0);
+
+ /**
+ * Advances the progress output X steps.
+ *
+ * @param int $step Number of steps to advance
+ */
+ public function progressAdvance($step = 1);
+
+ /**
+ * Finishes the progress output.
+ */
+ public function progressFinish();
+}
diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php
new file mode 100644
index 0000000000..1b2e7368f6
--- /dev/null
+++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php
@@ -0,0 +1,310 @@
+
+ *
+ * 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\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\Input\InputInterface;
+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;
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ public function __construct(InputInterface $input, OutputInterface $output)
+ {
+ $this->input = $input;
+
+ 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)
+ {
+ $messages = array_values((array) $messages);
+ $lines = array();
+
+ // add type
+ if (null !== $type) {
+ $messages[0] = sprintf('[%s] %s', $type, $messages[0]);
+ }
+
+ // wrap and add newlines for each element
+ foreach ($messages as $key => $message) {
+ $message = OutputFormatter::escape($message);
+ $lines = array_merge($lines, explode("\n", wordwrap($message, self::MAX_LINE_LENGTH - Helper::strlen($prefix))));
+
+ if (count($messages) > 1 && $key < count($message)) {
+ $lines[] = '';
+ }
+ }
+
+ if ($padding && $this->isDecorated()) {
+ array_unshift($lines, '');
+ $lines[] = '';
+ }
+
+ foreach ($lines as &$line) {
+ $line = sprintf('%s%s', $prefix, $line);
+ $line .= str_repeat(' ', self::MAX_LINE_LENGTH - Helper::strlen($line));
+
+ if ($style) {
+ $line = sprintf('<%s>%s>', $style, $line);
+ }
+ }
+
+ $this->writeln(implode("\n", $lines)."\n");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function title($message)
+ {
+ $this->writeln(sprintf("\n%s\n%s\n", $message, str_repeat('=', strlen($message))));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function section($message)
+ {
+ $this->writeln(sprintf("%s\n%s\n", $message, str_repeat('-', strlen($message))));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listing(array $elements)
+ {
+ $elements = array_map(function ($element) {
+ return sprintf(' * %s', $element);
+ },
+ $elements
+ );
+
+ $this->writeln(implode("\n\n", $elements)."\n");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function text($message)
+ {
+ if (!is_array($message)) {
+ $this->writeln(sprintf(' // %s', $message));
+
+ return;
+ }
+
+ foreach ($message as $element) {
+ $this->text($element);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function success($message)
+ {
+ $this->block($message, 'OK', 'fg=white;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)
+ {
+ $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, $validator);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function askHidden($question, $validator = null)
+ {
+ $question = new Question($question);
+ $question->setHidden(true);
+
+ return $this->askQuestion($question, $validator);
+ }
+
+ /**
+ * {@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->questionHelper) {
+ $this->questionHelper = new SymfonyQuestionHelper();
+ }
+
+ $answer = $this->questionHelper->ask($this->input, $this, $question);
+
+ $this->newLine();
+
+ return $answer;
+ }
+
+ /**
+ * @return ProgressBar
+ */
+ private function getProgressBar()
+ {
+ if (!$this->progressBar) {
+ throw new \RuntimeException('The ProgressBar is not started.');
+ }
+
+ return $this->progressBar;
+ }
+}