[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:
ogizanagi 2015-05-11 20:21:44 +02:00
parent 260702eae8
commit fc598ff6ad
18 changed files with 352 additions and 6 deletions

View File

@ -18,6 +18,7 @@ use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
@ -36,6 +37,7 @@ class SymfonyStyle extends OutputStyle
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
/**
* @param InputInterface $input
@ -45,6 +47,7 @@ class SymfonyStyle extends OutputStyle
{
$this->input = $input;
$this->lineLength = min($this->getTerminalWidth(), self::MAX_LINE_LENGTH);
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
parent::__construct($output);
}
@ -60,6 +63,7 @@ class SymfonyStyle extends OutputStyle
*/
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
{
$this->autoPrependBlock();
$messages = is_array($messages) ? array_values($messages) : array($messages);
$lines = array();
@ -101,7 +105,7 @@ class SymfonyStyle extends OutputStyle
*/
public function title($message)
{
$this->newLine();
$this->autoPrependBlock();
$this->writeln(array(
sprintf('<comment>%s</>', $message),
sprintf('<comment>%s</>', str_repeat('=', strlen($message))),
@ -114,6 +118,7 @@ class SymfonyStyle extends OutputStyle
*/
public function section($message)
{
$this->autoPrependBlock();
$this->writeln(array(
sprintf('<comment>%s</>', $message),
sprintf('<comment>%s</>', str_repeat('-', strlen($message))),
@ -126,11 +131,10 @@ class SymfonyStyle extends OutputStyle
*/
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(function ($element) {
return sprintf(' * %s', $element);
},
$elements
);
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements);
$this->newLine();
@ -141,6 +145,8 @@ class SymfonyStyle extends OutputStyle
*/
public function text($message)
{
$this->autoPrependText();
if (!is_array($message)) {
$this->writeln(sprintf(' // %s', $message));
@ -301,17 +307,51 @@ class SymfonyStyle extends OutputStyle
*/
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);
$this->newLine();
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
parent::writeln($messages, $type);
$this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
parent::write($messages, $newline, $type);
$this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
}
/**
* {@inheritdoc}
*/
public function newLine($count = 1)
{
parent::newLine($count);
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* @return ProgressBar
*/
@ -331,4 +371,33 @@ class SymfonyStyle extends OutputStyle
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));
}
}

View File

@ -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');
};

View File

@ -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');
};

View File

@ -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);
};

View File

@ -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');
};

View File

@ -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');
};

View File

@ -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',
));
};

View File

@ -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');
};

View File

@ -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');
};

View File

@ -0,0 +1,3 @@
! [CAUTION] Lorem ipsum dolor sit amet

View File

@ -0,0 +1,9 @@
Title
=====
[WARNING] Lorem ipsum dolor sit amet
Title
=====

View File

@ -0,0 +1,13 @@
[WARNING] Warning
! [CAUTION] Caution
[ERROR] Error
[OK] Success
! [NOTE] Note
X [CUSTOM] Custom block

View File

@ -0,0 +1,7 @@
First title
===========
Second title
============

View File

@ -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
===========

View File

@ -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

View File

@ -0,0 +1,6 @@
* Lorem ipsum dolor sit amet
* consectetur adipiscing elit
[OK] Lorem ipsum dolor sit amet

View File

@ -0,0 +1,5 @@
Title
=====
// Duis aute irure dolor in reprehenderit in voluptate velit esse

View File

@ -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'));
}
}