Yaffs site version 1.1
[yaffs-website] / vendor / consolidation / output-formatters / src / Transformations / WordWrapper.php
index 18519d077eaa5006f0331c71b396bbd13b055d16..36f9b88d04110df649c62b99062043d979aae3d0 100644 (file)
@@ -1,12 +1,14 @@
 <?php
 namespace Consolidation\OutputFormatters\Transformations;
 
+use Consolidation\OutputFormatters\Transformations\Wrap\CalculateWidths;
+use Consolidation\OutputFormatters\Transformations\Wrap\ColumnWidths;
 use Symfony\Component\Console\Helper\TableStyle;
 
 class WordWrapper
 {
     protected $width;
-    protected $minimumWidths = [];
+    protected $minimumWidths;
 
     // For now, hardcode these to match what the Symfony Table helper does.
     // Note that these might actually need to be adjusted depending on the
@@ -18,6 +20,7 @@ class WordWrapper
     public function __construct($width)
     {
         $this->width = $width;
+        $this->minimumWidths = new ColumnWidths();
     }
 
     /**
@@ -40,7 +43,15 @@ class WordWrapper
      */
     public function setMinimumWidths($minimumWidths)
     {
-        $this->minimumWidths = $minimumWidths;
+        $this->minimumWidths = new ColumnWidths($minimumWidths);
+    }
+
+    /**
+     * Set the minimum width of just one column
+     */
+    public function minimumWidth($colkey, $width)
+    {
+        $this->minimumWidths->setWidth($colkey, $width);
     }
 
     /**
@@ -50,19 +61,18 @@ class WordWrapper
      */
     public function wrap($rows, $widths = [])
     {
-        // If the width was not set, then disable wordwrap.
-        if (!$this->width) {
+        $auto_widths = $this->calculateWidths($rows, $widths);
+
+        // If no widths were provided, then disable wrapping
+        if ($auto_widths->isEmpty()) {
             return $rows;
         }
 
-        // Calculate the column widths to use based on the content.
-        $auto_widths = $this->columnAutowidth($rows, $widths);
-
         // Do wordwrap on all cells.
         $newrows = array();
         foreach ($rows as $rowkey => $row) {
             foreach ($row as $colkey => $cell) {
-                $newrows[$rowkey][$colkey] = $this->wrapCell($cell, $auto_widths[$colkey]);
+                $newrows[$rowkey][$colkey] = $this->wrapCell($cell, $auto_widths->width($colkey));
             }
         }
 
@@ -70,158 +80,43 @@ class WordWrapper
     }
 
     /**
-     * Wrap one cell.  Guard against modifying non-strings and
-     * then call through to wordwrap().
-     *
-     * @param mixed $cell
-     * @param string $cellWidth
-     * @return mixed
-     */
-    protected function wrapCell($cell, $cellWidth)
-    {
-        if (!is_string($cell)) {
-            return $cell;
-        }
-        return wordwrap($cell, $cellWidth, "\n", true);
-    }
-
-    /**
-     * Determine the best fit for column widths. Ported from Drush.
-     *
-     * @param array $rows The rows to use for calculations.
-     * @param array $widths Manually specified widths of each column
-     *   (in characters) - these will be left as is.
+     * Determine what widths we'll use for wrapping.
      */
-    protected function columnAutowidth($rows, $widths)
+    protected function calculateWidths($rows, $widths = [])
     {
-        $auto_widths = $widths;
-
-        // First we determine the distribution of row lengths in each column.
-        // This is an array of descending character length keys (i.e. starting at
-        // the rightmost character column), with the value indicating the number
-        // of rows where that character column is present.
-        $col_dist = [];
-        // We will also calculate the longest word in each column
-        $max_word_lens = [];
-        foreach ($rows as $rowkey => $row) {
-            foreach ($row as $col_id => $cell) {
-                $longest_word_len = static::longestWordLength($cell);
-                if ((!isset($max_word_lens[$col_id]) || ($max_word_lens[$col_id] < $longest_word_len))) {
-                    $max_word_lens[$col_id] = $longest_word_len;
-                }
-                if (empty($widths[$col_id])) {
-                    $length = strlen($cell);
-                    if ($length == 0) {
-                        $col_dist[$col_id][0] = 0;
-                    }
-                    while ($length > 0) {
-                        if (!isset($col_dist[$col_id][$length])) {
-                            $col_dist[$col_id][$length] = 0;
-                        }
-                        $col_dist[$col_id][$length]++;
-                        $length--;
-                    }
-                }
-            }
+        // Widths must be provided in some form or another, or we won't wrap.
+        if (empty($widths) && !$this->width) {
+            return new ColumnWidths();
         }
 
-        foreach ($col_dist as $col_id => $count) {
-            // Sort the distribution in decending key order.
-            krsort($col_dist[$col_id]);
-            // Initially we set all columns to their "ideal" longest width
-            // - i.e. the width of their longest column.
-            $auto_widths[$col_id] = max(array_keys($col_dist[$col_id]));
-        }
-
-        // We determine what width we have available to use, and what width the
-        // above "ideal" columns take up.
-        $available_width = $this->width - ($this->extraPaddingAtBeginningOfLine + $this->extraPaddingAtEndOfLine + (count($auto_widths) * $this->paddingInEachCell));
-        $auto_width_current = array_sum($auto_widths);
-
-        // If we cannot fit into the minimum width anyway, then just return
-        // the max word length of each column as the 'ideal'
-        $minimumIdealLength = array_sum($this->minimumWidths);
-        if ($minimumIdealLength && ($available_width < $minimumIdealLength)) {
-            return $max_word_lens;
-        }
+        // Technically, `$widths`, if provided here, should be used
+        // as the exact widths to wrap to. For now we'll just treat
+        // these as minimum widths
+        $minimumWidths = $this->minimumWidths->combine(new ColumnWidths($widths));
 
-        // If we need to reduce a column so that we can fit the space we use this
-        // loop to figure out which column will cause the "least wrapping",
-        // (relative to the other columns) and reduce the width of that column.
-        while ($auto_width_current > $available_width) {
-            list($column, $width) = $this->selectColumnToReduce($col_dist, $auto_widths, $max_word_lens);
+        $calculator = new CalculateWidths();
+        $dataCellWidths = $calculator->calculateLongestCell($rows);
 
-            if (!$column || $width <= 1) {
-                // If we have reached a width of 1 then give up, so wordwrap can still progress.
-                break;
-            }
-            // Reduce the width of the selected column.
-            $auto_widths[$column]--;
-            // Reduce our overall table width counter.
-            $auto_width_current--;
-            // Remove the corresponding data from the disctribution, so next time
-            // around we use the data for the row to the left.
-            unset($col_dist[$column][$width]);
-        }
-        return $auto_widths;
-    }
+        $availableWidth = $this->width - $dataCellWidths->paddingSpace($this->paddingInEachCell, $this->extraPaddingAtEndOfLine, $this->extraPaddingAtBeginningOfLine);
 
-    protected function selectColumnToReduce($col_dist, $auto_widths, $max_word_lens)
-    {
-        $column = false;
-        $count = 0;
-        $width = 0;
-        foreach ($col_dist as $col_id => $counts) {
-            // Of the columns whose length is still > than the the lenght
-            // of their maximum word length
-            if ($auto_widths[$col_id] > $max_word_lens[$col_id]) {
-                if ($this->shouldSelectThisColumn($count, $counts, $width)) {
-                    $column = $col_id;
-                    $count = current($counts);
-                    $width = key($counts);
-                }
-            }
-        }
-        if ($column !== false) {
-            return [$column, $width];
-        }
-        foreach ($col_dist as $col_id => $counts) {
-            if (empty($this->minimumWidths) || ($auto_widths[$col_id] > $this->minimumWidths[$col_id])) {
-                if ($this->shouldSelectThisColumn($count, $counts, $width)) {
-                    $column = $col_id;
-                    $count = current($counts);
-                    $width = key($counts);
-                }
-            }
-        }
-        return [$column, $width];
-    }
+        $this->minimumWidths->adjustMinimumWidths($availableWidth, $dataCellWidths);
 
-    protected function shouldSelectThisColumn($count, $counts, $width)
-    {
-        return
-            // If we are just starting out, select the first column.
-            ($count == 0) ||
-            // OR: if this column would cause less wrapping than the currently
-            // selected column, then select it.
-            (current($counts) < $count) ||
-            // OR: if this column would cause the same amount of wrapping, but is
-            // longer, then we choose to wrap the longer column (proportionally
-            // less wrapping, and helps avoid triple line wraps).
-            (current($counts) == $count && key($counts) > $width);
+        return $calculator->calculate($availableWidth, $dataCellWidths, $minimumWidths);
     }
 
     /**
-     * Return the length of the longest word in the string.
-     * @param string $str
-     * @return int
+     * Wrap one cell.  Guard against modifying non-strings and
+     * then call through to wordwrap().
+     *
+     * @param mixed $cell
+     * @param string $cellWidth
+     * @return mixed
      */
-    protected static function longestWordLength($str)
+    protected function wrapCell($cell, $cellWidth)
     {
-        $words = preg_split('/[ -]/', $str);
-        $lengths = array_map(function ($s) {
-            return strlen($s);
-        }, $words);
-        return max($lengths);
+        if (!is_string($cell)) {
+            return $cell;
+        }
+        return wordwrap($cell, $cellWidth, "\n", true);
     }
 }