OutputWrapper.php 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  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. /**
  48. * @var bool
  49. */
  50. private $allowCutUrls = false;
  51. /**
  52. * @param bool $allowCutUrls
  53. */
  54. public function __construct($allowCutUrls = false)
  55. {
  56. $this->allowCutUrls = $allowCutUrls;
  57. }
  58. public function wrap(string $text, int $width, string $break = "\n")
  59. {
  60. if (!$width) {
  61. return $text;
  62. }
  63. $tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
  64. $limitPattern = "{1,$width}";
  65. $patternBlocks = [$tagPattern];
  66. if (!$this->allowCutUrls) {
  67. $patternBlocks[] = self::URL_PATTERN;
  68. }
  69. $patternBlocks[] = '.';
  70. $blocks = implode('|', $patternBlocks);
  71. $rowPattern = "(?:$blocks)$limitPattern";
  72. $pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
  73. $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
  74. return str_replace(' '.$break, $break, $output);
  75. }
  76. }