OutputWrapper.php 2.9 KB

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