composer update

This commit is contained in:
Manish Verma
2018-12-05 10:50:52 +05:30
parent 9eabcacfa7
commit 4addd1e9c6
3328 changed files with 156676 additions and 138988 deletions

View File

@@ -753,12 +753,20 @@ class Application
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$title = sprintf(' [%s%s] ', \get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$class = \get_class($e);
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
if (false !== strpos($message, "class@anonymous\0")) {
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
}, $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
$lines = array();
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : array() as $line) {
@@ -793,6 +801,13 @@ class Application
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, array(
'function' => '',
'file' => $e->getFile() ?: 'n/a',
'line' => $e->getLine() ?: 'n/a',
'args' => array(),
));
for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';

View File

@@ -1,6 +1,17 @@
CHANGELOG
=========
4.2.0
-----
* allowed passing commands as `array($process, 'ENV_VAR' => 'value')` to
`ProcessHelper::run()` to pass environment variables
* deprecated passing a command as a string to `ProcessHelper::run()`,
pass it the command as an array of its arguments instead
* made the `ProcessHelper` class final
* added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`)
* added `capture_stderr_separately` option to `CommandTester::execute()`
4.1.0
-----

View File

@@ -150,7 +150,7 @@ class Command
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return null|int null or 0 if everything went fine, or an error code
* @return int|null null or 0 if everything went fine, or an error code
*
* @throws LogicException When this abstract method is not implemented
*
@@ -173,10 +173,14 @@ class Command
}
/**
* Initializes the command just after the input has been validated.
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
@@ -297,14 +301,13 @@ class Command
$this->definition->addOptions($this->application->getDefinition()->getOptions());
$this->applicationDefinitionMerged = true;
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->application->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
}
$this->applicationDefinitionMerged = true;
if ($mergeArgs) {
$this->applicationDefinitionMergedWithArgs = true;
}
}
@@ -357,10 +360,12 @@ class Command
/**
* Adds an argument.
*
* @param string $name The argument name
* @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
* @param string $name The argument name
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*
* @return $this
*/
@@ -374,11 +379,13 @@ class Command
/**
* Adds an option.
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param int $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
* @param string $name The option name
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*
* @return $this
*/

View File

@@ -12,7 +12,6 @@
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\Store\FlockStore;
@@ -36,7 +35,7 @@ trait LockableTrait
private function lock($name = null, $blocking = false)
{
if (!class_exists(SemaphoreStore::class)) {
throw new RuntimeException('To enable the locking feature you must install the symfony/lock component.');
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
if (null !== $this->lock) {

View File

@@ -16,6 +16,6 @@ namespace Symfony\Component\Console\Exception;
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
interface ExceptionInterface
interface ExceptionInterface extends \Throwable
{
}

View File

@@ -17,8 +17,9 @@ use Symfony\Component\Console\Exception\InvalidArgumentException;
* Formatter class for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class OutputFormatter implements OutputFormatterInterface
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = array();
@@ -130,10 +131,18 @@ class OutputFormatter implements OutputFormatterInterface
*/
public function format($message)
{
$message = (string) $message;
return $this->formatAndWrap((string) $message, 0);
}
/**
* {@inheritdoc}
*/
public function formatAndWrap(string $message, int $width)
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
$currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
@@ -144,7 +153,7 @@ class OutputFormatter implements OutputFormatterInterface
}
// add the text up to the next tag
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
// opening tag?
@@ -158,7 +167,7 @@ class OutputFormatter implements OutputFormatterInterface
// </>
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
$output .= $this->applyCurrentStyle($text);
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
} else {
@@ -166,7 +175,7 @@ class OutputFormatter implements OutputFormatterInterface
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset));
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, array("\0" => '\\', '\\<' => '<'));
@@ -223,8 +232,46 @@ class OutputFormatter implements OutputFormatterInterface
/**
* Applies current style from stack to text, if must be applied.
*/
private function applyCurrentStyle(string $text): string
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
{
return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
if ('' === $text) {
return '';
}
if (!$width) {
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
}
if (!$currentLineLength && '' !== $current) {
$text = ltrim($text);
}
if ($currentLineLength) {
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
$text = substr($text, $i);
} else {
$prefix = '';
}
preg_match('~(\\n)$~', $text, $matches);
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
$text = rtrim($text, "\n").($matches[1] ?? '');
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
$text = "\n".$text;
}
$lines = explode("\n", $text);
if ($width === $currentLineLength = \strlen(end($lines))) {
$currentLineLength = 0;
}
if ($this->isDecorated()) {
foreach ($lines as $i => $line) {
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
}
}
return implode("\n", $lines);
}
}

View File

@@ -90,11 +90,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
if (!isset(static::$availableForegroundColors[$color])) {
throw new InvalidArgumentException(sprintf(
'Invalid foreground color specified: "%s". Expected one of (%s)',
$color,
implode(', ', array_keys(static::$availableForegroundColors))
));
throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
}
$this->foreground = static::$availableForegroundColors[$color];
@@ -116,11 +112,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
}
if (!isset(static::$availableBackgroundColors[$color])) {
throw new InvalidArgumentException(sprintf(
'Invalid background color specified: "%s". Expected one of (%s)',
$color,
implode(', ', array_keys(static::$availableBackgroundColors))
));
throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
}
$this->background = static::$availableBackgroundColors[$color];
@@ -136,11 +128,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
public function setOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf(
'Invalid option specified: "%s". Expected one of (%s)',
$option,
implode(', ', array_keys(static::$availableOptions))
));
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
if (!\in_array(static::$availableOptions[$option], $this->options)) {
@@ -158,11 +146,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
public function unsetOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf(
'Invalid option specified: "%s". Expected one of (%s)',
$option,
implode(', ', array_keys(static::$availableOptions))
));
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
$pos = array_search(static::$availableOptions[$option], $this->options);

View File

@@ -12,11 +12,12 @@
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack
class OutputFormatterStyleStack implements ResetInterface
{
/**
* @var OutputFormatterStyleInterface[]

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output that supports word wrapping.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
/**
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
*/
public function formatAndWrap(string $message, int $width);
}

View File

@@ -20,18 +20,20 @@ use Symfony\Component\Process\Process;
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.2
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
* @param OutputInterface $output An OutputInterface instance
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
*
* @return Process The process that ran
*/
@@ -44,9 +46,22 @@ class ProcessHelper extends Helper
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$process = $cmd;
} else {
$cmd = array($cmd);
}
if (!\is_array($cmd)) {
@trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), E_USER_DEPRECATED);
$cmd = array(\method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd));
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = array();
} elseif (($cmd[0] ?? null) instanceof Process) {
$process = $cmd[0];
unset($cmd[0]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should an array whose first is element is either the path to the binary to run of a "Process" object.', __METHOD__));
}
if ($verbosity <= $output->getVerbosity()) {
@@ -57,7 +72,7 @@ class ProcessHelper extends Helper
$callback = $this->wrapCallback($output, $process, $callback);
}
$process->run($callback);
$process->run($callback, $cmd);
if ($verbosity <= $output->getVerbosity()) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
@@ -46,13 +47,23 @@ class QuestionHelper extends Helper
}
if (!$input->isInteractive()) {
if ($question instanceof ChoiceQuestion) {
$default = $question->getDefault();
if (null !== $default && $question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
return $choices[$question->getDefault()];
if (!$question->isMultiselect()) {
return isset($choices[$default]) ? $choices[$default] : $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = trim($v);
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
}
}
return $question->getDefault();
return $default;
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
@@ -89,7 +100,7 @@ class QuestionHelper extends Helper
/**
* Asks the question to the user.
*
* @return bool|mixed|null|string
* @return bool|mixed|string|null
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
@@ -123,6 +134,10 @@ class QuestionHelper extends Helper
$ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent($ret);
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {

View File

@@ -58,7 +58,7 @@ class SymfonyQuestionHelper extends QuestionHelper
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default]));
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));
break;

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
@@ -34,6 +35,9 @@ class Table
private const BORDER_OUTSIDE = 0;
private const BORDER_INSIDE = 1;
private $headerTitle;
private $footerTitle;
/**
* Table headers.
*/
@@ -77,6 +81,7 @@ class Table
* @var array
*/
private $columnWidths = array();
private $columnMaxWidths = array();
private static $styles;
@@ -180,11 +185,7 @@ class Table
*/
public function getColumnStyle($columnIndex)
{
if (isset($this->columnStyles[$columnIndex])) {
return $this->columnStyles[$columnIndex];
}
return $this->getStyle();
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
}
/**
@@ -219,6 +220,25 @@ class Table
return $this;
}
/**
* Sets the maximum width of a column.
*
* Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
* formatted strings are preserved.
*
* @return $this
*/
public function setColumnMaxWidth(int $columnIndex, int $width): self
{
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter())));
}
$this->columnMaxWidths[$columnIndex] = $width;
return $this;
}
public function setHeaders(array $headers)
{
$headers = array_values($headers);
@@ -290,19 +310,32 @@ class Table
return $this;
}
public function setHeaderTitle(?string $title): self
{
$this->headerTitle = $title;
return $this;
}
public function setFooterTitle(?string $title): self
{
$this->footerTitle = $title;
return $this;
}
/**
* Renders table to output.
*
* Example:
* <code>
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
* </code>
*
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
public function render()
{
@@ -331,15 +364,17 @@ class Table
}
if ($isHeader || $isFirstRow) {
$this->renderRowSeparator($isFirstRow ? self::SEPARATOR_TOP_BOTTOM : self::SEPARATOR_TOP);
if ($isFirstRow) {
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
$isFirstRow = false;
} else {
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
}
}
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM);
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
$this->cleanup();
$this->rendered = true;
@@ -348,9 +383,11 @@ class Table
/**
* Renders horizontal header separator.
*
* Example: <code>+-----+-----------+-------+</code>
* Example:
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID)
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
{
if (0 === $count = $this->numberOfColumns) {
return;
@@ -378,6 +415,23 @@ class Table
$markup .= $column === $count - 1 ? $rightChar : $midChar;
}
if (null !== $title) {
$titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title));
$markupLength = Helper::strlen($markup);
if ($titleLength > $limit = $markupLength - 4) {
$titleLength = $limit;
$formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, ''));
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
}
$titleStart = ($markupLength - $titleLength) / 2;
if (false === mb_detect_encoding($markup, null, true)) {
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
} else {
$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
}
}
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
}
@@ -394,7 +448,9 @@ class Table
/**
* Renders table row.
*
* Example: <code>| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |</code>
* Example:
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat)
{
@@ -458,12 +514,17 @@ class Table
private function buildTableRows($rows)
{
/** @var WrappableOutputFormatterInterface $formatter */
$formatter = $this->output->getFormatter();
$unmergedRows = array();
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column]);
}
if (!strstr($cell, "\n")) {
continue;
}
@@ -652,13 +713,13 @@ class Table
$lengths[] = $this->getCellWidth($row, $column);
}
$this->effectiveColumnWidths[$column] = max($lengths) + \strlen($this->style->getCellRowContentFormat()) - 2;
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
}
}
private function getColumnSeparatorWidth(): int
{
return \strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
}
private function getCellWidth(array $row, int $column): int
@@ -671,8 +732,9 @@ class Table
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
$cellWidth = max($cellWidth, $columnWidth);
return max($cellWidth, $columnWidth);
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
}
/**

View File

@@ -40,6 +40,8 @@ class TableStyle
private $crossingTopLeftBottomChar = '+';
private $crossingTopMidBottomChar = '+';
private $crossingTopRightBottomChar = '+';
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
@@ -276,7 +278,7 @@ class TableStyle
/**
* Gets crossing character.
*
* @return string $crossingChar
* @return string
*/
public function getCrossingChar()
{
@@ -429,4 +431,28 @@ class TableStyle
{
return $this->padType;
}
public function getHeaderTitleFormat(): string
{
return $this->headerTitleFormat;
}
public function setHeaderTitleFormat(string $format): self
{
$this->headerTitleFormat = $format;
return $this;
}
public function getFooterTitleFormat(): string
{
return $this->footerTitleFormat;
}
public function setFooterTitleFormat(string $format): self
{
$this->footerTitleFormat = $format;
return $this;
}
}

View File

@@ -121,7 +121,8 @@ class ArgvInput extends Input
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
$encoding = mb_detect_encoding($name, null, true);
throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
}
$option = $this->definition->getOptionForShortcut($name[$i]);

View File

@@ -31,10 +31,10 @@ class InputArgument
private $description;
/**
* @param string $name The argument name
* @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
* @param string $name The argument name
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
@@ -86,7 +86,7 @@ class InputArgument
/**
* Sets the default value.
*
* @param mixed $default The default value
* @param string|string[]|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
@@ -110,7 +110,7 @@ class InputArgument
/**
* Returns the default value.
*
* @return mixed The default value
* @return string|string[]|null The default value
*/
public function getDefault()
{

View File

@@ -21,8 +21,6 @@ interface InputAwareInterface
{
/**
* Sets the Console Input.
*
* @param InputInterface
*/
public function setInput(InputInterface $input);
}

View File

@@ -20,8 +20,8 @@ use Symfony\Component\Console\Exception\LogicException;
* Usage:
*
* $definition = new InputDefinition(array(
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ));
*
* @author Fabien Potencier <fabien@symfony.com>

View File

@@ -61,6 +61,8 @@ interface InputInterface
/**
* Binds the current Input instance with the given arguments and options.
*
* @throws RuntimeException
*/
public function bind(InputDefinition $definition);
@@ -83,7 +85,7 @@ interface InputInterface
*
* @param string $name The argument name
*
* @return mixed The argument value
* @return string|string[]|null The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
@@ -92,8 +94,8 @@ interface InputInterface
/**
* Sets an argument value by name.
*
* @param string $name The argument name
* @param string $value The argument value
* @param string $name The argument name
* @param string|string[]|null $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
@@ -120,7 +122,7 @@ interface InputInterface
*
* @param string $name The option name
*
* @return mixed The option value
* @return string|string[]|bool|null The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
@@ -129,8 +131,8 @@ interface InputInterface
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|bool $value The option value
* @param string $name The option name
* @param string|string[]|bool|null $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/

View File

@@ -33,11 +33,11 @@ class InputOption
private $description;
/**
* @param string $name The option name
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for self::VALUE_NONE)
* @param string $name The option name
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
@@ -149,7 +149,7 @@ class InputOption
/**
* Sets the default value.
*
* @param mixed $default The default value
* @param string|string[]|int|bool|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
@@ -173,7 +173,7 @@ class InputOption
/**
* Returns the default value.
*
* @return mixed The default value
* @return string|string[]|int|bool|null The default value
*/
public function getDefault()
{

View File

@@ -77,6 +77,18 @@ class ConsoleSectionOutput extends StreamOutput
return implode('', $this->content);
}
/**
* @internal
*/
public function addContent(string $input)
{
foreach (explode(PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = PHP_EOL;
}
}
/**
* {@inheritdoc}
*/
@@ -88,11 +100,7 @@ class ConsoleSectionOutput extends StreamOutput
$erasedContent = $this->popStreamContentUntilCurrentSection();
foreach (explode(PHP_EOL, $message) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = PHP_EOL;
}
$this->addContent($message);
parent::doWrite($message, true);
parent::doWrite($erasedContent, false);

View File

@@ -20,11 +20,11 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*
* Usage:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* As `StreamOutput` can use any stream, you can also use a file:
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -70,7 +70,11 @@ class StreamOutput extends Output
*/
protected function doWrite($message, $newline)
{
if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) {
if ($newline) {
$message .= PHP_EOL;
}
if (false === @fwrite($this->stream, $message)) {
// should never happen
throw new RuntimeException('Unable to write output.');
}

View File

@@ -117,7 +117,7 @@ class Question
/**
* Gets values for the autocompleter.
*
* @return null|iterable
* @return iterable|null
*/
public function getAutocompleterValues()
{
@@ -127,7 +127,7 @@ class Question
/**
* Sets values for the autocompleter.
*
* @param null|iterable $values
* @param iterable|null $values
*
* @return $this
*
@@ -141,7 +141,7 @@ class Question
}
if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.');
throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.');
}
if ($this->hidden) {
@@ -156,7 +156,7 @@ class Question
/**
* Sets a validator for the question.
*
* @param null|callable $validator
* @param callable|null $validator
*
* @return $this
*/
@@ -170,7 +170,7 @@ class Question
/**
* Gets the validator for the question.
*
* @return null|callable
* @return callable|null
*/
public function getValidator()
{
@@ -182,7 +182,7 @@ class Question
*
* Null means an unlimited number of attempts.
*
* @param null|int $attempts
* @param int|null $attempts
*
* @return $this
*
@@ -204,7 +204,7 @@ class Question
*
* Null means an unlimited number of attempts.
*
* @return null|int
* @return int|null
*/
public function getMaxAttempts()
{

View File

@@ -13,8 +13,6 @@ namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\StreamOutput;
/**
* Eases the testing of console applications.
@@ -33,7 +31,6 @@ class ApplicationTester
private $application;
private $input;
private $statusCode;
private $captureStreamsIndependently = false;
public function __construct(Application $application)
{
@@ -69,36 +66,7 @@ class ApplicationTester
putenv('SHELL_INTERACTIVE=1');
}
$this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) {
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
} else {
$this->output = new ConsoleOutput(
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
isset($options['decorated']) ? $options['decorated'] : null
);
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
$errorOutput->setFormatter($this->output->getFormatter());
$errorOutput->setVerbosity($this->output->getVerbosity());
$errorOutput->setDecorated($this->output->isDecorated());
$reflectedOutput = new \ReflectionObject($this->output);
$strErrProperty = $reflectedOutput->getProperty('stderr');
$strErrProperty->setAccessible(true);
$strErrProperty->setValue($this->output, $errorOutput);
$reflectedParent = $reflectedOutput->getParentClass();
$streamProperty = $reflectedParent->getProperty('stream');
$streamProperty->setAccessible(true);
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
}
$this->initOutput($options);
$this->statusCode = $this->application->run($this->input, $this->output);
@@ -106,28 +74,4 @@ class ApplicationTester
return $this->statusCode;
}
/**
* Gets the output written to STDERR by the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string
*/
public function getErrorOutput($normalize = false)
{
if (!$this->captureStreamsIndependently) {
throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
}
rewind($this->output->getErrorOutput()->getStream());
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
if ($normalize) {
$display = str_replace(PHP_EOL, "\n", $display);
}
return $display;
}
}

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
/**
* Eases the testing of console commands.
@@ -39,9 +38,10 @@ class CommandTester
*
* Available execution options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*
* @param array $input An array of command arguments and options
* @param array $options An array of execution options
@@ -68,12 +68,12 @@ class CommandTester
$this->input->setInteractive($options['interactive']);
}
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
$this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
if (!isset($options['decorated'])) {
$options['decorated'] = false;
}
$this->initOutput($options);
return $this->statusCode = $this->command->run($this->input, $this->output);
}
}

View File

@@ -12,19 +12,19 @@
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
/**
* @author Amrouche Hamza <hamza.simperfit@gmail.com>
*
* @internal
*/
trait TesterTrait
{
/** @var StreamOutput */
private $output;
private $inputs = array();
private $captureStreamsIndependently = false;
/**
* Gets the display returned by the last execution of the command or application.
@@ -46,6 +46,30 @@ trait TesterTrait
return $display;
}
/**
* Gets the output written to STDERR by the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string
*/
public function getErrorOutput($normalize = false)
{
if (!$this->captureStreamsIndependently) {
throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
}
rewind($this->output->getErrorOutput()->getStream());
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
if ($normalize) {
$display = str_replace(PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the input instance used by the last execution of the command or application.
*
@@ -79,8 +103,8 @@ trait TesterTrait
/**
* Sets the user inputs.
*
* @param $inputs array An array of strings representing each input
* passed to the command input stream
* @param array $inputs An array of strings representing each input
* passed to the command input stream
*
* @return self
*/
@@ -91,6 +115,49 @@ trait TesterTrait
return $this;
}
/**
* Initializes the output property.
*
* Available options:
*
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*/
private function initOutput(array $options)
{
$this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) {
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
} else {
$this->output = new ConsoleOutput(
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
isset($options['decorated']) ? $options['decorated'] : null
);
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
$errorOutput->setFormatter($this->output->getFormatter());
$errorOutput->setVerbosity($this->output->getVerbosity());
$errorOutput->setDecorated($this->output->isDecorated());
$reflectedOutput = new \ReflectionObject($this->output);
$strErrProperty = $reflectedOutput->getProperty('stderr');
$strErrProperty->setAccessible(true);
$strErrProperty->setValue($this->output, $errorOutput);
$reflectedParent = $reflectedOutput->getParentClass();
$streamProperty = $reflectedParent->getProperty('stream');
$streamProperty->setAccessible(true);
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
}
}
private static function createStream(array $inputs)
{
$stream = fopen('php://memory', 'r+', false);

View File

@@ -824,6 +824,56 @@ class ApplicationTest extends TestCase
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks');
}
public function testRenderAnonymousException()
{
$application = new Application();
$application->setAutoExit(false);
$application->register('foo')->setCode(function () {
throw new class('') extends \InvalidArgumentException {
};
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'foo'), array('decorated' => false));
$this->assertContains('[InvalidArgumentException@anonymous]', $tester->getDisplay(true));
$application = new Application();
$application->setAutoExit(false);
$application->register('foo')->setCode(function () {
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() {
})));
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'foo'), array('decorated' => false));
$this->assertContains('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
}
public function testRenderExceptionStackTraceContainsRootException()
{
$application = new Application();
$application->setAutoExit(false);
$application->register('foo')->setCode(function () {
throw new class('') extends \InvalidArgumentException {
};
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'foo'), array('decorated' => false));
$this->assertContains('[InvalidArgumentException@anonymous]', $tester->getDisplay(true));
$application = new Application();
$application->setAutoExit(false);
$application->register('foo')->setCode(function () {
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() {
})));
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'foo'), array('decorated' => false));
$this->assertContains('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
}
public function testRun()
{
$application = new Application();
@@ -960,6 +1010,30 @@ class ApplicationTest extends TestCase
$this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception');
}
public function testRunDispatchesIntegerExitCode()
{
$passedRightValue = false;
// We can assume here that some other test asserts that the event is dispatched at all
$dispatcher = new EventDispatcher();
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use (&$passedRightValue) {
$passedRightValue = (4 === $event->getExitCode());
});
$application = new Application();
$application->setDispatcher($dispatcher);
$application->setAutoExit(false);
$application->register('test')->setCode(function (InputInterface $input, OutputInterface $output) {
throw new \Exception('', 4);
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'test'));
$this->assertTrue($passedRightValue, '-> exit code 4 was passed in the console.terminate event');
}
public function testRunReturnsExitCodeOneForExceptionCodeZero()
{
$exception = new \Exception('', 0);
@@ -975,6 +1049,30 @@ class ApplicationTest extends TestCase
$this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is 0');
}
public function testRunDispatchesExitCodeOneForExceptionCodeZero()
{
$passedRightValue = false;
// We can assume here that some other test asserts that the event is dispatched at all
$dispatcher = new EventDispatcher();
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use (&$passedRightValue) {
$passedRightValue = (1 === $event->getExitCode());
});
$application = new Application();
$application->setDispatcher($dispatcher);
$application->setAutoExit(false);
$application->register('test')->setCode(function (InputInterface $input, OutputInterface $output) {
throw new \Exception();
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'test'));
$this->assertTrue($passedRightValue, '-> exit code 1 was passed in the console.terminate event');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage An option with shortcut "e" already exists.

View File

@@ -1,6 +1,5 @@
<?php
use Symfony\Component\Console\Command\Command;
class Foo6Command extends Command

View File

@@ -322,6 +322,25 @@ more text
EOF
));
}
public function testFormatAndWrap()
{
$formatter = new OutputFormatter(true);
$this->assertSame("fo\no\e[37;41mb\e[39;49m\n\e[37;41mar\e[39;49m\nba\nz", $formatter->formatAndWrap('foo<error>bar</error> baz', 2));
$this->assertSame("pr\ne \e[37;41m\e[39;49m\n\e[37;41mfo\e[39;49m\n\e[37;41mo \e[39;49m\n\e[37;41mba\e[39;49m\n\e[37;41mr \e[39;49m\n\e[37;41mba\e[39;49m\n\e[37;41mz\e[39;49m \npo\nst", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 2));
$this->assertSame("pre\e[37;41m\e[39;49m\n\e[37;41mfoo\e[39;49m\n\e[37;41mbar\e[39;49m\n\e[37;41mbaz\e[39;49m\npos\nt", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 3));
$this->assertSame("pre \e[37;41m\e[39;49m\n\e[37;41mfoo \e[39;49m\n\e[37;41mbar \e[39;49m\n\e[37;41mbaz\e[39;49m \npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 4));
$this->assertSame("pre \e[37;41mf\e[39;49m\n\e[37;41moo ba\e[39;49m\n\e[37;41mr baz\e[39;49m\npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 5));
$formatter = new OutputFormatter();
$this->assertSame("fo\nob\nar\nba\nz", $formatter->formatAndWrap('foo<error>bar</error> baz', 2));
$this->assertSame("pr\ne \nfo\no \nba\nr \nba\nz \npo\nst", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 2));
$this->assertSame("pre\nfoo\nbar\nbaz\npos\nt", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 3));
$this->assertSame("pre \nfoo \nbar \nbaz \npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 4));
$this->assertSame("pre f\noo ba\nr baz\npost", $formatter->formatAndWrap('pre <error>foo bar baz</error> post', 5));
}
}
class TableCell

View File

@@ -25,6 +25,10 @@ class ProcessHelperTest extends TestCase
*/
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error)
{
if (\is_string($cmd)) {
$cmd = \method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd);
}
$helper = new ProcessHelper();
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
$output = $this->getOutputStream($verbosity);
@@ -41,7 +45,7 @@ class ProcessHelperTest extends TestCase
$executed = false;
$callback = function () use (&$executed) { $executed = true; };
$helper->run($output, 'php -r "echo 42;"', null, $callback);
$helper->run($output, array('php', '-r', 'echo 42;'), null, $callback);
$this->assertTrue($executed);
}
@@ -81,12 +85,21 @@ EOT;
OUT out message
RES 252 Command did not run successfully
EOT;
$PHP = '\\' === \DIRECTORY_SEPARATOR ? '"!PHP!"' : '"$PHP"';
$successOutputPhp = <<<EOT
RUN php -r $PHP
OUT 42
RES Command ran successfully
EOT;
$errorMessage = 'An error occurred';
$args = new Process(array('php', '-r', 'echo 42;'));
$args = $args->getCommandLine();
$successOutputProcessDebug = str_replace("'php' '-r' 'echo 42;'", $args, $successOutputProcessDebug);
$fromShellCommandline = \method_exists(Process::class, 'fromShellCommandline') ? array(Process::class, 'fromShellCommandline') : function ($cmd) { return new Process($cmd); };
return array(
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null),
@@ -100,7 +113,9 @@ EOT;
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage),
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage),
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputDebug, $fromShellCommandline('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputProcessDebug, array(new Process(array('php', '-r', 'echo 42;'))), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputPhp, array($fromShellCommandline('php -r '.$PHP), 'PHP' => 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
);
}

View File

@@ -89,6 +89,63 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, true), $this->createOutputInterface(), $question));
}
public function testAskChoiceNonInteractive()
{
$questionHelper = new QuestionHelper();
$helperSet = new HelperSet(array(new FormatterHelper()));
$questionHelper->setHelperSet($helperSet);
$inputStream = $this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n");
$heroes = array('Superman', 'Batman', 'Spiderman');
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0');
$this->assertSame('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, 'Batman');
$this->assertSame('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
$this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0');
$question->setValidator(null);
$this->assertSame('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
try {
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
$questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question);
} catch (\InvalidArgumentException $e) {
$this->assertSame('Value "" is invalid', $e->getMessage());
}
$question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, 1');
$question->setMultiselect(true);
$this->assertSame(array('Superman', 'Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, 1');
$question->setMultiselect(true);
$question->setValidator(null);
$this->assertSame(array('Superman', 'Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, Batman');
$question->setMultiselect(true);
$this->assertSame(array('Superman', 'Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, null);
$question->setMultiselect(true);
$this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
try {
$question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '');
$question->setMultiselect(true);
$questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question);
} catch (\InvalidArgumentException $e) {
$this->assertSame('Value "" is invalid', $e->getMessage());
}
}
public function testAsk()
{
$dialog = new QuestionHelper();

View File

@@ -74,6 +74,18 @@ class SymfonyQuestionHelperTest extends AbstractQuestionHelperTest
$this->assertOutputContains('What is your favorite superhero? [Superman, Batman]', $output);
}
public function testAskChoiceWithChoiceValueAsDefault()
{
$questionHelper = new SymfonyQuestionHelper();
$helperSet = new HelperSet(array(new FormatterHelper()));
$questionHelper->setHelperSet($helperSet);
$question = new ChoiceQuestion('What is your favorite superhero?', array('Superman', 'Batman', 'Spiderman'), 'Batman');
$question->setMaxAttempts(1);
$this->assertSame('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($this->getInputStream("Batman\n")), $output = $this->createOutputInterface(), $question));
$this->assertOutputContains('What is your favorite superhero? [Batman]', $output);
}
public function testAskReturnsNullIfValidatorAllowsIt()
{
$questionHelper = new SymfonyQuestionHelper();

View File

@@ -783,7 +783,7 @@ TABLE;
$table->render();
}
public function testColumnWith()
public function testColumnWidth()
{
$table = new Table($output = $this->getOutputStream());
$table
@@ -815,7 +815,7 @@ TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testColumnWiths()
public function testColumnWidths()
{
$table = new Table($output = $this->getOutputStream());
$table
@@ -974,6 +974,145 @@ TABLE;
Table::getStyleDefinition('absent');
}
/**
* @dataProvider renderSetTitle
*/
public function testSetTitle($headerTitle, $footerTitle, $style, $expected)
{
(new Table($output = $this->getOutputStream()))
->setHeaderTitle($headerTitle)
->setFooterTitle($footerTitle)
->setHeaders(array('ISBN', 'Title', 'Author'))
->setRows(array(
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
))
->setStyle($style)
->render()
;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function renderSetTitle()
{
return array(
array(
'Books',
'Page 1/2',
'default',
<<<'TABLE'
+---------------+----------- Books --------+------------------+
| ISBN | Title | Author |
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------- Page 1/2 -------+------------------+
TABLE
),
array(
'Books',
'Page 1/2',
'box',
<<<'TABLE'
┌───────────────┬─────────── Books ────────┬──────────────────┐
│ ISBN │ Title │ Author │
├───────────────┼──────────────────────────┼──────────────────┤
│ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
│ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
│ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴───────── Page 1/2 ───────┴──────────────────┘
TABLE
),
array(
'Boooooooooooooooooooooooooooooooooooooooooooooooooooooooks',
'Page 1/999999999999999999999999999999999999999999999999999',
'default',
<<<'TABLE'
+- Booooooooooooooooooooooooooooooooooooooooooooooooooooo... -+
| ISBN | Title | Author |
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+- Page 1/99999999999999999999999999999999999999999999999... -+
TABLE
),
);
}
public function testColumnMaxWidths()
{
$table = new Table($output = $this->getOutputStream());
$table
->setRows(array(
array('Divine Comedy', 'A Tale of Two Cities', 'The Lord of the Rings', 'And Then There Were None'),
))
->setColumnMaxWidth(1, 5)
->setColumnMaxWidth(2, 10)
->setColumnMaxWidth(3, 15);
$table->render();
$expected =
<<<TABLE
+---------------+-------+------------+-----------------+
| Divine Comedy | A Tal | The Lord o | And Then There |
| | e of | f the Ring | Were None |
| | Two C | s | |
| | ities | | |
+---------------+-------+------------+-----------------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testBoxedStyleWithColspan()
{
$boxed = new TableStyle();
$boxed
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│')
->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
;
$table = new Table($output = $this->getOutputStream());
$table->setStyle($boxed);
$table
->setHeaders(array('ISBN', 'Title', 'Author'))
->setRows(array(
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
new TableSeparator(),
array(new TableCell('This value spans 3 columns.', array('colspan' => 3))),
))
;
$table->render();
$expected =
<<<TABLE
┌───────────────┬───────────────┬─────────────────┐
│ ISBN │ Title │ Author │
├───────────────┼───────────────┼─────────────────┤
│ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
├───────────────┼───────────────┼─────────────────┤
│ This value spans 3 columns. │
└───────────────┴───────────────┴─────────────────┘
TABLE;
$this->assertSame($expected, $this->getOutputContent($output));
}
protected function getOutputStream($decorated = false)
{
return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated);

View File

@@ -246,6 +246,11 @@ class ArgvInputTest extends TestCase
new InputDefinition(array(new InputArgument('number'))),
'The "-1" option does not exist.',
),
array(
array('cli.php', '-fЩ'),
new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))),
'The "-Щ" option does not exist.',
),
);
}

View File

@@ -13,9 +13,12 @@ namespace Symfony\Component\Console\Tests\Output;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Question\Question;
class ConsoleSectionOutputTest extends TestCase
{
@@ -23,7 +26,7 @@ class ConsoleSectionOutputTest extends TestCase
protected function setUp()
{
$this->stream = fopen('php://memory', 'r+', false);
$this->stream = fopen('php://memory', 'r+b', false);
}
protected function tearDown()
@@ -137,4 +140,24 @@ class ConsoleSectionOutputTest extends TestCase
rewind($output->getStream());
$this->assertEquals('Foo'.PHP_EOL.'Bar'.PHP_EOL."\x1b[2A\x1b[0JBar".PHP_EOL."\x1b[1A\x1b[0JBaz".PHP_EOL.'Bar'.PHP_EOL."\x1b[1A\x1b[0JFoobar".PHP_EOL, stream_get_contents($output->getStream()));
}
public function testClearSectionContainingQuestion()
{
$inputStream = fopen('php://memory', 'r+b', false);
fwrite($inputStream, "Batman & Robin\n");
rewind($inputStream);
$input = $this->getMockBuilder(StreamableInputInterface::class)->getMock();
$input->expects($this->once())->method('isInteractive')->willReturn(true);
$input->expects($this->once())->method('getStream')->willReturn($inputStream);
$sections = array();
$output = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
(new QuestionHelper())->ask($input, $output, new Question('What\'s your favorite super hero?'));
$output->clear();
rewind($output->getStream());
$this->assertSame('What\'s your favorite super hero?'.PHP_EOL."\x1b[2A\x1b[0J", stream_get_contents($output->getStream()));
}
}

View File

@@ -90,4 +90,24 @@ class ApplicationTesterTest extends TestCase
{
$this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code');
}
public function testErrorOutput()
{
$application = new Application();
$application->setAutoExit(false);
$application->register('foo')
->addArgument('foo')
->setCode(function ($input, $output) {
$output->getErrorOutput()->write('foo');
})
;
$tester = new ApplicationTester($application);
$tester->run(
array('command' => 'foo', 'foo' => 'bar'),
array('capture_stderr_separately' => true)
);
$this->assertSame('foo', $tester->getErrorOutput());
}
}

View File

@@ -160,4 +160,23 @@ class CommandTesterTest extends TestCase
$this->assertEquals(0, $tester->getStatusCode());
}
public function testErrorOutput()
{
$command = new Command('foo');
$command->addArgument('command');
$command->addArgument('foo');
$command->setCode(function ($input, $output) {
$output->getErrorOutput()->write('foo');
}
);
$tester = new CommandTester($command);
$tester->execute(
array('foo' => 'bar'),
array('capture_stderr_separately' => true)
);
$this->assertSame('foo', $tester->getErrorOutput());
}
}

View File

@@ -17,6 +17,7 @@
],
"require": {
"php": "^7.1.3",
"symfony/contracts": "^1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
@@ -46,7 +47,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
"dev-master": "4.2-dev"
}
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"