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