Completed
Pull Request — master (#55)
by Greg
02:21
created

ColumnWidths::distribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
rs 9.4285
cc 2
eloc 11
nc 2
nop 1
1
<?php
2
namespace Consolidation\OutputFormatters\Transformations\Wrap;
3
4
use Symfony\Component\Console\Helper\TableStyle;
5
6
/**
7
 * Calculate the width of data in table cells in preparation for word wrapping.
8
 */
9
class ColumnWidths
10
{
11
    protected $widths;
12
13
    public function __construct($widths = [])
14
    {
15
        $this->widths = $widths;
16
    }
17
18
    public function paddingSpace(
19
        $paddingInEachCell,
20
        $extraPaddingAtEndOfLine = 0,
21
        $extraPaddingAtBeginningOfLine = 0
22
    ) {
23
24
        return ($extraPaddingAtBeginningOfLine + $extraPaddingAtEndOfLine + (count($this->widths) * $paddingInEachCell));
25
    }
26
27
    /**
28
     * Find all of the columns that are shorter than the specified threshold.
29
     */
30
    public function findShortColumns($thresholdWidth)
31
    {
32
        $thresholdWidths = array_combine(array_keys($this->widths), array_fill(0, count($this->widths), $thresholdWidth));
33
34
        return $this->findColumnsUnderThreshold($thresholdWidths);
35
    }
36
37
    /**
38
     * Find all of the columns that are shorter than the corresponding minimum widths.
39
     */
40
    public function findUndersizedColumns($minimumWidths)
41
    {
42
        return $this->findColumnsUnderThreshold($minimumWidths->widths());
43
    }
44
45
    protected function findColumnsUnderThreshold(array $thresholdWidths)
46
    {
47
        $shortColWidths = [];
48
        foreach ($this->widths as $key => $maxLength) {
49
            if (isset($thresholdWidths[$key]) && ($maxLength <= $thresholdWidths[$key])) {
50
                $shortColWidths[$key] = $maxLength;
51
            }
52
        }
53
54
        return new ColumnWidths($shortColWidths);
55
    }
56
57
    /**
58
     * If the widths specified by this object do not fit within the
59
     * provided avaiable width, then reduce them all proportionally.
60
     */
61
    public function adjustMinimumWidths($availableWidth, $dataCellWidths)
62
    {
63
        $result = $this->selectColumns($dataCellWidths->keys());
64
        $numberOfColumns = $dataCellWidths->count();
65
66
        // How many unspecified columns are there?
67
        $unspecifiedColumns = $numberOfColumns - $result->count();
68
        $averageWidth = $this->averageWidth($availableWidth);
69
70
        // Reserve some space for the columns that have no minimum.
71
        // Make sure they collectively get at least half of the average
72
        // width for each column. Or should it be a quarter?
73
        $reservedSpacePerColumn = ($averageWidth / 2);
74
        $reservedSpace = $reservedSpacePerColumn * $unspecifiedColumns;
75
76
        // Calculate how much of the available space is remaining for use by
77
        // the minimum column widths after the reserved space is accounted for.
78
        $remainingAvailable = $availableWidth - $reservedSpace;
79
80
        // Don't do anything if our widths fit inside the available widths.
81
        if ($result->totalWidth() <= $remainingAvailable) {
82
            return $result;
83
        }
84
85
        // Shrink the minimum widths if the table is too compressed.
86
        return $result->distribute($remainingAvailable);
87
    }
88
89
    /**
90
     * Return proportional weights
91
     */
92
    public function distribute($availableWidth)
93
    {
94
        $result = [];
95
        $totalWidth = $this->totalWidth();
96
        $lastColumn = $this->lastColumn();
97
        $widths = $this->widths();
98
99
        // Take off the last column, and calculate proportional weights
100
        // for the first N-1 columns.
101
        array_pop($widths);
102
        foreach ($widths as $key => $width) {
103
            $result[$key] = round(($width / $totalWidth) * $availableWidth);
104
        }
105
106
        // Give the last column the rest of the available width
107
        $usedWidth = $this->sumWidth($result);
108
        $result[$lastColumn] = $availableWidth - $usedWidth;
109
110
        return new ColumnWidths($result);
111
    }
112
113
    public function lastColumn()
114
    {
115
        $keys = $this->keys();
116
        return array_pop($keys);
117
    }
118
119
    /**
120
     * Return the number of columns.
121
     */
122
    public function count()
123
    {
124
        return count($this->widths);
125
    }
126
127
    /**
128
     * Calculate how much space is available on average for all columns.
129
     */
130
    public function averageWidth($availableWidth)
131
    {
132
        return $availableWidth / $this->count();
133
    }
134
135
    /**
136
     * Return the available keys (column identifiers) from the calculated
137
     * data set.
138
     */
139
    public function keys()
140
    {
141
        return array_keys($this->widths);
142
    }
143
144
    /**
145
     * Set the length of the specified column.
146
     */
147
    public function setWidth($key, $width)
148
    {
149
        $this->widths[$key] = $width;
150
    }
151
152
    /**
153
     * Return the length of the specified column.
154
     */
155
    public function width($key)
156
    {
157
        return isset($this->widths[$key]) ? $this->widths[$key] : 0;
158
    }
159
160
    /**
161
     * Return all of the lengths
162
     */
163
    public function widths()
164
    {
165
        return $this->widths;
166
    }
167
168
    /**
169
     * Return true if there is no data in this object
170
     */
171
    public function isEmpty()
172
    {
173
        return empty($this->widths);
174
    }
175
176
    /**
177
     * Return the sum of the lengths of the provided widths.
178
     */
179
    public function totalWidth()
180
    {
181
        return static::sumWidth($this->widths());
182
    }
183
184
    /**
185
     * Return the sum of the lengths of the provided widths.
186
     */
187
    public static function sumWidth($widths)
188
    {
189
        return array_reduce(
190
            $widths,
191
            function ($carry, $item) {
192
                return $carry + $item;
193
            }
194
        );
195
    }
196
197
    /**
198
     * Ensure that every item in $widths that has a corresponding entry
199
     * in $minimumWidths is as least as large as the minimum value held there.
200
     */
201
    public function enforceMinimums($minimumWidths)
202
    {
203
        $result = [];
204
        if ($minimumWidths instanceof ColumnWidths) {
205
            $minimumWidths = $minimumWidths->widths();
206
        }
207
        $minimumWidths += $this->widths;
208
209
        foreach ($this->widths as $key => $value) {
210
            $result[$key] = min($value, $minimumWidths[$key]);
211
        }
212
213
        return new ColumnWidths($result);
214
    }
215
216
    /**
217
     * Remove all of the specified columns from this data structure.
218
     */
219
    public function removeColumns($columnKeys)
220
    {
221
        $widths = $this->widths();
222
223
        foreach ($columnKeys as $key) {
224
            unset($widths[$key]);
225
        }
226
227
        return new ColumnWidths($widths);
228
    }
229
230
    /**
231
     * Select all columns that exist in the provided list of keys.
232
     */
233
    public function selectColumns($columnKeys)
234
    {
235
        $widths = [];
236
237
        foreach ($columnKeys as $key) {
238
            $widths[$key] = $this->width($key);
239
        }
240
241
        return new ColumnWidths($widths);
242
    }
243
244
    /**
245
     * Combine this set of widths with another set, and return
246
     * a new set that contains the entries from both.
247
     */
248
    public function combine(ColumnWidths $combineWith)
249
    {
250
        $combined = array_merge($combineWith->widths(), $this->widths());
251
252
        return new ColumnWidths($combined);
253
    }
254
}
255