From ad5c44ed0b00a3b242d9b64cf19371639069242d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 13 Sep 2023 20:38:21 -0400 Subject: [PATCH 1/9] add a table helper --- src/Concerns/Themes.php | 3 ++ src/Table.php | 50 ++++++++++++++++++++++++++ src/Themes/Default/TableRenderer.php | 38 ++++++++++++++++++++ src/helpers.php | 8 +++++ tests/Feature/TableTest.php | 54 ++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/Table.php create mode 100644 src/Themes/Default/TableRenderer.php create mode 100644 tests/Feature/TableTest.php diff --git a/src/Concerns/Themes.php b/src/Concerns/Themes.php index cfdbd5eb..1af16c5c 100644 --- a/src/Concerns/Themes.php +++ b/src/Concerns/Themes.php @@ -11,6 +11,7 @@ use Laravel\Prompts\SelectPrompt; use Laravel\Prompts\Spinner; use Laravel\Prompts\SuggestPrompt; +use Laravel\Prompts\Table; use Laravel\Prompts\TextPrompt; use Laravel\Prompts\Themes\Default\ConfirmPromptRenderer; use Laravel\Prompts\Themes\Default\MultiSelectPromptRenderer; @@ -20,6 +21,7 @@ use Laravel\Prompts\Themes\Default\SelectPromptRenderer; use Laravel\Prompts\Themes\Default\SpinnerRenderer; use Laravel\Prompts\Themes\Default\SuggestPromptRenderer; +use Laravel\Prompts\Themes\Default\TableRenderer; use Laravel\Prompts\Themes\Default\TextPromptRenderer; trait Themes @@ -45,6 +47,7 @@ trait Themes SuggestPrompt::class => SuggestPromptRenderer::class, Spinner::class => SpinnerRenderer::class, Note::class => NoteRenderer::class, + Table::class => TableRenderer::class, ], ]; diff --git a/src/Table.php b/src/Table.php new file mode 100644 index 00000000..7b565e1f --- /dev/null +++ b/src/Table.php @@ -0,0 +1,50 @@ +|Collection $headers + * @param array|Collection $rows + */ + public function __construct(public array|Collection $headers, public array|Collection $rows) + { + $this->headers = $this->headers instanceof Collection ? $this->headers->all() : $this->headers; + $this->rows = $this->rows instanceof Collection ? $this->rows->all() : $this->rows; + } + + /** + * Display the table. + */ + public function display(): void + { + $this->prompt(); + } + + /** + * Display the table. + */ + public function prompt(): bool + { + $this->capturePreviousNewLines(); + + $this->state = 'submit'; + + static::output()->write($this->renderTheme()); + + return true; + } + + /** + * Get the value of the prompt. + */ + public function value(): bool + { + return true; + } +} diff --git a/src/Themes/Default/TableRenderer.php b/src/Themes/Default/TableRenderer.php new file mode 100644 index 00000000..65c8fc4a --- /dev/null +++ b/src/Themes/Default/TableRenderer.php @@ -0,0 +1,38 @@ +setHorizontalBorderChars('─') + ->setVerticalBorderChars(' │', '│') + ->setCrossingChars('┼', ' ┌', '┬', '─┐', '─┤', '─┘', '┴', ' └', ' ├') + ->setCellHeaderFormat($this->dim('%s')) + ->setBorderFormat($this->dim('%s')); + + $buffered = new BufferedConsoleOutput(); + + (new SymfonyTable($buffered)) + ->setHeaders($table->headers) + ->setRows($table->rows) + ->setStyle($tableStyle) + ->render(); + + collect(explode(PHP_EOL, $buffered->content())) + ->filter(fn ($line) => $line !== '') + ->each(fn ($line) => $this->line($line)); + + return $this; + } +} diff --git a/src/helpers.php b/src/helpers.php index bc058fad..d5fc346f 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -139,3 +139,11 @@ function outro(string $message): void { (new Note($message, 'outro'))->display(); } + +/** + * Display a table. + */ +function table(array|Collection $headers, array|Collection $rows): void +{ + (new Table($headers, $rows))->display(); +} diff --git a/tests/Feature/TableTest.php b/tests/Feature/TableTest.php new file mode 100644 index 00000000..8daeaae7 --- /dev/null +++ b/tests/Feature/TableTest.php @@ -0,0 +1,54 @@ +with([ + [ + ['Name', 'Twitter'], + [ + ['Taylor Otwell', '@taylorotwell'], + ['Dries Vints', '@driesvints'], + ['James Brooks', '@jbrooksuk'], + ['Nuno Maduro', '@enunomaduro'], + ['Mior Muhammad Zaki', '@crynobone'], + ['Jess Archer', '@jessarchercodes'], + ['Guus Leeuw', '@phpguus'], + ['Tim MacDonald', '@timacdonald87'], + ['Joe Dixon', '@_joedixon'], + ], + ], + [ + collect(['Name', 'Twitter']), + collect([ + ['Taylor Otwell', '@taylorotwell'], + ['Dries Vints', '@driesvints'], + ['James Brooks', '@jbrooksuk'], + ['Nuno Maduro', '@enunomaduro'], + ['Mior Muhammad Zaki', '@crynobone'], + ['Jess Archer', '@jessarchercodes'], + ['Guus Leeuw', '@phpguus'], + ['Tim MacDonald', '@timacdonald87'], + ['Joe Dixon', '@_joedixon'], + ]), + ], +]); From c4a6ce15a7d89af595b10b3dac1b92025a7d9c0f Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 13 Sep 2023 20:48:14 -0400 Subject: [PATCH 2/9] added docblock for table params --- src/helpers.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers.php b/src/helpers.php index d5fc346f..c9a56358 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -142,6 +142,9 @@ function outro(string $message): void /** * Display a table. + * + * @param array|Collection $headers + * @param array|Collection $rows */ function table(array|Collection $headers, array|Collection $rows): void { From f68ef7eeebe2302360a8da1f2c256729f7569866 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 13 Sep 2023 20:51:49 -0400 Subject: [PATCH 3/9] dealing with the collections in the helper function appeasing static analysis --- src/Table.php | 10 +++------- src/helpers.php | 3 +++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Table.php b/src/Table.php index 7b565e1f..e079a006 100644 --- a/src/Table.php +++ b/src/Table.php @@ -2,20 +2,16 @@ namespace Laravel\Prompts; -use Illuminate\Support\Collection; - class Table extends Prompt { /** * Create a new Table instance. * - * @param array|Collection $headers - * @param array|Collection $rows + * @param array $headers + * @param array $rows */ - public function __construct(public array|Collection $headers, public array|Collection $rows) + public function __construct(public array $headers, public array $rows) { - $this->headers = $this->headers instanceof Collection ? $this->headers->all() : $this->headers; - $this->rows = $this->rows instanceof Collection ? $this->rows->all() : $this->rows; } /** diff --git a/src/helpers.php b/src/helpers.php index c9a56358..87d2aac6 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -148,5 +148,8 @@ function outro(string $message): void */ function table(array|Collection $headers, array|Collection $rows): void { + $headers = $headers instanceof Collection ? $headers->all() : $headers; + $rows = $rows instanceof Collection ? $rows->all() : $rows; + (new Table($headers, $rows))->display(); } From d57fcffb1e7357ca9999646b675bd98256022860 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Thu, 14 Sep 2023 17:06:39 +1000 Subject: [PATCH 4/9] Fix colspan and make border color consistent --- src/Themes/Default/TableRenderer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Themes/Default/TableRenderer.php b/src/Themes/Default/TableRenderer.php index 65c8fc4a..1c3bf913 100644 --- a/src/Themes/Default/TableRenderer.php +++ b/src/Themes/Default/TableRenderer.php @@ -17,9 +17,9 @@ public function __invoke(Table $table): string $tableStyle = (new TableStyle()) ->setHorizontalBorderChars('─') ->setVerticalBorderChars(' │', '│') - ->setCrossingChars('┼', ' ┌', '┬', '─┐', '─┤', '─┘', '┴', ' └', ' ├') - ->setCellHeaderFormat($this->dim('%s')) - ->setBorderFormat($this->dim('%s')); + ->setCrossingChars('┼', ' ┌', '┬', '─┐', '─┤', '─┘', '┴', ' └', ' ├') + ->setCellHeaderFormat($this->dim('%s')) + ->setCellRowFormat('%s'); $buffered = new BufferedConsoleOutput(); From 93397932f2780b13eeca4dc7211dce06c4ec0ea0 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Thu, 14 Sep 2023 17:20:28 +1000 Subject: [PATCH 5/9] Remove extra whitespace from last column --- src/Themes/Default/TableRenderer.php | 9 ++++---- tests/Feature/TableTest.php | 32 +++++++++++++++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/Themes/Default/TableRenderer.php b/src/Themes/Default/TableRenderer.php index 1c3bf913..e1081500 100644 --- a/src/Themes/Default/TableRenderer.php +++ b/src/Themes/Default/TableRenderer.php @@ -16,8 +16,8 @@ public function __invoke(Table $table): string { $tableStyle = (new TableStyle()) ->setHorizontalBorderChars('─') - ->setVerticalBorderChars(' │', '│') - ->setCrossingChars('┼', ' ┌', '┬', '─┐', '─┤', '─┘', '┴', ' └', ' ├') + ->setVerticalBorderChars('│', '│') + ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') ->setCellHeaderFormat($this->dim('%s')) ->setCellRowFormat('%s'); @@ -29,9 +29,8 @@ public function __invoke(Table $table): string ->setStyle($tableStyle) ->render(); - collect(explode(PHP_EOL, $buffered->content())) - ->filter(fn ($line) => $line !== '') - ->each(fn ($line) => $this->line($line)); + collect(explode(PHP_EOL, trim($buffered->content(), PHP_EOL))) + ->each(fn ($line) => $this->line(' '.$line)); return $this; } diff --git a/tests/Feature/TableTest.php b/tests/Feature/TableTest.php index 8daeaae7..0b2781e3 100644 --- a/tests/Feature/TableTest.php +++ b/tests/Feature/TableTest.php @@ -9,21 +9,23 @@ table($headers, $rows); - Prompt::assertStrippedOutputContains('┌────────────────────┬───────────────────┐'); - Prompt::assertStrippedOutputContains('│ Name │ Twitter │'); - Prompt::assertStrippedOutputContains('├────────────────────┼───────────────────┤'); - Prompt::assertStrippedOutputContains('│ Taylor Otwell │ @taylorotwell │'); - Prompt::assertStrippedOutputContains('│ Dries Vints │ @driesvints │'); - Prompt::assertStrippedOutputContains('│ James Brooks │ @jbrooksuk │'); - Prompt::assertStrippedOutputContains('│ Nuno Maduro │ @enunomaduro │'); - Prompt::assertStrippedOutputContains('│ Mior Muhammad Zaki │ @crynobone │'); - Prompt::assertStrippedOutputContains('│ Jess Archer │ @jessarchercodes │'); - Prompt::assertStrippedOutputContains('│ Guus Leeuw │ @phpguus │'); - Prompt::assertStrippedOutputContains('│ Tim MacDonald │ @timacdonald87 │'); - Prompt::assertStrippedOutputContains('│ Joe Dixon │ @_joedixon │'); - Prompt::assertStrippedOutputContains('└────────────────────┴───────────────────┘'); + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌────────────────────┬──────────────────┐ + │ Name │ Twitter │ + ├────────────────────┼──────────────────┤ + │ Taylor Otwell │ @taylorotwell │ + │ Dries Vints │ @driesvints │ + │ James Brooks │ @jbrooksuk │ + │ Nuno Maduro │ @enunomaduro │ + │ Mior Muhammad Zaki │ @crynobone │ + │ Jess Archer │ @jessarchercodes │ + │ Guus Leeuw │ @phpguus │ + │ Tim MacDonald │ @timacdonald87 │ + │ Joe Dixon │ @_joedixon │ + └────────────────────┴──────────────────┘ + OUTPUT); })->with([ - [ + 'arrays' => [ ['Name', 'Twitter'], [ ['Taylor Otwell', '@taylorotwell'], @@ -37,7 +39,7 @@ ['Joe Dixon', '@_joedixon'], ], ], - [ + 'collections' => [ collect(['Name', 'Twitter']), collect([ ['Taylor Otwell', '@taylorotwell'], From 9fe5ad6579e0cf37c726fb66caa7f79d6d45a576 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Thu, 14 Sep 2023 17:32:39 +1000 Subject: [PATCH 6/9] Fix border when there are no headers --- src/Themes/Default/TableRenderer.php | 7 +++- tests/Feature/TableTest.php | 49 +++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/Themes/Default/TableRenderer.php b/src/Themes/Default/TableRenderer.php index e1081500..185f4500 100644 --- a/src/Themes/Default/TableRenderer.php +++ b/src/Themes/Default/TableRenderer.php @@ -17,10 +17,15 @@ public function __invoke(Table $table): string $tableStyle = (new TableStyle()) ->setHorizontalBorderChars('─') ->setVerticalBorderChars('│', '│') - ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') ->setCellHeaderFormat($this->dim('%s')) ->setCellRowFormat('%s'); + if (empty($table->headers)) { + $tableStyle->setCrossingChars('┼', '', '', '', '┤', '┘', '┴', '└', '├', '┌', '┬', '┐'); + } else { + $tableStyle->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├'); + } + $buffered = new BufferedConsoleOutput(); (new SymfonyTable($buffered)) diff --git a/tests/Feature/TableTest.php b/tests/Feature/TableTest.php index 0b2781e3..a52060b8 100644 --- a/tests/Feature/TableTest.php +++ b/tests/Feature/TableTest.php @@ -4,7 +4,7 @@ use function Laravel\Prompts\table; -it('renders a table', function ($headers, $rows) { +it('renders a table with headers', function ($headers, $rows) { Prompt::fake(); table($headers, $rows); @@ -54,3 +54,50 @@ ]), ], ]); + +it('renders a table without headers', function ($rows) { + Prompt::fake(); + + table([], $rows); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌────────────────────┬──────────────────┐ + │ Taylor Otwell │ @taylorotwell │ + │ Dries Vints │ @driesvints │ + │ James Brooks │ @jbrooksuk │ + │ Nuno Maduro │ @enunomaduro │ + │ Mior Muhammad Zaki │ @crynobone │ + │ Jess Archer │ @jessarchercodes │ + │ Guus Leeuw │ @phpguus │ + │ Tim MacDonald │ @timacdonald87 │ + │ Joe Dixon │ @_joedixon │ + └────────────────────┴──────────────────┘ + OUTPUT); +})->with([ + 'arrays' => [ + [ + ['Taylor Otwell', '@taylorotwell'], + ['Dries Vints', '@driesvints'], + ['James Brooks', '@jbrooksuk'], + ['Nuno Maduro', '@enunomaduro'], + ['Mior Muhammad Zaki', '@crynobone'], + ['Jess Archer', '@jessarchercodes'], + ['Guus Leeuw', '@phpguus'], + ['Tim MacDonald', '@timacdonald87'], + ['Joe Dixon', '@_joedixon'], + ], + ], + 'collections' => [ + collect([ + ['Taylor Otwell', '@taylorotwell'], + ['Dries Vints', '@driesvints'], + ['James Brooks', '@jbrooksuk'], + ['Nuno Maduro', '@enunomaduro'], + ['Mior Muhammad Zaki', '@crynobone'], + ['Jess Archer', '@jessarchercodes'], + ['Guus Leeuw', '@phpguus'], + ['Tim MacDonald', '@timacdonald87'], + ['Joe Dixon', '@_joedixon'], + ]), + ], +]); From 30973ae0ab5c627100edf9c2f9e68cc4d23259e5 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Thu, 14 Sep 2023 17:46:44 +1000 Subject: [PATCH 7/9] Allow passing rows as only argument --- src/Table.php | 31 ++++++++++++++++++++++++++++--- src/helpers.php | 11 +++++------ tests/Feature/TableTest.php | 2 +- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Table.php b/src/Table.php index e079a006..79eff029 100644 --- a/src/Table.php +++ b/src/Table.php @@ -2,16 +2,41 @@ namespace Laravel\Prompts; +use Illuminate\Support\Collection; + class Table extends Prompt { + /** + * The table headers. + * + * @var array> + */ + public array $headers; + + /** + * The table rows. + * + * @var array> + */ + public array $rows; + /** * Create a new Table instance. * - * @param array $headers - * @param array $rows + * @param array>|Collection> $headers + * @param array>|Collection> $rows + * + * @phpstan-param ($rows is null ? list>|Collection> : list>|Collection>) $headers */ - public function __construct(public array $headers, public array $rows) + public function __construct(array|Collection $headers = [], array|Collection $rows = null) { + if ($rows === null) { + $rows = $headers; + $headers = []; + } + + $this->headers = $headers instanceof Collection ? $headers->all() : $headers; + $this->rows = $rows instanceof Collection ? $rows->all() : $rows; } /** diff --git a/src/helpers.php b/src/helpers.php index 87d2aac6..221a4cf5 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -143,13 +143,12 @@ function outro(string $message): void /** * Display a table. * - * @param array|Collection $headers - * @param array|Collection $rows + * @param array>|Collection> $headers + * @param array>|Collection> $rows + * + * @phpstan-param ($rows is null ? list>|Collection> : list>|Collection>) $headers */ -function table(array|Collection $headers, array|Collection $rows): void +function table(array|Collection $headers = [], array|Collection $rows = null): void { - $headers = $headers instanceof Collection ? $headers->all() : $headers; - $rows = $rows instanceof Collection ? $rows->all() : $rows; - (new Table($headers, $rows))->display(); } diff --git a/tests/Feature/TableTest.php b/tests/Feature/TableTest.php index a52060b8..121c6a98 100644 --- a/tests/Feature/TableTest.php +++ b/tests/Feature/TableTest.php @@ -58,7 +58,7 @@ it('renders a table without headers', function ($rows) { Prompt::fake(); - table([], $rows); + table($rows); Prompt::assertStrippedOutputContains(<<<'OUTPUT' ┌────────────────────┬──────────────────┐ From 689e0d636cd051088461fb9fae5bbc6198857f30 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Thu, 14 Sep 2023 17:51:10 +1000 Subject: [PATCH 8/9] Add `table` playground --- playground/table.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 playground/table.php diff --git a/playground/table.php b/playground/table.php new file mode 100644 index 00000000..a9f5fa79 --- /dev/null +++ b/playground/table.php @@ -0,0 +1,20 @@ + Date: Thu, 14 Sep 2023 17:53:21 +1000 Subject: [PATCH 9/9] Remove unnecessary type --- src/helpers.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers.php b/src/helpers.php index 221a4cf5..04d4010a 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -145,8 +145,6 @@ function outro(string $message): void * * @param array>|Collection> $headers * @param array>|Collection> $rows - * - * @phpstan-param ($rows is null ? list>|Collection> : list>|Collection>) $headers */ function table(array|Collection $headers = [], array|Collection $rows = null): void {