Skip to content

[Console] Console output formatter improvement #45318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

fchris82
Copy link
Contributor

@fchris82 fchris82 commented Feb 4, 2022

Q A
Branch? 6.1
Bug fix? yes
New feature? yes
Deprecations? no
Tickets Fix #30920
License MIT
Doc PR symfony/symfony-docs#16476

Current SymfonyStyle can cause some problems:

  • can cut format tags
  • cut/split URLs, the user can't click on them in the terminal

I fixed the problem and added or modified some tests. This is a simple, fast, and working solution with a regular expression that won't wrap in the middle of a formatting tag or a link. The last one is optional, see the doc change.

@carsonbot
Copy link

Hey!

I think @boesing has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

Copy link
Contributor

@theofidry theofidry left a comment

Choose a reason for hiding this comment

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

A giant 👍 for this. Badly needed!


/**
* Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
* answer: https://stackoverflow.com/a/20434776/1476819 from SLN.
Copy link
Contributor

Choose a reason for hiding this comment

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

SLN?

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 started to do this 2 years ago, SLN was the original name of the user557597.


interface OutputWrapperInterface
{
public const TAG_OPEN_REGEX = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
Copy link
Contributor

Choose a reason for hiding this comment

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

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

? I am not sure it's the best terminology either. I'm just a bit concerned that "regex" here feels wrong as, as a user, I would expect a regex to give a valid regex, not a part of it.

*/
class OutputWrapper implements OutputWrapperInterface
{
public const URL_PATTERN = 'https?://\S+';
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be better to make it private.

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

@@ -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 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'
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe a little emoji in the mix? 😄

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. Good to know, this is a Hungarian 'artificial word' that contains all of the special characters in the language. In free translation: 'floodproof-mirror-driller'. :D

ornare</error> efficitur.
EOS;
$result = $wrapper->wrap($text, 20);
$this->assertEquals($baseExpected, $result);
Copy link
Contributor

@theofidry theofidry Feb 5, 2022

Choose a reason for hiding this comment

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

might I suggest a provider pattern instead?

/**
 * @dataProvider textProvider
 */
public function testBasicWrap(string $text, int $width, bool $allowCutUrls, string $expected): void {
  $wrapper = new OutputWrapper();
  $wrapper->setAllowCutUrls($allowCutUrls);

  $actual = $wrapper->wrap($text, $width);

  self::assertSame($expected, $actual);
}

public static function textProvider(): iterable {

  yield 'title' => [...];
}

@fchris82 fchris82 force-pushed the console-output-formatter-improvement branch from fc32637 to 525e092 Compare February 12, 2022 20:32
@fchris82 fchris82 force-pushed the console-output-formatter-improvement branch from 525e092 to 8e06868 Compare February 12, 2022 20:33
@fchris82
Copy link
Contributor Author

@theofidry Hi, could you review and close the fixed issues?

@theofidry
Copy link
Contributor

For some reasons I cannot mark them as resolved. LGTM though 👍

Co-authored-by: Théo FIDRY <theo.fidry@gmail.com>
@fchris82
Copy link
Contributor Author

For some reasons I cannot mark them as resolved. LGTM though +1

None of them? 🤔

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

Here are some random notes :)

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

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


public function wrap(string $text, int $width, string $break = "\n"): string
{
if (0 === $width) {
Copy link
Member

Choose a reason for hiding this comment

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

if (!$width) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@fabpot fabpot modified the milestones: 6.1, 6.2 May 20, 2022
@@ -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 agree with Nicolas, we should not make it extensible.

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

Let's revert as it's not needed.


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

@GromNaN
Copy link
Member

GromNaN commented Jul 26, 2022

Hello @fchris82, I created a new PR to rebase your work and apply reviews.
Feel free to review #47062

@fabpot
Copy link
Member

fabpot commented Jul 26, 2022

Closing in favor of #47062 then.

@fabpot fabpot closed this Jul 26, 2022
fabpot added a commit that referenced this pull request Jul 26, 2022
… (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
javiereguiluz added a commit to javiereguiluz/symfony-docs that referenced this pull request Sep 22, 2022
…utput wrapper (fchris82)

This PR was submitted for the 6.1 branch but it was merged into the 6.2 branch instead.

Discussion
----------

[Console] Add note about URL cutting of default output wrapper

Add note about the new output wrapper function. Related MR: symfony/symfony#45318

Commits
-------

0bfa8ac [Console] Add note about URL cutting of default output wrapper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Console] Broken tags with long words in a block
7 participants