-
Notifications
You must be signed in to change notification settings - Fork 115
Expand file tree
/
Copy pathTable.php
More file actions
349 lines (312 loc) · 9.33 KB
/
Table.php
File metadata and controls
349 lines (312 loc) · 9.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
<?php
/**
* PHP Command Line Tools
*
* This source file is subject to the MIT license that is bundled
* with this package in the file LICENSE.
*
* @author James Logsdon <dwarf@girsbrain.org>
* @copyright 2010 James Logsdom (http://girsbrain.org)
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace cli;
use cli\Shell;
use cli\Streams;
use cli\table\Ascii;
use cli\table\Column;
use cli\table\Renderer;
use cli\table\Tabular;
/**
* The `Table` class is used to display data in a tabular format.
*/
class Table {
protected $_renderer;
protected $_headers = array();
protected $_footers = array();
protected $_width = array();
protected $_rows = array();
protected $_alignments = array();
/**
* Cached map of valid alignment constants.
*
* @var array|null
*/
private static $_valid_alignments_map = null;
/**
* Initializes the `Table` class.
*
* There are 3 ways to instantiate this class:
*
* 1. Pass an array of strings as the first parameter for the column headers
* and a 2-dimensional array as the second parameter for the data rows.
* 2. Pass an array of hash tables (string indexes instead of numerical)
* where each hash table is a row and the indexes of the *first* hash
* table are used as the header values.
* 3. Pass nothing and use `setHeaders()` and `addRow()` or `setRows()`.
*
* @param array $headers Headers used in this table. Optional.
* @param array $rows The rows of data for this table. Optional.
* @param array $footers Footers used in this table. Optional.
* @param array $alignments Column alignments. Optional.
*/
public function __construct(array $headers = array(), array $rows = array(), array $footers = array(), array $alignments = array()) {
if (!empty($headers)) {
// If all the rows is given in $headers we use the keys from the
// first row for the header values
if ($rows === array()) {
$rows = $headers;
$keys = array_keys(array_shift($headers));
$headers = array();
foreach ($keys as $header) {
$headers[$header] = $header;
}
}
$this->setHeaders($headers);
$this->setRows($rows);
}
if (!empty($footers)) {
$this->setFooters($footers);
}
if (!empty($alignments)) {
$this->setAlignments($alignments);
}
if (Shell::isPiped()) {
$this->setRenderer(new Tabular());
} else {
$this->setRenderer(new Ascii());
}
}
public function resetTable()
{
$this->_headers = array();
$this->_width = array();
$this->_rows = array();
$this->_footers = array();
$this->_alignments = array();
return $this;
}
/**
* Resets only the rows in the table, keeping headers, footers, and width information.
*
* @return $this
*/
public function resetRows()
{
$this->_rows = array();
return $this;
}
/**
* Sets the renderer used by this table.
*
* @param table\Renderer $renderer The renderer to use for output.
* @see table\Renderer
* @see table\Ascii
* @see table\Tabular
*/
public function setRenderer(Renderer $renderer) {
$this->_renderer = $renderer;
}
/**
* Loops through the row and sets the maximum width for each column.
*
* @param array $row The table row.
* @return array $row
*/
protected function checkRow(array $row) {
foreach ($row as $column => $str) {
$width = Colors::width( $str, $this->isAsciiPreColorized( $column ) );
if (!isset($this->_width[$column]) || $width > $this->_width[$column]) {
$this->_width[$column] = $width;
}
}
return $row;
}
/**
* Output the table to `STDOUT` using `cli\line()`.
*
* If STDOUT is a pipe or redirected to a file, should output simple
* tab-separated text. Otherwise, renders table with ASCII table borders
*
* @uses cli\Shell::isPiped() Determine what format to output
*
* @see cli\Table::renderRow()
*/
public function display() {
foreach( $this->getDisplayLines() as $line ) {
Streams::line( $line );
}
}
/**
* Display a single row without headers or top border.
*
* This method is useful for adding rows incrementally to an already-rendered table.
* It will display the row with side borders and a bottom border (if using Ascii renderer).
*
* @param array $row The row data to display.
*/
public function displayRow(array $row) {
// Update widths if this row has wider content
$row = $this->checkRow($row);
// Recalculate widths for the renderer
$this->_renderer->setWidths($this->_width, false);
$rendered_row = $this->_renderer->row($row);
$row_lines = explode( PHP_EOL, $rendered_row );
foreach ( $row_lines as $line ) {
Streams::line( $line );
}
$border = $this->_renderer->border();
if (isset($border)) {
Streams::line( $border );
}
}
/**
* Get the table lines to output.
*
* @see cli\Table::display()
* @see cli\Table::renderRow()
*
* @return array
*/
public function getDisplayLines() {
$this->_renderer->setWidths($this->_width, $fallback = true);
$this->_renderer->setHeaders($this->_headers);
$this->_renderer->setAlignments($this->_alignments);
$border = $this->_renderer->border();
$out = array();
if (isset($border)) {
$out[] = $border;
}
$out[] = $this->_renderer->row($this->_headers);
if (isset($border)) {
$out[] = $border;
}
foreach ($this->_rows as $row) {
$row = $this->_renderer->row($row);
$row = explode( PHP_EOL, $row );
$out = array_merge( $out, $row );
}
// Only add final border if there are rows
if (!empty($this->_rows) && isset($border)) {
$out[] = $border;
}
if ($this->_footers) {
$out[] = $this->_renderer->row($this->_footers);
if (isset($border)) {
$out[] = $border;
}
}
return $out;
}
/**
* Sort the table by a column. Must be called before `cli\Table::display()`.
*
* @param int $column The index of the column to sort by.
*/
public function sort($column) {
if (!isset($this->_headers[$column])) {
trigger_error('No column with index ' . $column, E_USER_NOTICE);
return;
}
usort($this->_rows, function($a, $b) use ($column) {
return strcmp($a[$column], $b[$column]);
});
}
/**
* Set the headers of the table.
*
* @param array $headers An array of strings containing column header names.
*/
public function setHeaders(array $headers) {
$this->_headers = $this->checkRow($headers);
}
/**
* Set the footers of the table.
*
* @param array $footers An array of strings containing column footers names.
*/
public function setFooters(array $footers) {
$this->_footers = $this->checkRow($footers);
}
/**
* Set the alignments of the table.
*
* @param array $alignments An array of alignment constants keyed by column name.
*/
public function setAlignments(array $alignments) {
// Initialize the cached valid alignments map on first use
if ( null === self::$_valid_alignments_map ) {
self::$_valid_alignments_map = array_flip( array( Column::ALIGN_LEFT, Column::ALIGN_RIGHT, Column::ALIGN_CENTER ) );
}
$headers_map = ! empty( $this->_headers ) ? array_flip( $this->_headers ) : null;
foreach ( $alignments as $column => $alignment ) {
if ( ! isset( self::$_valid_alignments_map[ $alignment ] ) ) {
throw new \InvalidArgumentException( "Invalid alignment value '$alignment' for column '$column'." );
}
// Only validate column names if headers are already set
if ( $headers_map !== null && ! isset( $headers_map[ $column ] ) ) {
throw new \InvalidArgumentException( "Column '$column' does not exist in table headers." );
}
}
$this->_alignments = $alignments;
}
/**
* Add a row to the table.
*
* @param array $row The row data.
* @see cli\Table::checkRow()
*/
public function addRow(array $row) {
$this->_rows[] = $this->checkRow($row);
}
/**
* Clears all previous rows and adds the given rows.
*
* @param array $rows A 2-dimensional array of row data.
* @see cli\Table::addRow()
*/
public function setRows(array $rows) {
$this->_rows = array();
foreach ($rows as $row) {
$this->addRow($row);
}
}
public function countRows() {
return count($this->_rows);
}
/**
* Set whether items in an Ascii table are pre-colorized.
*
* @param bool|array $precolorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized.
* @see cli\Ascii::setPreColorized()
*/
public function setAsciiPreColorized( $pre_colorized ) {
if ( $this->_renderer instanceof Ascii ) {
$this->_renderer->setPreColorized( $pre_colorized );
}
}
/**
* Set the wrapping mode for table cells.
*
* @param string $mode One of: 'wrap' (default - wrap at character boundaries),
* 'word-wrap' (wrap at word boundaries), or 'truncate' (truncate with ellipsis).
* @see cli\Ascii::setWrappingMode()
*/
public function setWrappingMode( $mode ) {
if ( $this->_renderer instanceof Ascii ) {
$this->_renderer->setWrappingMode( $mode );
}
}
/**
* Is a column in an Ascii table pre-colorized?
*
* @param int $column Column index to check.
* @return bool True if whole Ascii table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
* @see cli\Ascii::isPreColorized()
*/
private function isAsciiPreColorized( $column ) {
if ( $this->_renderer instanceof Ascii ) {
return $this->_renderer->isPreColorized( $column );
}
return false;
}
}