Skip to content

Commit dfd6b61

Browse files
Copilotswissspidy
andcommitted
Add table column alignment feature
- Create Column interface with ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER constants - Update Renderer base class to support alignments and headers - Update Ascii renderer to apply alignment when padding columns - Update Table class to support alignment in constructor and setAlignments() - Add comprehensive tests for all alignment types - Address PR #171 feedback: headers passed explicitly to renderer, validation allows setting alignments before headers Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
1 parent 110db79 commit dfd6b61

File tree

5 files changed

+171
-6
lines changed

5 files changed

+171
-6
lines changed

lib/cli/Table.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use cli\Shell;
1616
use cli\Streams;
1717
use cli\table\Ascii;
18+
use cli\table\Column;
1819
use cli\table\Renderer;
1920
use cli\table\Tabular;
2021

@@ -27,6 +28,7 @@ class Table {
2728
protected $_footers = array();
2829
protected $_width = array();
2930
protected $_rows = array();
31+
protected $_alignments = array();
3032

3133
/**
3234
* Initializes the `Table` class.
@@ -40,11 +42,12 @@ class Table {
4042
* table are used as the header values.
4143
* 3. Pass nothing and use `setHeaders()` and `addRow()` or `setRows()`.
4244
*
43-
* @param array $headers Headers used in this table. Optional.
44-
* @param array $rows The rows of data for this table. Optional.
45-
* @param array $footers Footers used in this table. Optional.
45+
* @param array $headers Headers used in this table. Optional.
46+
* @param array $rows The rows of data for this table. Optional.
47+
* @param array $footers Footers used in this table. Optional.
48+
* @param array $alignments Column alignments. Optional.
4649
*/
47-
public function __construct(array $headers = array(), array $rows = array(), array $footers = array()) {
50+
public function __construct(array $headers = array(), array $rows = array(), array $footers = array(), array $alignments = array()) {
4851
if (!empty($headers)) {
4952
// If all the rows is given in $headers we use the keys from the
5053
// first row for the header values
@@ -66,6 +69,10 @@ public function __construct(array $headers = array(), array $rows = array(), arr
6669
$this->setFooters($footers);
6770
}
6871

72+
if (!empty($alignments)) {
73+
$this->setAlignments($alignments);
74+
}
75+
6976
if (Shell::isPiped()) {
7077
$this->setRenderer(new Tabular());
7178
} else {
@@ -79,6 +86,7 @@ public function resetTable()
7986
$this->_width = array();
8087
$this->_rows = array();
8188
$this->_footers = array();
89+
$this->_alignments = array();
8290
return $this;
8391
}
8492

@@ -137,6 +145,8 @@ public function display() {
137145
*/
138146
public function getDisplayLines() {
139147
$this->_renderer->setWidths($this->_width, $fallback = true);
148+
$this->_renderer->setHeaders($this->_headers);
149+
$this->_renderer->setAlignments($this->_alignments);
140150
$border = $this->_renderer->border();
141151

142152
$out = array();
@@ -201,6 +211,25 @@ public function setFooters(array $footers) {
201211
$this->_footers = $this->checkRow($footers);
202212
}
203213

214+
/**
215+
* Set the alignments of the table.
216+
*
217+
* @param array $alignments An array of alignment constants keyed by column name.
218+
*/
219+
public function setAlignments(array $alignments) {
220+
$valid_alignments = array( Column::ALIGN_LEFT, Column::ALIGN_RIGHT, Column::ALIGN_CENTER );
221+
foreach ( $alignments as $column => $alignment ) {
222+
if ( ! in_array( $alignment, $valid_alignments, true ) ) {
223+
throw new \InvalidArgumentException( "Invalid alignment value '$alignment' for column '$column'." );
224+
}
225+
// Only validate column names if headers are already set
226+
if ( ! empty( $this->_headers ) && ! in_array( $column, $this->_headers, true ) ) {
227+
throw new \InvalidArgumentException( "Column '$column' does not exist in table headers." );
228+
}
229+
}
230+
$this->_alignments = $alignments;
231+
}
232+
204233

205234
/**
206235
* Add a row to the table.

lib/cli/table/Ascii.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,10 @@ public function row( array $row ) {
198198
}
199199

200200
private function padColumn($content, $column) {
201+
$column_name = isset( $this->_headers[$column] ) ? $this->_headers[$column] : '';
202+
$alignment = ( $column_name !== '' && array_key_exists( $column_name, $this->_alignments ) ) ? $this->_alignments[$column_name] : Column::ALIGN_LEFT;
201203
$content = str_replace( "\t", ' ', (string) $content );
202-
return $this->_characters['padding'] . Colors::pad( $content, $this->_widths[ $column ], $this->isPreColorized( $column ) ) . $this->_characters['padding'];
204+
return $this->_characters['padding'] . Colors::pad( $content, $this->_widths[ $column ], $this->isPreColorized( $column ), false, $alignment ) . $this->_characters['padding'];
203205
}
204206

205207
/**

lib/cli/table/Column.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* PHP Command Line Tools
4+
*
5+
* This source file is subject to the MIT license that is bundled
6+
* with this package in the file LICENSE.
7+
*
8+
* @author James Logsdon <dwarf@girsbrain.org>
9+
* @copyright 2010 James Logsdom (http://girsbrain.org)
10+
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
11+
*/
12+
13+
namespace cli\table;
14+
15+
/**
16+
* Column alignment constants for table rendering.
17+
*/
18+
interface Column {
19+
const ALIGN_LEFT = STR_PAD_RIGHT;
20+
const ALIGN_RIGHT = STR_PAD_LEFT;
21+
const ALIGN_CENTER = STR_PAD_BOTH;
22+
}

lib/cli/table/Renderer.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,30 @@
1717
*/
1818
abstract class Renderer {
1919
protected $_widths = array();
20+
protected $_alignments = array();
21+
protected $_headers = array();
2022

21-
public function __construct(array $widths = array()) {
23+
public function __construct(array $widths = array(), array $alignments = array()) {
2224
$this->setWidths($widths);
25+
$this->setAlignments($alignments);
26+
}
27+
28+
/**
29+
* Set the alignments of each column in the table.
30+
*
31+
* @param array $alignments The alignments of the columns.
32+
*/
33+
public function setAlignments(array $alignments) {
34+
$this->_alignments = $alignments;
35+
}
36+
37+
/**
38+
* Set the headers of the table.
39+
*
40+
* @param array $headers The headers of the table.
41+
*/
42+
public function setHeaders(array $headers) {
43+
$this->_headers = $headers;
2344
}
2445

2546
/**

tests/Test_Table.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,95 @@ public function test_null_values_are_handled() {
289289
];
290290
$this->assertSame( $expected, $out, 'Null values should be safely converted to empty strings in table output.' );
291291
}
292+
293+
public function test_default_alignment() {
294+
$table = new cli\Table();
295+
$table->setRenderer( new cli\Table\Ascii() );
296+
$table->setHeaders( array( 'Header1', 'Header2' ) );
297+
$table->addRow( array( 'Row1Col1', 'Row1Col2' ) );
298+
299+
$out = $table->getDisplayLines();
300+
301+
// By default, columns should be left-aligned.
302+
$this->assertStringContainsString( '| Header1 | Header2 |', $out[1] );
303+
$this->assertStringContainsString( '| Row1Col1 | Row1Col2 |', $out[3] );
304+
}
305+
306+
public function test_right_alignment() {
307+
$table = new cli\Table();
308+
$table->setRenderer( new cli\Table\Ascii() );
309+
$table->setHeaders( array( 'Name', 'Size' ) );
310+
$table->setAlignments( array( 'Name' => \cli\table\Column::ALIGN_RIGHT, 'Size' => \cli\table\Column::ALIGN_RIGHT ) );
311+
$table->addRow( array( 'file.txt', '1024 B' ) );
312+
313+
$out = $table->getDisplayLines();
314+
315+
// Headers should be right-aligned in their columns
316+
$this->assertStringContainsString( '| Name | Size |', $out[1] );
317+
// Data should be right-aligned
318+
$this->assertStringContainsString( '| file.txt | 1024 B |', $out[3] );
319+
}
320+
321+
public function test_center_alignment() {
322+
$table = new cli\Table();
323+
$table->setRenderer( new cli\Table\Ascii() );
324+
$table->setHeaders( array( 'A', 'B' ) );
325+
$table->setAlignments( array( 'A' => \cli\table\Column::ALIGN_CENTER, 'B' => \cli\table\Column::ALIGN_CENTER ) );
326+
$table->addRow( array( 'test', 'data' ) );
327+
328+
$out = $table->getDisplayLines();
329+
330+
// Headers should be center-aligned
331+
$this->assertStringContainsString( '| A | B |', $out[1] );
332+
// Data should be center-aligned
333+
$this->assertStringContainsString( '| test | data |', $out[3] );
334+
}
335+
336+
public function test_mixed_alignments() {
337+
$table = new cli\Table();
338+
$table->setRenderer( new cli\Table\Ascii() );
339+
$table->setHeaders( array( 'Name', 'Count', 'Status' ) );
340+
$table->setAlignments( array(
341+
'Name' => \cli\table\Column::ALIGN_LEFT,
342+
'Count' => \cli\table\Column::ALIGN_RIGHT,
343+
'Status' => \cli\table\Column::ALIGN_CENTER,
344+
) );
345+
$table->addRow( array( 'Item', '42', 'OK' ) );
346+
347+
$out = $table->getDisplayLines();
348+
349+
// Headers line should show all three with proper alignment
350+
$this->assertStringContainsString( '| Name | Count | Status |', $out[1] );
351+
// Data line: Name left, Count right, Status center
352+
$this->assertStringContainsString( '| Item | 42 | OK |', $out[3] );
353+
}
354+
355+
public function test_invalid_alignment_value() {
356+
$this->expectException( \InvalidArgumentException::class );
357+
$table = new cli\Table();
358+
$table->setHeaders( array( 'Header1' ) );
359+
$table->setAlignments( array( 'Header1' => 'invalid-alignment' ) );
360+
}
361+
362+
public function test_invalid_alignment_column() {
363+
$this->expectException( \InvalidArgumentException::class );
364+
$table = new cli\Table();
365+
$table->setHeaders( array( 'Header1' ) );
366+
$table->setAlignments( array( 'NonExistent' => \cli\table\Column::ALIGN_LEFT ) );
367+
}
368+
369+
public function test_alignment_before_headers() {
370+
// Test that alignments can be set before headers without throwing an error
371+
$table = new cli\Table();
372+
$table->setRenderer( new cli\Table\Ascii() );
373+
$table->setAlignments( array( 'Name' => \cli\table\Column::ALIGN_RIGHT ) );
374+
$table->setHeaders( array( 'Name' ) );
375+
$table->addRow( array( 'LongName' ) );
376+
377+
$out = $table->getDisplayLines();
378+
379+
// Should be right-aligned - "Name" is 4 chars, "LongName" is 8 chars, so column width is 8
380+
$this->assertStringContainsString( '| Name |', $out[1] );
381+
$this->assertStringContainsString( '| LongName |', $out[3] );
382+
}
292383
}

0 commit comments

Comments
 (0)