--- /dev/null
+<?php
+namespace Consolidation\OutputFormatters\Transformations\Wrap;
+
+use Symfony\Component\Console\Helper\TableStyle;
+
+/**
+ * Calculate column widths for table cells.
+ *
+ * Influenced by Drush and webmozart/console.
+ */
+class CalculateWidths
+{
+ public function __construct()
+ {
+ }
+
+ /**
+ * Given the total amount of available space, and the width of
+ * the columns to place, calculate the optimum column widths to use.
+ */
+ public function calculate($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
+ {
+ // First, check to see if all columns will fit at their full widths.
+ // If so, do no further calculations. (This may be redundant with
+ // the short column width calculation.)
+ if ($dataWidths->totalWidth() <= $availableWidth) {
+ return $dataWidths->enforceMinimums($minimumWidths);
+ }
+
+ // Get the short columns first. If there are none, then distribute all
+ // of the available width among the remaining columns.
+ $shortColWidths = $this->getShortColumns($availableWidth, $dataWidths, $minimumWidths);
+ if ($shortColWidths->isEmpty()) {
+ return $this->distributeLongColumns($availableWidth, $dataWidths, $minimumWidths);
+ }
+
+ // If some short columns were removed, then account for the length
+ // of the removed columns and make a recursive call (since the average
+ // width may be higher now, if the removed columns were shorter in
+ // length than the previous average).
+ $availableWidth -= $shortColWidths->totalWidth();
+ $remainingWidths = $dataWidths->removeColumns($shortColWidths->keys());
+ $remainingColWidths = $this->calculate($availableWidth, $remainingWidths, $minimumWidths);
+
+ return $shortColWidths->combine($remainingColWidths);
+ }
+
+ /**
+ * Calculate the longest cell data from any row of each of the cells.
+ */
+ public function calculateLongestCell($rows)
+ {
+ return $this->calculateColumnWidths(
+ $rows,
+ function ($cell) {
+ return strlen($cell);
+ }
+ );
+ }
+
+ /**
+ * Calculate the longest word and longest line in the provided data.
+ */
+ public function calculateLongestWord($rows)
+ {
+ return $this->calculateColumnWidths(
+ $rows,
+ function ($cell) {
+ return static::longestWordLength($cell);
+ }
+ );
+ }
+
+ protected function calculateColumnWidths($rows, callable $fn)
+ {
+ $widths = [];
+
+ // Examine each row and find the longest line length and longest
+ // word in each column.
+ foreach ($rows as $rowkey => $row) {
+ foreach ($row as $colkey => $cell) {
+ $value = $fn($cell);
+ if ((!isset($widths[$colkey]) || ($widths[$colkey] < $value))) {
+ $widths[$colkey] = $value;
+ }
+ }
+ }
+
+ return new ColumnWidths($widths);
+ }
+
+ /**
+ * Return all of the columns whose longest line length is less than or
+ * equal to the average width.
+ */
+ public function getShortColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
+ {
+ $averageWidth = $dataWidths->averageWidth($availableWidth);
+ $shortColWidths = $dataWidths->findShortColumns($averageWidth);
+ return $shortColWidths->enforceMinimums($minimumWidths);
+ }
+
+ /**
+ * Distribute the remainig space among the columns that were not
+ * included in the list of "short" columns.
+ */
+ public function distributeLongColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
+ {
+ // First distribute the remainder without regard to the minimum widths.
+ $result = $dataWidths->distribute($availableWidth);
+
+ // Find columns that are shorter than their minimum width.
+ $undersized = $result->findUndersizedColumns($minimumWidths);
+
+ // Nothing too small? Great, we're done!
+ if ($undersized->isEmpty()) {
+ return $result;
+ }
+
+ // Take out the columns that are too small and redistribute the rest.
+ $availableWidth -= $undersized->totalWidth();
+ $remaining = $dataWidths->removeColumns($undersized->keys());
+ $distributeRemaining = $this->distributeLongColumns($availableWidth, $remaining, $minimumWidths);
+
+ return $undersized->combine($distributeRemaining);
+ }
+
+ /**
+ * Return the length of the longest word in the string.
+ * @param string $str
+ * @return int
+ */
+ protected static function longestWordLength($str)
+ {
+ $words = preg_split('#[ /-]#', $str);
+ $lengths = array_map(function ($s) {
+ return strlen($s);
+ }, $words);
+ return max($lengths);
+ }
+}