Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ CHANGELOG
---

* Add method `__toString()` to `InputInterface`
* Added `OutputWrapperInterface` and `OutputWrapper` to allow modifying your
wrapping strategy in `SymfonyStyle` or in other `OutputStyle`. Eg: you can
switch off to wrap URLs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced we need to open for extensibility here. Fight me if you want to keep it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because the behavior you need can depend on your console, your language, and other things. I added a really basic solution here, but people may want different behavior, or they want to split the long URL-s on their way. Eg: originally I just wanted to solve the text wrapping but it turned out, URL wrapping is another issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Nicolas, we should not make it extensible.

* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead

6.0
Expand Down
7 changes: 4 additions & 3 deletions src/Symfony/Component/Console/Formatter/OutputFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Helper\OutputWrapperInterface;

/**
* Formatter class for console output.
Expand Down Expand Up @@ -140,8 +141,8 @@ public function formatAndWrap(?string $message, int $width)
{
$offset = 0;
$output = '';
$openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
$closeTagRegex = '[a-z][^<>]*+';
$openTagRegex = OutputWrapperInterface::TAG_OPEN_REGEX_SEGMENT;
$closeTagRegex = OutputWrapperInterface::TAG_CLOSE_REGEX_SEGMENT;
$currentLineLength = 0;
preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
Expand All @@ -157,7 +158,7 @@ public function formatAndWrap(?string $message, int $width)
$offset = $pos + \strlen($text);

// opening tag?
if ($open = '/' != $text[1]) {
if ($open = ('/' !== $text[1])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the brackets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it because PHPStorm recommended this change, and I agree, it is more readable what is happening, even if you are totally right, the original version worked. If you want, I can 'undo' this, up to you. But now you know why I did it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revert as it's not needed.

$tag = $matches[1][$i][0];
} else {
$tag = $matches[3][$i][0] ?? '';
Expand Down
83 changes: 83 additions & 0 deletions src/Symfony/Component/Console/Helper/OutputWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?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\Helper;

/**
* Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
* answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
*
* (?:
* # -- Words/Characters
* ( # (1 start)
* (?> # Atomic Group - Match words with valid breaks
* .{1,16} # 1-N characters
* # Followed by one of 4 prioritized, non-linebreak whitespace
* (?: # break types:
* (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
* [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
* | (?= \r? \n ) # 2. - Ahead a linebreak
* | $ # 3. - EOS
* | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
* )
* ) # End atomic group
* |
* .{1,16} # No valid word breaks, just break on the N'th character
* ) # (1 end)
* (?: \r? \n )? # Optional linebreak after Words/Characters
* |
* # -- Or, Linebreak
* (?: \r? \n | $ ) # Stand alone linebreak or at EOS
* )
*
* @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
*
* @see https://stackoverflow.com/a/20434776/1476819
*/
final class OutputWrapper implements OutputWrapperInterface
{
private const URL_PATTERN = 'https?://\S+';

private bool $allowCutUrls = false;

public function isAllowCutUrls(): bool
{
return $this->allowCutUrls;
}

public function setAllowCutUrls(bool $allowCutUrls)
{
$this->allowCutUrls = $allowCutUrls;

return $this;
}

public function wrap(string $text, int $width, string $break = "\n"): string
{
if (!$width) {
return $text;
}

$tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', OutputWrapperInterface::TAG_OPEN_REGEX_SEGMENT, OutputWrapperInterface::TAG_CLOSE_REGEX_SEGMENT);
$limitPattern = "{1,$width}";
$patternBlocks = [$tagPattern];
if (!$this->allowCutUrls) {
$patternBlocks[] = self::URL_PATTERN;
}
$patternBlocks[] = '.';
$blocks = implode('|', $patternBlocks);
$rowPattern = "(?:$blocks)$limitPattern";
$pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
$output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);

return str_replace(' '.$break, $break, $output);
}
}
23 changes: 23 additions & 0 deletions src/Symfony/Component/Console/Helper/OutputWrapperInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?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\Helper;

interface OutputWrapperInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this interface.

{
public const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
public const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';

/**
* @param positive-int|0 $width
*/
public function wrap(string $text, int $width, string $break = "\n"): string;
}
34 changes: 26 additions & 8 deletions src/Symfony/Component/Console/Style/SymfonyStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\OutputWrapper;
use Symfony\Component\Console\Helper\OutputWrapperInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
Expand Down Expand Up @@ -44,18 +46,32 @@ class SymfonyStyle extends OutputStyle
private ProgressBar $progressBar;
private int $lineLength;
private TrimmedBufferOutput $bufferedOutput;
private OutputWrapperInterface $outputWrapper;

