-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[WIP] [Console] Pretty word wrapping #30590
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
Conversation
@ro0NL Let's discuss! :) I fully refactored the code, I made a helper from it, and integrated into |
* | ||
* @return string | ||
*/ | ||
public function wordwrap(string $message, int $width, int $cutOption = null): string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is new feature isnt it? To wrap a raw string with tags (unformatted still) ...
Do we really need it? As such it's a BC break, and it's the exact same reason we added a new WrappableOutputFormatterInterface
in 4.2.
New features target master and thus would imply another interface. I think we should avoid that and solve the issue in formatAndWrap()
.
Which takes me to my 2nd point; this PR will parse the tags twice IIUC. My main motivation for "formatAndWrap" was to be able to handle it at once. I think we should patch it instead, relying on a helper if really needed for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about it also. I saw 3 ways:
- I add the CUT option parameter to the
formatAndWrap()
--> this causes BC break - I create a new wrap function that only can wrap --> it doesn't cause BC break
- I leave the CUT option parameter --> it doesn't cause BC break, but it causes inflexible wrapping function
Maybe there is a 4. option: I remove wordwrap()
(maybe the formatAndWrap()
too), and if somebody want to wrap then call like here:
// Without wrapping
$output->getFormatter()->format($text);
// With wrapping
$output->getFormatter()->format(
PrettyWordWrappingHelper::wordwrap($text, 120)
);
It would be clearer, but it supersede your WrappableOutputFormatterInterface
because wrapping and formatting are really and fully separated (and helps to programmers changing it a custom wrapper if they want) I would prefer this.
this PR will parse the tags twice
Yes, it is true. This is a side effect, which I think is necessary. Formatting (styling?) and wrapping can enough complex separately, that is why I think we should handle it separately. It causes more maintenance and extendable code. And this will be used in command line, the performance isn't so important. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
option 5) Your "With wrapping" example to me equals formatter->formatAndWrap($text, 120)
already. That's API in 4.2 we can fix, maybe even with an @internal
WordwrapHelper, or public for 4.3/master.
option 6) deprecate WrappableOutputFormatterInterface
in 4.3 and add the "best" API directly to OutputFormatterInterface using #28902 (which wasnt possible back then)
In general i think the wordwrap helper should be an implementation detail of the output formatter. But we need some consensus first to move forward. Given a lot of work is done here already, perhaps we should finish it :) The bug is real.
this PR will parse the tags twice
Yes, it is true. This is a side effect, which I think is necessary.
You know best :) so far i thought it seemed reasonably possible ... but it's also still flawed currently 😅 I need to look closer at this new approach to see how it differs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... I'm thinking about it ...
@@ -406,7 +407,12 @@ private function createBlock(iterable $messages, string $type = null, string $st | |||
$message = OutputFormatter::escape($message); | |||
} | |||
|
|||
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true))); | |||
$lines = array_merge($lines, explode(PHP_EOL, PrettyWordWrapperHelper::wrap( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically i started my patch by invoking formatAndWrap()
here, which conceptually works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed fixtures look sensible 👍 , but the amount of code introduced is hard to grasp. E.g. do we really need all the CUT_
options (now)?
This seems like a lot of new features combined, which doesnt make them wrong, but i'd prefer smaller PRs personally.
At this point, aren't we better of to rewrite the tag parsing as a whole? I.e. using a (real) lexer.
I like this feature, but I'm concerned about the naming:
|
About CUT options/flags@ro0NL and @javiereguiluz Before I reply for you one by one, I would like to clarify the importance of CUT options. I started this feature because of my existing other program, where I had to print some well formatted and colorized help/description texts, which were ugly with the existing output formatting solution. These are longer texts, with some URLs. public function wordwrap($string, $witdth, $cutLongWords = true, $cutURLs = false, $fillUpEnd = false, ...)
{
// ...
} There are many other functions where there are lots of configuration options, e.g:
If you have long rows (>50-60 chars) you may not need any No cut:
Cut every words (
Cut only the long words, that are longer than
Please look at the documentation too: https://github.com/symfony/symfony-docs/pull/11190/files , there are examples and after that maybe the conception will be clearer. |
I was thinking about it also. I started to do a tokenizer/lexer, but I rejected it fast. The "grammar" is "too" simple. There are words and some simple tags. There aren't tag attributes, special characters, placeholders, etc. Yes, a lexer would be nicer, but we don't forget: this is the console block, for commands, what are used by programmers - in most cases -, and not "non-professional" users. We need a wrapper for "help/description/information" texts and for table cells. That's all. |
*/ | ||
public function format($message) | ||
public function getDefaultWrapCutOption(): int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really needed is it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A protected property that you can only write? It looks a little bit bad concept. A property what you can only read, it could be justified, but a property without reading option, not really.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im skeptical about the setter too :)
setters/getters can live independently IMHO, it's not needed for every property by definition or so. If we dont have reason to add a getter/settter i would avoid it (it introduces extra state).
In this case i'd prefer passing the cut options to formatAndWrap(), i dont see a real reason to control this default behavior.
* | ||
* @see PrettyWordWrapperHelper | ||
*/ | ||
public function setDefaultWrapCutOption(int $defaultWrapCutOption) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same, where do we use it? I tend to prefer having a $cutOptions
as 3rd argument of formatAndWrap()
, which makes it a clear new feature for 4.3
@@ -228,13 +230,72 @@ public function setColumnWidths(array $widths) | |||
* | |||
* @return $this | |||
*/ | |||
public function setColumnMaxWidth(int $columnIndex, int $width): self | |||
public function setColumnMaxWidth(int $columnIndex, int $width, int $cutOption = null): self | |||
{ | |||
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { | |||
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter()))); | |||
} | |||
|
|||
$this->columnMaxWidths[$columnIndex] = $width; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would use [$width, $cutOption]
to make it clear this belongs together.
|
||
return $this; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure we need all this extra API, i would keep it out at first.
Hello @javiereguiluz , thank you for your comment.
OK, it was a good idea, I have changed it :)
There exists a
Default show:
With
The missing characters are spaces, and the background color will "flow to line endings". |
@ro0NL What if there was a // It prints the text in a 8 char length, yellow background box
$output->writeln('<wrap=8,fillup;bg=yellow>Lorem <info>ipsum</info> dolor sit amet.</>'); And I would refactor the text "pattern" with a lexer/tokenizer. So, what I would do:
|
This new feature was born from a bugfix. This feature add a PrettyWordWrapping class that you can use in console, when you want to wrap the text.