Skip to content

[WIP] [Console] Refactored console formatting #31131

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
wants to merge 0 commits into from

Conversation

fchris82
Copy link
Contributor

Q A
Branch? master
Bug fix? yes
New feature? yes
BC breaks? yes
Deprecations? yes
Tests pass? yes
Fixed tickets #30519, #30920
License MIT
Doc PR TODO

Antecedent:

I made a full refactoring. The original output formatting isn't elegant and hard to extend. I made a lexer with tokens and visitors. This solution is based on Design Patterns: Elements of Reusable Object-Oriented Software book, from Erich Gamma, Richard Helm, Ralph Johnson, John M Vlissides (Chapter 2)

TODO:

  • Extend Upgrade.md
  • Create Doc PR
  • Discussion

@fchris82
Copy link
Contributor Author

@ro0NL Hello again :) I'm waiting for your observations. Yes, it became huge. :-/

@@ -6,6 +6,13 @@ CHANGELOG

* added support for hyperlinks
* added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating
* refactored console output formatter: add lexer and tokens. Important changes:
* added `<wrap>` and `<nowrap>` formatter tags to console
* [BC BREAK] earlier the unhandled/unknown tags will be shown, but now every tag will disappear after formatting. If you want to show a "tag", you have to escape it with `\` sign: `\<visibleTag>...\</visibleTag>`. You can use the `\Symfony\Component\Console\Formatter\OutputFormatter::escape()` function as well.
Copy link
Member

Choose a reason for hiding this comment

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

BC breaks are a no-go in a minor version. This must trigger a deprecation instead to provide a migration path.

* refactored console output formatter: add lexer and tokens. Important changes:
* added `<wrap>` and `<nowrap>` formatter tags to console
* [BC BREAK] earlier the unhandled/unknown tags will be shown, but now every tag will disappear after formatting. If you want to show a "tag", you have to escape it with `\` sign: `\<visibleTag>...\</visibleTag>`. You can use the `\Symfony\Component\Console\Formatter\OutputFormatter::escape()` function as well.
* [BC BREAK] `\Symfony\Component\Console\Formatter\OutputFormatterStyleInterface` was changed: `apply()` function was removed, and there were set 2 new methods: `start()`, `close()`
Copy link
Member

Choose a reason for hiding this comment

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

no-go. This must use deprecations, and adding new methods in an interface is also a no-go (use a separate interface maybe ?)

* added `<wrap>` and `<nowrap>` formatter tags to console
* [BC BREAK] earlier the unhandled/unknown tags will be shown, but now every tag will disappear after formatting. If you want to show a "tag", you have to escape it with `\` sign: `\<visibleTag>...\</visibleTag>`. You can use the `\Symfony\Component\Console\Formatter\OutputFormatter::escape()` function as well.
* [BC BREAK] `\Symfony\Component\Console\Formatter\OutputFormatterStyleInterface` was changed: `apply()` function was removed, and there were set 2 new methods: `start()`, `close()`
* [BC BREAK] `\Symfony\Component\Console\Formatter\OutputFormatter` is no longer instance of `WrappableOutputFormatterInterface`
Copy link
Member

Choose a reason for hiding this comment

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

that BC break is also a no-go.

class Lexer implements LexerInterface
{
/** @var FullTextToken */
protected $fullTextToken;
Copy link
Member

Choose a reason for hiding this comment

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

everything must use private visibility, not protected one, unless we explicitly decide to make inheritance the supported extension point (hint: such decision is always the last choice).

*
* @param string $text
*
* @return \IteratorAggregate
Copy link
Member

Choose a reason for hiding this comment

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

should return \Traversable or iterable. There is no reason to enforce that \IteratorAggregate is used to implementator the iterator.

{
$this->styles[] = $style;
$this->styles[$depth] = $style;
Copy link
Member

Choose a reason for hiding this comment

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

this is not pushing anything anymore. It replaces an element of the stack

* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Formatter\Tokens;
Copy link
Member

Choose a reason for hiding this comment

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

our namespaces are singular

@@ -15,6 +15,10 @@
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
Copy link
Member

Choose a reason for hiding this comment

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

We don't put @author phpdoc on test files.

$style = new OutputFormatterStyle('blue', 'white');
$formatter->setStyle('test', $style);
Copy link
Member

Choose a reason for hiding this comment

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

this test should be kept unchanged. If you cannot, it means there is a BC break. And that's not something we want

@@ -318,26 +323,13 @@ public function testContentWithLineBreaks()
));
}

public function testFormatAndWrap()
Copy link
Member

Choose a reason for hiding this comment

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

this test must be kept.

@stof
Copy link
Member

stof commented Apr 16, 2019

Can we also get a performance comparison of the old and new system ?

@fchris82
Copy link
Contributor Author

Can we also get a performance comparison of the old and new system ?

I will make one. I think the new is worse, but I thought this is the console, we don't want to format large texts/outputs, and the time isn't so important on the console. I inserted a limitation here: https://github.com/symfony/symfony/pull/31131/files#diff-a42222f449d2d525a1100ed2b995e429R43 on the bases of this: http://piotrpasich.com/spl-iterators-against-the-performance/ (first comment: http://piotrpasich.com/spl-iterators-against-the-performance/#comment-1818526630 )

@fchris82
Copy link
Contributor Author

fchris82 commented Apr 16, 2019

@stof I wrote a benchmark: https://gist.github.com/fchris82/e09e4d9723e4b0e73a768ab53860d321 and I ran it:

1000x original
--------------
Short           0.03s
Short with tags 0.29s
Simple          0.03s
With tags       1.56s

100x new
--------
Short           1.02s
Short with tags 1.24s
Simple          44.41s
With tags       47.42s

Yes, there are big differences. Maybe should I keep the original Formatter and create a new one for wrapping and other actions?

@nicolas-grekas nicolas-grekas added this to the next milestone Apr 17, 2019
@fchris82
Copy link
Contributor Author

@stof Hello, I made a twice faster solution with plain array token container: fchris82@d4fc1d2

$tokens = [
    [Lexer::TYPE_WORD, 'Lorem'],
    [Lexer::TYPE_SEPARATOR, ' '],
    [Lexer::TYPE_WORD, 'ipsum'],
    // ...
    new FullTokenTag('<comment>'),
    // ...
];

This solution is faster, but some things are lost in this case, eg: type hinting (there are strings and Token objects mixed in "one array") What do you prefer?

My opinion: I kept the original OutputFormatter also, so if somebody want to format anything fast (eg: large text and wrapping insn't important), original can be used for them. In most of case we use short texts on console, "twice time" doesn't matter.

@stof
Copy link
Member

stof commented Apr 18, 2019

why does your benchmark result have 1000x original and 100x new ? We need comparable data. If I understand things properly, the old code can format 1000 messages in 30ms, meaning 30µs per message, while the new code formats 100 messages in 1s, meaning 10000µs per message. That's not twice slower. That's 300 times slower.

@fchris82
Copy link
Contributor Author

fchris82 commented Apr 19, 2019

@stof You misunderstood my last comment. There are more problems with the word wrapping in SymfonyStyle now:

  1. tag wrapping
  2. UTF-8 chars
  3. wrapping URL-s

There are 3 different "solution" now:

Yes, this solution is much slower, but works, it wraps the text well, easy to extend, and my question is: does this speed matter on console? Is there any different than the full text is shown 0.03 s instead of 0.0001 s on the screen? Important: we are talking about console, we use here shorter texts. (What I used in the performance test file, it is a long lorem ipsum text, we almost never print like this) If you think, that is too slow, I will finish the first version. If you think, this can be acceptable, I will finish this version.

I would like to solve the problem, I don't insist on this version, only I think this is the "best" version, with an reasonable speed "problem".

@fchris82
Copy link
Contributor Author

@stof Please give me a direction, what do you prefer:

  1. fast with double "parsing"
  2. slow but easy to extend

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.

4 participants