public function __construct(InputInterface $input, OutputInterface $output)
public function __construct(InputInterface $input, OutputInterface $output, OutputWrapperInterface $outputWrapper = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this argument.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the argument and the setters are removed, the property OutputWrapper::$allowCutUrls cannot be updated and becomes useless.

{
$this->input = $input;
$this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
$this->outputWrapper = $outputWrapper ?: new OutputWrapper();

parent::__construct($this->output = $output);
}

public function getOutputWrapper(): OutputWrapperInterface
{
return $this->outputWrapper;
}

public function setOutputWrapper(OutputWrapperInterface $outputWrapper)
{
$this->outputWrapper = $outputWrapper;

return $this;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also remove these gettter/setter methods.


/**
* Formats a message as a block of text.
*/
Expand Down Expand Up @@ -453,7 +469,7 @@ private function createBlock(iterable $messages, string $type = null, string $st

if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$indentLength = Helper::width($type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look directly related to your change no? Asking this in case as it feels like there is a missing test for it and I don't know if your new tests covers it or not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$lineIndentation = str_repeat(' ', $indentLength);
}

Expand All @@ -463,12 +479,14 @@ private function createBlock(iterable $messages, string $type = null, string $st
$message = OutputFormatter::escape($message);
}

$decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message));
$messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength);
$messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true));
foreach ($messageLines as $messageLine) {
$lines[] = $messageLine;
}
$lines = array_merge(
$lines,
explode(\PHP_EOL, $this->outputWrapper->wrap(
$message,
$this->lineLength - $prefixLength - $indentLength,
\PHP_EOL
))
);

if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
$output->setDecorated(true);
$output = new SymfonyStyle($input, $output);
$output->comment(
'Lorem ipsum dolor sit <comment>amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</comment> Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
'Árvíztűrőtükörfúrógép 🎼 Lorem ipsum dolor sit <comment>💕 amet, consectetur adipisicing elit, sed do eiusmod tempor incididu labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</comment> Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

// ensure that nested tags have no effect on the color of the '//' prefix
return function (InputInterface $input, OutputInterface $output) {
$output->setDecorated(true);
$output = new SymfonyStyle($input, $output);
$output->block(
'Árvíztűrőtükörfúrógép Lorem ipsum dolor sit <comment>amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</comment> Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum',
'★', // UTF-8 star!
null,
'<fg=default;bg=default> ║ </>', // UTF-8 double line!
false,
false
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

 // Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore 
 // magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 
 // consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 // pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
 // est laborum
 // Árvíztűrőtükörfúrógép 🎼 Lorem ipsum dolor sit 💕 amet, consectetur adipisicing elit, sed do eiusmod tempor incididu
 // labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
 // ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 // pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
 // laborum

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

 ║ [★] Árvíztűrőtükörfúrógép Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt 
 ║  ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut 
 ║  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 
 ║  fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
 ║  anim id est laborum

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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\Tests\Helper;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Helper\OutputWrapper;

class OutputWrapperTest extends TestCase
{
/**
* @dataProvider textProvider
*/
public function testBasicWrap(string $text, int $width, ?bool $allowCutUrls, string $expected)
{
$wrapper = new OutputWrapper();
if (\is_bool($allowCutUrls)) {
$wrapper->setAllowCutUrls($allowCutUrls);
}
$result = $wrapper->wrap($text, $width);
$this->assertEquals($expected, $result);
}

public static function textProvider(): iterable
{
$baseTextWithUtf8AndUrl = 'Árvíztűrőtükörfúrógép https://github.com/symfony/symfony Lorem ipsum <comment>dolor</comment> sit amet, consectetur adipiscing elit. Praesent vestibulum nulla quis urna maximus porttitor. Donec ullamcorper risus at <error>libero ornare</error> efficitur.';

yield 'Default URL cut' => [
$baseTextWithUtf8AndUrl,
20,
null,
<<<'EOS'
Árvíztűrőtükörfúrógé
p https://github.com/symfony/symfony Lorem ipsum
<comment>dolor</comment> sit amet,
consectetur
adipiscing elit.
Praesent vestibulum
nulla quis urna
maximus porttitor.
Donec ullamcorper
risus at <error>libero
ornare</error> efficitur.
EOS,
];

yield 'Allow URL cut' => [
$baseTextWithUtf8AndUrl,
20,
true,
<<<'EOS'
Árvíztűrőtükörfúrógé
p
https://github.com/s
ymfony/symfony Lorem
ipsum <comment>dolor</comment> sit
amet, consectetur
adipiscing elit.
Praesent vestibulum
nulla quis urna
maximus porttitor.
Donec ullamcorper
risus at <error>libero
ornare</error> efficitur.
EOS,
];
}
}