From ddd8f6ef845a7224e9252fa3a2e684878ca0d71f Mon Sep 17 00:00:00 2001 From: Wesley Date: Mon, 16 Oct 2017 18:14:58 -0400 Subject: [PATCH 1/3] Add MagentoStyle as Console nput/output helper object to allow easier acces to io helpers in symfony/console --- .../Console/InputValidationException.php | 14 + .../Setup/Console/Style/MagentoStyle.php | 508 ++++++++++++++++++ .../Console/Style/MagentoStyleInterface.php | 15 + .../Unit/Console/Style/MagentoStyleTest.php | 62 +++ 4 files changed, 599 insertions(+) create mode 100755 setup/src/Magento/Setup/Console/InputValidationException.php create mode 100755 setup/src/Magento/Setup/Console/Style/MagentoStyle.php create mode 100755 setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php create mode 100755 setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php diff --git a/setup/src/Magento/Setup/Console/InputValidationException.php b/setup/src/Magento/Setup/Console/InputValidationException.php new file mode 100755 index 0000000000000..5d78bdabf3c8f --- /dev/null +++ b/setup/src/Magento/Setup/Console/InputValidationException.php @@ -0,0 +1,14 @@ +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. + $currentLength = $this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'); + $this->lineLength = min($currentLength, 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) + { + $messages = is_array($messages) ? array_values($messages) : array($messages); + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $bar = str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message)); + $this->writeln(array( + sprintf(' %s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf(' %s', $bar), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $bar = str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message)); + $this->writeln(array( + sprintf(' %s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf(' %s', $bar), + )); + $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, $padding = false) + { + $this->block($message, null, 'comment', ' ', $padding); + } + + /** + * {@inheritdoc} + */ + public function success($message, $padding = true) + { + $this->block($message, 'SUCCESS', 'fg=black;bg=green', ' ', $padding); + } + + /** + * {@inheritdoc} + */ + public function error($message, $padding = true) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', $padding); + } + + /** + * {@inheritdoc} + */ + public function warning($message, $padding = true) + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', $padding); + } + + /** + * {@inheritdoc} + */ + public function note($message, $padding = false) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ', $padding); + } + + /** + * {@inheritdoc} + */ + public function caution($message, $padding = true) + { + $this->block($message, 'CAUTION', 'fg=black;bg=yellow', ' ! ', $padding); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null, $maxAttempts = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + $question->setMaxAttempts($maxAttempts); + + 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); + $progressBar->setEmptyBarCharacter(' '); + $progressBar->setProgressCharacter('>'); + $progressBar->setBarCharacter('='); + + 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; + } + + /** + * Ask for an missing argument. + * + * @param string $argument + * @param string $question + * @param string|null $default + * @param callable|null $validator + * @param int|null $maxAttempts + * @param bool $comment + * @param string $commentFormat + */ + public function askForMissingArgument( + $argument, + $question, + $default = null, + $validator = null, + $maxAttempts = null, + $comment = null, + $commentFormat = "Argument [%s] set to: %s" + ) { + try { + if( is_null($this->input->getArgument($argument)) ) { + $this->input->setArgument($argument, $this->ask($question, $default, $validator, $maxAttempts) ); + } + $argumentValue = $this->input->getArgument($argument); + $validated = ( is_callable($validator) ? $validator($argumentValue) : $argumentValue ); + if( (bool) ( is_null($comment) ? $this->isDebug() : $comment ) ) + { + $this->comment( sprintf($commentFormat, $argument, $validated) ); + } + } catch( InputValidationException $e ) { + $this->error("Validation Error: ".$e->getMessage()); + $this->askForMissingArgument($argument, $question, $default, $validator, + $maxAttempts, $comment, $commentFormat); + } + } + + /** + * Ask for an missing option. + * + * @param string $option + * @param string $question + * @param string|null $default + * @param callable|null $validator + * @param int|null $maxAttempts + * @param bool $comment + * @param string $commentFormat + */ + public function askForMissingOption( + $option, + $question, + $default = null, + $validator = null, + $maxAttempts = null, + $comment = null, + $commentFormat = "Option [%s] set to: %s" + ) { + try { + if( is_null($this->input->getOption($option)) ) { + $this->input->setOption($option, $this->ask($question, $default, $validator, $maxAttempts) ); + } + $optionValue = $this->input->getOption($option); + $validated = ( is_callable($validator) ? $validator($optionValue) : $optionValue ); + if( (bool) ( is_null($comment) ? $this->isDebug() : $comment ) ) + { + $this->comment( sprintf($commentFormat, $option, $validated) ); + } + } catch( InputValidationException $e ) { + $this->error("Validation Error: ".$e->getMessage()); + $this->askForMissingOption($option, $question, $default, $validator, + $maxAttempts, $comment, $commentFormat); + } + } + + /** + * {@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)); + } + + private function createBlock( + $messages, + $type = null, + $style = null, + $prefix = ' ', + $padding = false, + $escape = false + ) { + $indentLength = 0; + $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lines = array(); + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $wordwrap = wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true); + $lines = array_merge($lines, explode(PHP_EOL, $wordwrap)); + + if (count($messages) > 1 && $key < count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $multiplier = $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line); + $line .= str_repeat(' ', $multiplier); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } + +} \ No newline at end of file diff --git a/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php b/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php new file mode 100755 index 0000000000000..7760480623006 --- /dev/null +++ b/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php @@ -0,0 +1,15 @@ + 'foo'), new InputDefinition(array(new InputArgument('name')))); + $output = new TestOutput(); + + $io = new MagentoStyle($input,$output); + + $io->title("My Title"); + + $expected = "\r\n My Title\n ========\n\r\n"; + + $this->assertEquals($expected,$output->output,"Title does not match output"); + } + + public function testSectionStyle() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + $output = new TestOutput(); + + $io = new MagentoStyle($input,$output); + + $io->section("My Section"); + + $expected = "\r\n My Section\n ----------\n\r\n"; + + $this->assertEquals($expected,$output->output,"Section does not match output"); + } + +} + +class TestOutput extends Output +{ + public $output = ''; + + public function clear() + { + $this->output = ''; + } + + protected function doWrite($message, $newline) + { + $this->output .= $message.($newline ? "\n" : ''); + } +} \ No newline at end of file From 30be2697f17373dc287eb1b75f68fd197735d86e Mon Sep 17 00:00:00 2001 From: nmalevanec Date: Tue, 20 Feb 2018 10:55:29 +0200 Subject: [PATCH 2/3] Add MagentoStyle as Console input/output helper object to allow easier access to io helpers in symfony/console --- .../Console/InputValidationException.php | 2 +- .../Setup/Console/Style/MagentoStyle.php | 302 +++++++++++------ .../Console/Style/MagentoStyleInterface.php | 6 +- .../Unit/Console/Style/MagentoStyleTest.php | 314 ++++++++++++++++-- .../Test/Unit/Console/Style/TestOutput.php | 27 ++ 5 files changed, 516 insertions(+), 135 deletions(-) create mode 100644 setup/src/Magento/Setup/Test/Unit/Console/Style/TestOutput.php diff --git a/setup/src/Magento/Setup/Console/InputValidationException.php b/setup/src/Magento/Setup/Console/InputValidationException.php index 5d78bdabf3c8f..efa8a03bcfe49 100755 --- a/setup/src/Magento/Setup/Console/InputValidationException.php +++ b/setup/src/Magento/Setup/Console/InputValidationException.php @@ -11,4 +11,4 @@ class InputValidationException extends RuntimeException { -} \ No newline at end of file +} diff --git a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php index cc384d03b452c..4dec01f9497e1 100755 --- a/setup/src/Magento/Setup/Console/Style/MagentoStyle.php +++ b/setup/src/Magento/Setup/Console/Style/MagentoStyle.php @@ -1,5 +1,4 @@ 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. - $currentLength = $this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'); + $currentLength = $this->getTerminalWidth() - (int)(DIRECTORY_SEPARATOR === '\\'); $this->lineLength = min($currentLength, self::MAX_LINE_LENGTH); - parent::__construct($output); } @@ -52,17 +88,22 @@ public function __construct(InputInterface $input, OutputInterface $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 + * @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 + * @return void */ - public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) - { - $messages = is_array($messages) ? array_values($messages) : array($messages); - + public function block( + $messages, + string $type = null, + string $style = null, + string $prefix = ' ', + bool $padding = false + ) { + $messages = is_array($messages) ? array_values($messages) : [$messages]; $this->autoPrependBlock(); - $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true)); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding)); $this->newLine(); } @@ -73,10 +114,10 @@ public function title($message) { $this->autoPrependBlock(); $bar = str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message)); - $this->writeln(array( + $this->writeln([ sprintf(' %s', OutputFormatter::escapeTrailingBackslash($message)), sprintf(' %s', $bar), - )); + ]); $this->newLine(); } @@ -87,10 +128,10 @@ public function section($message) { $this->autoPrependBlock(); $bar = str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message)); - $this->writeln(array( + $this->writeln([ sprintf(' %s', OutputFormatter::escapeTrailingBackslash($message)), sprintf(' %s', $bar), - )); + ]); $this->newLine(); } @@ -114,10 +155,9 @@ public function listing(array $elements) public function text($message) { $this->autoPrependText(); - - $messages = is_array($message) ? array_values($message) : array($message); - foreach ($messages as $message) { - $this->writeln(sprintf(' %s', $message)); + $messages = is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $singleMessage) { + $this->writeln(sprintf(' %s', $singleMessage)); } } @@ -125,6 +165,8 @@ public function text($message) * Formats a command comment. * * @param string|array $message + * @param bool $padding + * @return void */ public function comment($message, $padding = false) { @@ -190,6 +232,7 @@ public function table(array $headers, array $rows) /** * {@inheritdoc} + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException */ public function ask($question, $default = null, $validator = null, $maxAttempts = null) { @@ -202,6 +245,7 @@ public function ask($question, $default = null, $validator = null, $maxAttempts /** * {@inheritdoc} + * @throws \Symfony\Component\Console\Exception\LogicException */ public function askHidden($question, $validator = null) { @@ -245,6 +289,8 @@ public function progressStart($max = 0) /** * {@inheritdoc} + * @throws \Symfony\Component\Console\Exception\LogicException + * @throws \Symfony\Component\Console\Exception\RuntimeException */ public function progressAdvance($step = 1) { @@ -253,6 +299,7 @@ public function progressAdvance($step = 1) /** * {@inheritdoc} + * @throws \Symfony\Component\Console\Exception\RuntimeException */ public function progressFinish() { @@ -275,6 +322,8 @@ public function createProgressBar($max = 0) } /** + * Ask user question. + * * @param Question $question * * @return string @@ -293,7 +342,7 @@ public function askQuestion(Question $question) if ($this->input->isInteractive()) { $this->newLine(); - $this->bufferedOutput->write("\n"); + $this->bufferedOutput->write(PHP_EOL); } return $answer; @@ -302,74 +351,88 @@ public function askQuestion(Question $question) /** * Ask for an missing argument. * - * @param string $argument - * @param string $question - * @param string|null $default + * @param string $argument + * @param string $question + * @param string|null $default * @param callable|null $validator - * @param int|null $maxAttempts - * @param bool $comment - * @param string $commentFormat + * @param int|null $maxAttempts + * @param bool $comment + * @param string $commentFormat + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException */ public function askForMissingArgument( - $argument, - $question, - $default = null, - $validator = null, - $maxAttempts = null, - $comment = null, - $commentFormat = "Argument [%s] set to: %s" + string $argument, + string $question, + string $default = null, + callable $validator = null, + int $maxAttempts = null, + bool $comment = null, + string $commentFormat = 'Argument [%s] set to: %s' ) { try { - if( is_null($this->input->getArgument($argument)) ) { - $this->input->setArgument($argument, $this->ask($question, $default, $validator, $maxAttempts) ); + if ($this->input->getArgument($argument) === null) { + $this->input->setArgument($argument, $this->ask($question, $default, $validator, $maxAttempts)); } $argumentValue = $this->input->getArgument($argument); - $validated = ( is_callable($validator) ? $validator($argumentValue) : $argumentValue ); - if( (bool) ( is_null($comment) ? $this->isDebug() : $comment ) ) - { - $this->comment( sprintf($commentFormat, $argument, $validated) ); + $validated = (is_callable($validator) ? $validator($argumentValue) : $argumentValue); + if ((bool)($comment ?? $this->isDebug())) { + $this->comment(sprintf($commentFormat, $argument, $validated)); } - } catch( InputValidationException $e ) { - $this->error("Validation Error: ".$e->getMessage()); - $this->askForMissingArgument($argument, $question, $default, $validator, - $maxAttempts, $comment, $commentFormat); + } catch (InputValidationException $e) { + $this->error('Validation Error: ' . $e->getMessage()); + $this->askForMissingArgument( + $argument, + $question, + $default, + $validator, + $maxAttempts, + $comment, + $commentFormat + ); } } /** * Ask for an missing option. * - * @param string $option - * @param string $question - * @param string|null $default + * @param string $option + * @param string $question + * @param string|null $default * @param callable|null $validator - * @param int|null $maxAttempts - * @param bool $comment - * @param string $commentFormat + * @param int|null $maxAttempts + * @param bool $comment + * @param string $commentFormat + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException */ public function askForMissingOption( - $option, - $question, - $default = null, - $validator = null, - $maxAttempts = null, - $comment = null, - $commentFormat = "Option [%s] set to: %s" + string $option, + string $question, + string $default = null, + callable $validator = null, + int $maxAttempts = null, + bool $comment = null, + string $commentFormat = 'Option [%s] set to: %s' ) { try { - if( is_null($this->input->getOption($option)) ) { - $this->input->setOption($option, $this->ask($question, $default, $validator, $maxAttempts) ); + if (null === $this->input->getOption($option)) { + $this->input->setOption($option, $this->ask($question, $default, $validator, $maxAttempts)); } $optionValue = $this->input->getOption($option); - $validated = ( is_callable($validator) ? $validator($optionValue) : $optionValue ); - if( (bool) ( is_null($comment) ? $this->isDebug() : $comment ) ) - { - $this->comment( sprintf($commentFormat, $option, $validated) ); + $validated = (is_callable($validator) ? $validator($optionValue) : $optionValue); + if ((bool)($comment ?? $this->isDebug())) { + $this->comment(sprintf($commentFormat, $option, $validated)); } - } catch( InputValidationException $e ) { - $this->error("Validation Error: ".$e->getMessage()); - $this->askForMissingOption($option, $question, $default, $validator, - $maxAttempts, $comment, $commentFormat); + } catch (InputValidationException $e) { + $this->error('Validation Error: ' . $e->getMessage()); + $this->askForMissingOption( + $option, + $question, + $default, + $validator, + $maxAttempts, + $comment, + $commentFormat + ); } } @@ -397,11 +460,14 @@ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) public function newLine($count = 1) { parent::newLine($count); - $this->bufferedOutput->write(str_repeat("\n", $count)); + $this->bufferedOutput->write(str_repeat(PHP_EOL, $count)); } /** + * Get progress bar instance. + * * @return ProgressBar + * @throws RuntimeException in case progress bar hasn't been instantiated yet. */ private function getProgressBar() { @@ -412,6 +478,9 @@ private function getProgressBar() return $this->progressBar; } + /** + * @return int + */ private function getTerminalWidth() { $application = new Application(); @@ -420,22 +489,31 @@ private function getTerminalWidth() return $dimensions[0] ?: self::MAX_LINE_LENGTH; } + /** + * Add empty line before output element in case there were no empty lines before. + * + * @return void + */ private function autoPrependBlock() { - $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); - + $chars = substr($this->bufferedOutput->fetch(), -2); if (!isset($chars[0])) { - return $this->newLine(); //empty history, so we should start with a new line. + $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")); + $this->newLine(2 - substr_count($chars, PHP_EOL)); } + /** + * Add empty line before text(listing) output element. + * + * @return void + */ private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); //Prepend new line if last char isn't EOL: - if ("\n" !== substr($fetched, -1)) { + if (PHP_EOL !== substr($fetched, -1)) { $this->newLine(); } } @@ -446,57 +524,47 @@ private function reduceBuffer($messages) // 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)); + }, array_merge([$this->bufferedOutput->fetch()], (array)$messages)); } + /** + * Build output in block style. + * + * @param array $messages + * @param string|null $type + * @param string|null $style + * @param string $prefix + * @param bool $padding + * @return array + */ private function createBlock( - $messages, - $type = null, - $style = null, - $prefix = ' ', - $padding = false, - $escape = false + array $messages, + string $type = null, + string $style = null, + string $prefix = ' ', + bool $padding = false ) { $indentLength = 0; $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); - $lines = array(); - if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } - - // wrap and add newlines for each element - foreach ($messages as $key => $message) { - if ($escape) { - $message = OutputFormatter::escape($message); - } - - $wordwrap = wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true); - $lines = array_merge($lines, explode(PHP_EOL, $wordwrap)); - - if (count($messages) > 1 && $key < count($messages) - 1) { - $lines[] = ''; - } - } - + $lines = $this->getBlockLines($messages, $prefixLength, $indentLength); $firstLineIndex = 0; if ($padding && $this->isDecorated()) { $firstLineIndex = 1; array_unshift($lines, ''); $lines[] = ''; } - foreach ($lines as $i => &$line) { if (null !== $type) { - $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + $line = $firstLineIndex === $i ? $type . $line : $lineIndentation . $line; } - - $line = $prefix.$line; + $line = $prefix . $line; $multiplier = $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line); $line .= str_repeat(' ', $multiplier); - if ($style) { $line = sprintf('<%s>%s', $style, $line); } @@ -505,4 +573,30 @@ private function createBlock( return $lines; } -} \ No newline at end of file + /** + * Wrap and add new lines for each element. + * + * @param array $messages + * @param int $prefixLength + * @param int $indentLength + * @return array + */ + private function getBlockLines( + array $messages, + int $prefixLength, + int $indentLength + ) { + $lines = [[]]; + foreach ($messages as $key => $message) { + $message = OutputFormatter::escape($message); + $wordwrap = wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true); + $lines[] = explode(PHP_EOL, $wordwrap); + if (count($messages) > 1 && $key < count($messages) - 1) { + $lines[][] = ''; + } + } + $lines = array_merge(...$lines); + + return $lines; + } +} diff --git a/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php b/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php index 7760480623006..a7aba31549699 100755 --- a/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php +++ b/setup/src/Magento/Setup/Console/Style/MagentoStyleInterface.php @@ -8,8 +8,10 @@ use Symfony\Component\Console\Style\StyleInterface; - +/** + * Interface for output decorator. + */ interface MagentoStyleInterface extends StyleInterface { -} \ No newline at end of file +} diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php index 6ad7c132f2909..b4e8c32dadcde 100755 --- a/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php @@ -1,62 +1,320 @@ 'foo'], new InputDefinition([new InputArgument('name')])); + $this->testOutput = new TestOutput(); + $this->magentoStyle = new MagentoStyle($input, $this->testOutput); + } + + /** + * Test style decorator will output block with correct style. + * + * @return void + */ + public function testBlockStyle() + { + $this->magentoStyle->block( + ['test first message', 'test second message'], + 'testBlockType', + 'testBlockStyle', + 'testBlockPrefix' + ); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + 'testBlockPrefix[testBlockType] test first message ' + . PHP_EOL . 'testBlockPrefix ' + . PHP_EOL . 'testBlockPrefix test second message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd + $this->assertEquals($expected, $this->testOutput->output, 'Block does not match output'); + } + + /** + * Test style decorator will add title with correct style. + * + * @return void + */ public function testTitleStyle() { - $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); - $output = new TestOutput(); + $this->magentoStyle->title('My Title'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . ' My Title' . PHP_EOL . ' ========' . PHP_EOL . PHP_EOL; + + $this->assertEquals($expected, $this->testOutput->output, 'Title does not match output'); + } + + /** + * Test style decorator will output section with correct style. + * + * @return void + */ + public function testSectionStyle() + { + $this->magentoStyle->section('My Section'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . ' My Section' . PHP_EOL . ' ----------' . PHP_EOL . PHP_EOL; - $io = new MagentoStyle($input,$output); + $this->assertEquals($expected, $this->testOutput->output, 'Section does not match output'); + } - $io->title("My Title"); + /** + * Test style decorator will output listing with proper style. + * + * @return void + */ + public function testListingStyle() + { + $this->magentoStyle->listing(['test first element', 'test second element']); + $expected = PHP_EOL . ' * test first element' . PHP_EOL . ' * test second element' . PHP_EOL . PHP_EOL; - $expected = "\r\n My Title\n ========\n\r\n"; + $this->assertEquals($expected, $this->testOutput->output, 'Listing does not match output'); + } + + /** + * Test style decorator will output text with proper style. + * + * @return void + */ + public function testTextStyle() + { + $this->magentoStyle->text('test message'); + $expected = PHP_EOL . ' test message' . PHP_EOL; - $this->assertEquals($expected,$output->output,"Title does not match output"); + $this->assertEquals($expected, $this->testOutput->output, 'Text does not match output'); } - public function testSectionStyle() + /** + * Test style decorator will output comment with proper style. + * + * @return void + */ + public function testCommentStyle() + { + $this->magentoStyle->comment('test comment'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' test comment ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd + + $this->assertEquals($expected, $this->testOutput->output, 'Comment does not match output'); + } + + /** + * Test style decorator will output success message with proper style. + * + * @return void + */ + public function testSuccessStyle() { - $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); - $output = new TestOutput(); + $this->magentoStyle->success('test success message'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' [SUCCESS] test success message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd - $io = new MagentoStyle($input,$output); + $this->assertEquals($expected, $this->testOutput->output, 'Success message does not match output'); + } - $io->section("My Section"); + /** + * Test style decorator will output error message with proper style. + * + * @return void + */ + public function testErrorStyle() + { + $this->magentoStyle->error('test error message'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' [ERROR] test error message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd - $expected = "\r\n My Section\n ----------\n\r\n"; + $this->assertEquals($expected, $this->testOutput->output, 'Error message does not match output'); + } - $this->assertEquals($expected,$output->output,"Section does not match output"); + /** + * Test style decorator will output warning message with proper style. + * + * @return void + */ + public function testWarningStyle() + { + $this->magentoStyle->warning('test warning message'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' [WARNING] test warning message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd + + $this->assertEquals($expected, $this->testOutput->output, 'Warning message does not match output'); } -} + /** + * Test style decorator will output note message with proper style. + * + * @return void + */ + public function testNoteStyle() + { + $this->magentoStyle->note('test note message'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' [NOTE] test note message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd -class TestOutput extends Output -{ - public $output = ''; + $this->assertEquals($expected, $this->testOutput->output, 'Note message does not match output'); + } - public function clear() + /** + * Test style decorator will output caution message with proper style. + * + * @return void + */ + public function testCautionStyle() { - $this->output = ''; + $this->magentoStyle->caution('test caution message'); + // @codingStandardsIgnoreStart + $expected = PHP_EOL . PHP_EOL . PHP_EOL . + ' ! [CAUTION] test caution message ' + . PHP_EOL . PHP_EOL; + // @codingStandardsIgnoreEnd + + $this->assertEquals($expected, $this->testOutput->output, 'Caution message does not match output'); } - protected function doWrite($message, $newline) + /** + * Test style decorator will output table with proper style. + * + * @return void + */ + public function testTableStyle() { - $this->output .= $message.($newline ? "\n" : ''); + $headers = [ + [new TableCell('Main table title', ['colspan' => 2])], + ['testHeader1', 'testHeader2', 'testHeader3'], + ]; + $rows = [ + [ + 'testValue1', + 'testValue2', + new TableCell('testValue3', ['rowspan' => 2]), + ], + ['testValue4', 'testValue5'], + ]; + $this->magentoStyle->table($headers, $rows); + $expected = ' ------------- ------------- ------------- ' . PHP_EOL . + ' Main table title ' . PHP_EOL . + ' ------------- ------------- ------------- ' . PHP_EOL . + ' testHeader1 testHeader2 testHeader3 ' . PHP_EOL . + ' ------------- ------------- ------------- ' . PHP_EOL . + ' testValue1 testValue2 testValue3 ' . PHP_EOL . + ' testValue4 testValue5 ' . PHP_EOL . + ' ------------- ------------- ------------- ' . PHP_EOL . PHP_EOL; + + $this->assertEquals($expected, $this->testOutput->output, 'Table does not match output'); } -} \ No newline at end of file + + /** + * @return void + */ + public function testAsk() + { + $objectManager = new ObjectManager($this); + $formatter = $this->getMockBuilder(OutputFormatter::class) + ->disableOriginalConstructor() + ->getMock(); + $input = $this->getMockBuilder(InputInterface::class) + ->setMethods(['isInteractive']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $input->expects($this->exactly(2)) + ->method('isInteractive') + ->willReturn(false); + $output = $this->getMockBuilder(OutputInterface::class) + ->setMethods(['getVerbosity', 'getFormatter']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $output->expects($this->once()) + ->method('getVerbosity') + ->willReturn(32); + $output->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + $magentoStyle = $objectManager->getObject( + MagentoStyle::class, + [ + 'input' => $input, + 'output' => $output, + ] + ); + $questionHelper = $this->getMockBuilder(SymfonyQuestionHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $questionHelper->expects($this->once()) + ->method('ask') + ->willReturn('test Answer'); + $objectManager->setBackwardCompatibleProperty($magentoStyle, 'questionHelper', $questionHelper); + + $this->assertEquals( + 'test Answer', + $magentoStyle->ask('test question?', 'test default') + ); + } + + /** + * Test style decorator will output progress with proper style. + * + * @return void + */ + public function testProgress() + { + $this->magentoStyle->progressStart(2); + $this->magentoStyle->progressAdvance(3); + $this->magentoStyle->progressFinish(); + $expected = ' 0/2 [> ] 0%' . PHP_EOL . + ' 3/3 [============================] 100%' . PHP_EOL . PHP_EOL; + $this->assertEquals($expected, $this->testOutput->output, 'Progress does not match output'); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Style/TestOutput.php b/setup/src/Magento/Setup/Test/Unit/Console/Style/TestOutput.php new file mode 100644 index 0000000000000..1407e5ed183e4 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Console/Style/TestOutput.php @@ -0,0 +1,27 @@ +output = ''; + } + + protected function doWrite($message, $newline) + { + $this->output .= $message . ($newline ? "\n" : ''); + } +} From 7be9367846ad62dd89857696b50cb5e7f72a855b Mon Sep 17 00:00:00 2001 From: Magento Community Engineering <31669971+magento-engcom-team@users.noreply.github.com> Date: Fri, 16 Feb 2018 11:39:16 -0600 Subject: [PATCH 3/3] Add MagentoStyle as Console Input/output helper object... #11504 - fixed broken Travis tests --- .../Unit/Console/Style/MagentoStyleTest.php | 65 +++++-------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php index b4e8c32dadcde..92aa43251ba26 100755 --- a/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Style/MagentoStyleTest.php @@ -62,12 +62,12 @@ public function testBlockStyle() ); // @codingStandardsIgnoreStart $expected = PHP_EOL . PHP_EOL . PHP_EOL . - 'testBlockPrefix[testBlockType] test first message ' - . PHP_EOL . 'testBlockPrefix ' - . PHP_EOL . 'testBlockPrefix test second message ' + '\testBlockPrefix\[testBlockType\] test first message\s+' + . PHP_EOL . '\testBlockPrefix\s+' + . PHP_EOL . '\testBlockPrefix \s+ test second message\s+' . PHP_EOL . PHP_EOL; // @codingStandardsIgnoreEnd - $this->assertEquals($expected, $this->testOutput->output, 'Block does not match output'); + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Block does not match output'); } /** @@ -79,7 +79,6 @@ public function testTitleStyle() { $this->magentoStyle->title('My Title'); $expected = PHP_EOL . PHP_EOL . PHP_EOL . ' My Title' . PHP_EOL . ' ========' . PHP_EOL . PHP_EOL; - $this->assertEquals($expected, $this->testOutput->output, 'Title does not match output'); } @@ -92,7 +91,6 @@ public function testSectionStyle() { $this->magentoStyle->section('My Section'); $expected = PHP_EOL . PHP_EOL . PHP_EOL . ' My Section' . PHP_EOL . ' ----------' . PHP_EOL . PHP_EOL; - $this->assertEquals($expected, $this->testOutput->output, 'Section does not match output'); } @@ -105,7 +103,6 @@ public function testListingStyle() { $this->magentoStyle->listing(['test first element', 'test second element']); $expected = PHP_EOL . ' * test first element' . PHP_EOL . ' * test second element' . PHP_EOL . PHP_EOL; - $this->assertEquals($expected, $this->testOutput->output, 'Listing does not match output'); } @@ -130,13 +127,8 @@ public function testTextStyle() public function testCommentStyle() { $this->magentoStyle->comment('test comment'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' test comment ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Comment does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . '\s+test comment\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Comment does not match output'); } /** @@ -147,13 +139,8 @@ public function testCommentStyle() public function testSuccessStyle() { $this->magentoStyle->success('test success message'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' [SUCCESS] test success message ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Success message does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . ' \[SUCCESS\] test success message\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Success message does not match output'); } /** @@ -164,13 +151,8 @@ public function testSuccessStyle() public function testErrorStyle() { $this->magentoStyle->error('test error message'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' [ERROR] test error message ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Error message does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . '\s+\[ERROR\] test error message\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Error message does not match output'); } /** @@ -181,13 +163,8 @@ public function testErrorStyle() public function testWarningStyle() { $this->magentoStyle->warning('test warning message'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' [WARNING] test warning message ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Warning message does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . '\s+\[WARNING\] test warning message\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Warning message does not match output'); } /** @@ -198,13 +175,8 @@ public function testWarningStyle() public function testNoteStyle() { $this->magentoStyle->note('test note message'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' [NOTE] test note message ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Note message does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . '\s+\[NOTE\] test note message\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Note message does not match output'); } /** @@ -215,13 +187,8 @@ public function testNoteStyle() public function testCautionStyle() { $this->magentoStyle->caution('test caution message'); - // @codingStandardsIgnoreStart - $expected = PHP_EOL . PHP_EOL . PHP_EOL . - ' ! [CAUTION] test caution message ' - . PHP_EOL . PHP_EOL; - // @codingStandardsIgnoreEnd - - $this->assertEquals($expected, $this->testOutput->output, 'Caution message does not match output'); + $expected = PHP_EOL . PHP_EOL . PHP_EOL . '\s+! \[CAUTION\] test caution message\s+' . PHP_EOL . PHP_EOL; + $this->assertRegExp('/' . $expected . '/', $this->testOutput->output, 'Caution message does not match output'); } /**