[Console] SymfonyStyle : fix & automate block gaps.
Autoprepend appropriate blocks by the correct number of blank lines. Handle most of the SymfonyStyle guide line breaks and gaps automatically and fix thing such as double blank lines between titles. Add output tests. Fix askQuestion method, which should not output anything when the input isn't interactive.
This commit is contained in:
parent
260702eae8
commit
fc598ff6ad
@ -18,6 +18,7 @@ use Symfony\Component\Console\Helper\ProgressBar;
|
|||||||
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
@ -36,6 +37,7 @@ class SymfonyStyle extends OutputStyle
|
|||||||
private $questionHelper;
|
private $questionHelper;
|
||||||
private $progressBar;
|
private $progressBar;
|
||||||
private $lineLength;
|
private $lineLength;
|
||||||
|
private $bufferedOutput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param InputInterface $input
|
* @param InputInterface $input
|
||||||
@ -45,6 +47,7 @@ class SymfonyStyle extends OutputStyle
|
|||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
$this->lineLength = min($this->getTerminalWidth(), self::MAX_LINE_LENGTH);
|
$this->lineLength = min($this->getTerminalWidth(), self::MAX_LINE_LENGTH);
|
||||||
|
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
|
||||||
|
|
||||||
parent::__construct($output);
|
parent::__construct($output);
|
||||||
}
|
}
|
||||||
@ -60,6 +63,7 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
|
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
|
||||||
{
|
{
|
||||||
|
$this->autoPrependBlock();
|
||||||
$messages = is_array($messages) ? array_values($messages) : array($messages);
|
$messages = is_array($messages) ? array_values($messages) : array($messages);
|
||||||
$lines = array();
|
$lines = array();
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function title($message)
|
public function title($message)
|
||||||
{
|
{
|
||||||
$this->newLine();
|
$this->autoPrependBlock();
|
||||||
$this->writeln(array(
|
$this->writeln(array(
|
||||||
sprintf('<comment>%s</>', $message),
|
sprintf('<comment>%s</>', $message),
|
||||||
sprintf('<comment>%s</>', str_repeat('=', strlen($message))),
|
sprintf('<comment>%s</>', str_repeat('=', strlen($message))),
|
||||||
@ -114,6 +118,7 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function section($message)
|
public function section($message)
|
||||||
{
|
{
|
||||||
|
$this->autoPrependBlock();
|
||||||
$this->writeln(array(
|
$this->writeln(array(
|
||||||
sprintf('<comment>%s</>', $message),
|
sprintf('<comment>%s</>', $message),
|
||||||
sprintf('<comment>%s</>', str_repeat('-', strlen($message))),
|
sprintf('<comment>%s</>', str_repeat('-', strlen($message))),
|
||||||
@ -126,11 +131,10 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function listing(array $elements)
|
public function listing(array $elements)
|
||||||
{
|
{
|
||||||
|
$this->autoPrependText();
|
||||||
$elements = array_map(function ($element) {
|
$elements = array_map(function ($element) {
|
||||||
return sprintf(' * %s', $element);
|
return sprintf(' * %s', $element);
|
||||||
},
|
}, $elements);
|
||||||
$elements
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->writeln($elements);
|
$this->writeln($elements);
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
@ -141,6 +145,8 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function text($message)
|
public function text($message)
|
||||||
{
|
{
|
||||||
|
$this->autoPrependText();
|
||||||
|
|
||||||
if (!is_array($message)) {
|
if (!is_array($message)) {
|
||||||
$this->writeln(sprintf(' // %s', $message));
|
$this->writeln(sprintf(' // %s', $message));
|
||||||
|
|
||||||
@ -301,17 +307,51 @@ class SymfonyStyle extends OutputStyle
|
|||||||
*/
|
*/
|
||||||
public function askQuestion(Question $question)
|
public function askQuestion(Question $question)
|
||||||
{
|
{
|
||||||
|
if ($this->input->isInteractive()) {
|
||||||
|
$this->autoPrependBlock();
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->questionHelper) {
|
if (!$this->questionHelper) {
|
||||||
$this->questionHelper = new SymfonyQuestionHelper();
|
$this->questionHelper = new SymfonyQuestionHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
$answer = $this->questionHelper->ask($this->input, $this, $question);
|
$answer = $this->questionHelper->ask($this->input, $this, $question);
|
||||||
|
|
||||||
$this->newLine();
|
if ($this->input->isInteractive()) {
|
||||||
|
$this->newLine();
|
||||||
|
$this->bufferedOutput->write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
return $answer;
|
return $answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function writeln($messages, $type = self::OUTPUT_NORMAL)
|
||||||
|
{
|
||||||
|
parent::writeln($messages, $type);
|
||||||
|
$this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
|
||||||
|
{
|
||||||
|
parent::write($messages, $newline, $type);
|
||||||
|
$this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function newLine($count = 1)
|
||||||
|
{
|
||||||
|
parent::newLine($count);
|
||||||
|
$this->bufferedOutput->write(str_repeat("\n", $count));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ProgressBar
|
* @return ProgressBar
|
||||||
*/
|
*/
|
||||||
@ -331,4 +371,33 @@ class SymfonyStyle extends OutputStyle
|
|||||||
|
|
||||||
return $dimensions[0] ?: self::MAX_LINE_LENGTH;
|
return $dimensions[0] ?: self::MAX_LINE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function autoPrependBlock()
|
||||||
|
{
|
||||||
|
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
|
||||||
|
|
||||||
|
if (false === $chars) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has single blank line at start when using block element
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
$output->caution('Lorem ipsum dolor sit amet');
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has single blank line between titles and blocks
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
$output->title('Title');
|
||||||
|
$output->warning('Lorem ipsum dolor sit amet');
|
||||||
|
$output->title('Title');
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has single blank line between blocks
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
$output->warning('Warning');
|
||||||
|
$output->caution('Caution');
|
||||||
|
$output->error('Error');
|
||||||
|
$output->success('Success');
|
||||||
|
$output->note('Note');
|
||||||
|
$output->block('Custom block', 'CUSTOM', 'fg=white;bg=green', 'X ', true);
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has single blank line between two titles
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
$output->title('First title');
|
||||||
|
$output->title('Second title');
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has single blank line after any text and a title
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$output->write('Lorem ipsum dolor sit amet');
|
||||||
|
$output->title('First title');
|
||||||
|
|
||||||
|
$output->writeln('Lorem ipsum dolor sit amet');
|
||||||
|
$output->title('Second title');
|
||||||
|
|
||||||
|
$output->write('Lorem ipsum dolor sit amet');
|
||||||
|
$output->write('');
|
||||||
|
$output->title('Third title');
|
||||||
|
|
||||||
|
//Ensure edge case by appending empty strings to history:
|
||||||
|
$output->write('Lorem ipsum dolor sit amet');
|
||||||
|
$output->write(array('', '', ''));
|
||||||
|
$output->title('Fourth title');
|
||||||
|
|
||||||
|
//Ensure have manual control over number of blank lines:
|
||||||
|
$output->writeln('Lorem ipsum dolor sit amet');
|
||||||
|
$output->writeln(array('', '')); //Should append an extra blank line
|
||||||
|
$output->title('Fifth title');
|
||||||
|
|
||||||
|
$output->writeln('Lorem ipsum dolor sit amet');
|
||||||
|
$output->newLine(2); //Should append an extra blank line
|
||||||
|
$output->title('Fifth title');
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has proper line ending before outputing a text block like with SymfonyStyle::listing() or SymfonyStyle::text()
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$output->writeln('Lorem ipsum dolor sit amet');
|
||||||
|
$output->listing(array(
|
||||||
|
'Lorem ipsum dolor sit amet',
|
||||||
|
'consectetur adipiscing elit',
|
||||||
|
));
|
||||||
|
|
||||||
|
//Even using write:
|
||||||
|
$output->write('Lorem ipsum dolor sit amet');
|
||||||
|
$output->listing(array(
|
||||||
|
'Lorem ipsum dolor sit amet',
|
||||||
|
'consectetur adipiscing elit',
|
||||||
|
));
|
||||||
|
|
||||||
|
$output->write('Lorem ipsum dolor sit amet');
|
||||||
|
$output->text(array(
|
||||||
|
'Lorem ipsum dolor sit amet',
|
||||||
|
'consectetur adipiscing elit',
|
||||||
|
));
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure has proper blank line after text block when using a block like with SymfonyStyle::success
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$output->listing(array(
|
||||||
|
'Lorem ipsum dolor sit amet',
|
||||||
|
'consectetur adipiscing elit',
|
||||||
|
));
|
||||||
|
$output->success('Lorem ipsum dolor sit amet');
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
//Ensure questions do not output anything when input is non-interactive
|
||||||
|
return function (InputInterface $input, OutputInterface $output) {
|
||||||
|
$output = new SymfonyStyle($input, $output);
|
||||||
|
$output->title('Title');
|
||||||
|
$output->askHidden('Hidden question');
|
||||||
|
$output->choice('Choice question with default', array('choice1', 'choice2'), 'choice1');
|
||||||
|
$output->confirm('Confirmation with yes default', true);
|
||||||
|
$output->text('Duis aute irure dolor in reprehenderit in voluptate velit esse');
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
! [CAUTION] Lorem ipsum dolor sit amet
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
Title
|
||||||
|
=====
|
||||||
|
|
||||||
|
[WARNING] Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Title
|
||||||
|
=====
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
[WARNING] Warning
|
||||||
|
|
||||||
|
! [CAUTION] Caution
|
||||||
|
|
||||||
|
[ERROR] Error
|
||||||
|
|
||||||
|
[OK] Success
|
||||||
|
|
||||||
|
! [NOTE] Note
|
||||||
|
|
||||||
|
X [CUSTOM] Custom block
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
First title
|
||||||
|
===========
|
||||||
|
|
||||||
|
Second title
|
||||||
|
============
|
||||||
|
|
@ -0,0 +1,32 @@
|
|||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
First title
|
||||||
|
===========
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Second title
|
||||||
|
============
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Third title
|
||||||
|
===========
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Fourth title
|
||||||
|
============
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
|
||||||
|
Fifth title
|
||||||
|
===========
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
|
||||||
|
Fifth title
|
||||||
|
===========
|
||||||
|
|
@ -0,0 +1,11 @@
|
|||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
* Lorem ipsum dolor sit amet
|
||||||
|
* consectetur adipiscing elit
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
* Lorem ipsum dolor sit amet
|
||||||
|
* consectetur adipiscing elit
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet
|
||||||
|
// Lorem ipsum dolor sit amet
|
||||||
|
// consectetur adipiscing elit
|
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
* Lorem ipsum dolor sit amet
|
||||||
|
* consectetur adipiscing elit
|
||||||
|
|
||||||
|
[OK] Lorem ipsum dolor sit amet
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
Title
|
||||||
|
=====
|
||||||
|
|
||||||
|
// Duis aute irure dolor in reprehenderit in voluptate velit esse
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Console\Tests\Style;
|
||||||
|
|
||||||
|
use PHPUnit_Framework_TestCase;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
|
||||||
|
class SymfonyStyleTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/** @var Command */
|
||||||
|
protected $command;
|
||||||
|
/** @var CommandTester */
|
||||||
|
protected $tester;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->command = new Command('sfstyle');
|
||||||
|
$this->tester = new CommandTester($this->command);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown()
|
||||||
|
{
|
||||||
|
$this->command = null;
|
||||||
|
$this->tester = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider inputCommandToOutputFilesProvider
|
||||||
|
*/
|
||||||
|
public function testOutputs($inputCommandFilepath, $outputFilepath)
|
||||||
|
{
|
||||||
|
$code = require $inputCommandFilepath;
|
||||||
|
$this->command->setCode($code);
|
||||||
|
$this->tester->execute(array(), array('interactive' => false, 'decorated' => false));
|
||||||
|
$this->assertStringEqualsFile($outputFilepath, $this->tester->getDisplay(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function inputCommandToOutputFilesProvider()
|
||||||
|
{
|
||||||
|
$baseDir = __DIR__.'/../Fixtures/Style/SymfonyStyle';
|
||||||
|
|
||||||
|
return array_map(null, glob($baseDir.'/command/command_*.php'), glob($baseDir.'/output/output_*.txt'));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user