8000 feature #47062 [Console] Don't cut Urls wrapped in SymfonyStyle block… · symfony/symfony@b1b77f3 · GitHub
[go: up one dir, main page]

Skip to content

Commit b1b77f3

Browse files
committed
feature #47062 [Console] Don't cut Urls wrapped in SymfonyStyle block (fchris82, GromNaN)
This PR was merged into the 6.2 branch. Discussion ---------- [Console] Don't cut Urls wrapped in SymfonyStyle block | Q | A | ------------- | --- | Branch? | 6.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #30920 | License | MIT | Doc PR | symfony/symfony-docs#16476 Continuation of #45318 by fchris82 Commits ------- b317f06 Remove OutputWrapperInterface 2bc2571 Console output formatter improvement
2 parents 3633475 + b317f06 commit b1b77f3

File tree

9 files changed

+194
-14
lines changed

9 files changed

+194
-14
lines changed

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212

1313
* Add support to display table vertically when calling setVertical()
1414
* Add method `__toString()` to `InputInterface`
15+
* Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`.
1516
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
1617
* Add suggested values for arguments and options in input definition, for input completion
1718
* Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results.

src/Symfony/Component/Console/Formatter/OutputFormatter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public function formatAndWrap(?string $message, int $width)
161161
$offset = $pos + \strlen($text);
162162

163163
// opening tag?
164-
if ($open = '/' != $text[1]) {
164+
if ($open = '/' !== $text[1]) {
165165
$tag = $matches[1][$i][0];
166166
} else {
167167
$tag = $matches[3][$i][0] ?? '';
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Helper;
13+
14+
/**
15+
* Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
16+
* answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
17+
*
18+
* (?:
19+
* # -- Words/Characters
20+
* ( # (1 start)
21+
* (?> # Atomic Group - Match words with valid breaks
22+
* .{1,16} # 1-N characters
23+
* # Followed by one of 4 prioritized, non-linebreak whitespace
24+
* (?: # break types:
25+
* (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
26+
* [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
27+
* | (?= \r? \n ) # 2. - Ahead a linebreak
28+
* | $ # 3. - EOS
29+
* | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
30+
* )
31+
* ) # End atomic group
32+
* |
33+
* .{1,16} # No valid word breaks, just break on the N'th character
34+
* ) # (1 end)
35+
* (?: \r? \n )? # Optional linebreak after Words/Characters
36+
* |
37+
* # -- Or, Linebreak
38+
* (?: \r? \n | $ ) # Stand alone linebreak or at EOS
39+
* )
40+
*
41+
* @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
42+
*
43+
* @see https://stackoverflow.com/a/20434776/1476819
44+
*/
45+
final class OutputWrapper
46+
{
47+
private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
48+
private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
49+
private const URL_PATTERN = 'https?://\S+';
50+
51+
public function __construct(
52+
private bool $allowCutUrls = false
53+
) {
54+
}
55+
56+
public function wrap(string $text, int $width, string $break = "\n"): string
57+
{
58+
if (!$width) {
59+
return $text;
60+
}
61+
62+
$tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
63+
$limitPattern = "{1,$width}";
64+
$patternBlocks = [$tagPattern];
65+
if (!$this->allowCutUrls) {
66+
$patternBlocks[] = self::URL_PATTERN;
67+
}
68+
$patternBlocks[] = '.';
69+
$blocks = implode('|', $patternBlocks);
70+
$rowPattern = "(?:$blocks)$limitPattern";
71+
$pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
72+
$output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
73+
74+
return str_replace(' '.$break, $break, $output);
75+
}
76+
}

src/Symfony/Component/Console/Style/SymfonyStyle.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Console\Exception\RuntimeException;
1616
use Symfony\Component\Console\Formatter\OutputFormatter;
1717
use Symfony\Component\Console\Helper\Helper;
18+
use Symfony\Component\Console\Helper\OutputWrapper;
1819
use Symfony\Component\Console\Helper\ProgressBar;
1920
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
2021
use Symfony\Component\Console\Helper\Table;
@@ -456,22 +457,25 @@ private function createBlock(iterable $messages, string $type = null, string $st
456457

457458
if (null !== $type) {
458459
$type = sprintf('[%s] ', $type);
459-
$indentLength = \strlen($type);
460+
$indentLength = Helper::width($type);
460461
$lineIndentation = str_repeat(' ', $indentLength);
461462
}
462463

463464
// wrap and add newlines for each element
465+
$outputWrapper = new OutputWrapper();
464466
foreach ($messages as $key => $message) {
465467
if ($escape) {
466468
$message = OutputFormatter::escape($message);
467469
}
468470

469-
$decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message));
470-
$messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength);
471-
$messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true));
472-
foreach ($messageLines as $messageLine) {
473-
$lines[] = $messageLine;
474-
}
471+
$lines = array_merge(
472+
$lines,
473+
explode(\PHP_EOL, $outputWrapper->wrap(
474+
$message,
475+
$this->lineLength - $prefixLength - $indentLength,
476+
\PHP_EOL
477+
))
478+
);
475479

476480
if (\count($messages) > 1 && $key < \count($messages) - 1) {
477481
$lines[] = '';

src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_13.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
$output->setDecorated(true);
1010
$output = new SymfonyStyle($input, $output);
1111
$output->comment(
12-
'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'
12+
'Á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'
1313
);
1414
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Input\InputInterface;
4+
use Symfony\Component\Console\Output\OutputInterface;
5+
use Symfony\Component\Console\Style\SymfonyStyle;
6+
7+
// ensure that nested tags have no effect on the color of the '//' prefix
8+
return function (InputInterface $input, OutputInterface $output) {
9+
$output->setDecorated(true);
10+
$output = new SymfonyStyle($input, $output);
11+
$output->block(
12+
'Á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',
13+
'', // UTF-8 star!
14+
null,
15+
'<fg=default;bg=default> ║ </>', // UTF-8 double line!
16+
false,
17+
false
18+
);
19+
};
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
 ║ [★] Árvíztűrőtükörfúrógép Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt 
3+
 ║  ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut 
4+
 ║  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 
5+
 ║  fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
6+
 ║  anim id est laborum
7+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Tests\Helper;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\Helper\OutputWrapper;
16+
17+
class OutputWrapperTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider textProvider
21+
*/
22+
public function testBasicWrap(string $text, int $width, bool $allowCutUrls, string $expected)
23+
{
24+
$wrapper = new OutputWrapper($allowCutUrls);
25+
$result = $wrapper->wrap($text, $width);
26+
$this->assertEquals($expected, $result);
27+
}
28+
29+
public static function textProvider(): iterable
30+
{
31+
$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.';
32+
33+
yield 'Default URL cut' => [
34+
$baseTextWithUtf8AndUrl,
35+
20,
36+
false,
37+
<<<'EOS'
38+
Árvíztűrőtükörfúrógé
39+
p https://github.com/symfony/symfony Lorem ipsum
40+
<comment>dolor</comment> sit amet,
41+
consectetur
42+
adipiscing elit.
43+
Praesent vestibulum
44+
nulla quis urna
45+
maximus porttitor.
46+
Donec ullamcorper
47+
risus at <error>libero
48+
ornare</error> efficitur.
49+
EOS,
50+
];
51+
52+
yield 'Allow URL cut' => [
53+
$baseTextWithUtf8AndUrl,
54+
20,
55+
true,
56+
<<<'EOS'
57+
Árvíztűrőtükörfúrógé
58+
p
59+
https://github.com/s
60+
ymfony/symfony Lorem
61+
ipsum <comment>dolor</comment> sit
62+
amet, consectetur
63+
adipiscing elit.
64+
Praesent vestibulum
65+
nulla quis urna
66+
maximus porttitor.
67+
Donec ullamcorper
68+
risus at <error>libero
69+
ornare</error> efficitur.
70+
EOS,
71+
];
72+
}
73+
}

0 commit comments

Comments
 (0)
0