From 22cd2c400795558002891f2aa09bfe37a862d787 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Tue, 26 Mar 2024 19:29:18 +0100 Subject: [PATCH 1/5] feat: writes a key/value set to two columns in a row --- src/Helper/Arr.php | 65 +++++++++++++++++++++++++++++++++++++ src/Output/ProgressBar.php | 2 +- src/Output/Writer.php | 51 +++++++++++++++++++++++++++++ tests/Output/WriterTest.php | 21 ++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/Helper/Arr.php diff --git a/src/Helper/Arr.php b/src/Helper/Arr.php new file mode 100644 index 0000000..5651b3a --- /dev/null +++ b/src/Helper/Arr.php @@ -0,0 +1,65 @@ + + * + * + * Licensed under MIT license. + */ + +namespace Ahc\Cli\Helper; + +use function current; +use function func_get_args; +use function is_array; +use function is_int; +use function next; + +/** + * helper specializing in table manipulation + * + * @author Dimitri Sitchet Tomkeu + * @license MIT + * + * @link https://github.com/adhocore/cli + */ +class Arr +{ + /** + * This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`. + * + * The difference between this method and the built-in ones, is that if an array key contains another array, then + * Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for + * keys that contain scalar values (unlike `array_merge_recursive`). + * + * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays. + * + * @param array $data Array to be merged + * @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged + * + * @return array Merged array + * + * @credit CakePHP - http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge + */ + public static function merge(array $data, $merge) + { + $args = func_get_args(); + $return = current($args); + + while (($arg = next($args)) !== false) { + foreach ((array) $arg as $key => $val) { + if (! empty($return[$key]) && is_array($return[$key]) && is_array($val)) { + $return[$key] = self::merge($return[$key], $val); + } elseif (is_int($key) && isset($return[$key])) { + $return[] = $val; + } else { + $return[$key] = $val; + } + } + } + + return $return; + } +} diff --git a/src/Output/ProgressBar.php b/src/Output/ProgressBar.php index ec96069..77ed9aa 100644 --- a/src/Output/ProgressBar.php +++ b/src/Output/ProgressBar.php @@ -96,7 +96,7 @@ public function __construct(?int $total = null, ?Writer $writer = null) $this->writer = $writer ?: new Writer(); $this->cursor = $this->writer->cursor(); - $this->terminal = new Terminal(); + $this->terminal = $this->writer->terminal(); } /** diff --git a/src/Output/Writer.php b/src/Output/Writer.php index f9bf709..fc6cd3c 100644 --- a/src/Output/Writer.php +++ b/src/Output/Writer.php @@ -11,12 +11,16 @@ namespace Ahc\Cli\Output; +use Ahc\Cli\Helper\Arr; +use Ahc\Cli\Helper\Terminal; + use function fopen; use function fwrite; use function max; use function method_exists; use function str_repeat; use function stripos; +use function strlen; use function strpos; use function ucfirst; @@ -183,6 +187,8 @@ class Writer protected Cursor $cursor; + protected Terminal $terminal; + public function __construct(string $path = null, Color $colorizer = null) { if ($path) { @@ -194,6 +200,7 @@ public function __construct(string $path = null, Color $colorizer = null) $this->cursor = new Cursor; $this->colorizer = $colorizer ?? new Color; + $this->terminal = new Terminal(); } /** @@ -212,6 +219,14 @@ public function cursor(): Cursor return $this->cursor; } + /** + * Get Terminal. + */ + public function terminal(): Terminal + { + return $this->terminal; + } + /** * Magically set methods. * @@ -288,6 +303,42 @@ public function table(array $rows, array $styles = []): self return $this->colors($table); } + /** + * writes a key/value set to two columns in a row + * + * @example PHP Version ............................................................. 8.1.4 + * + * @param string $first The text to write in left side + * @param string|null $second The text to write in right side + * @param array $options Options to use when writing Eg: ['fg' => Color::GREEB, 'bold' => 1, 'sep' => '-'] + * + * @return self + */ + public function twoColumnDetail(string $first, ?string $second = null, array $options = []): self + { + $options = Arr::merge([ + 'first' => ['bg' => null, 'fg' => Color::WHITE, 'bold' => 0], + 'second' => ['bg' => null, 'fg' => Color::WHITE, 'bold' => 1], + 'sep' => '.', + ], $options); + + $second = (string) $second; + + $dashWidth = $this->terminal->width() - (strlen($first) + strlen($second)); + // remove left and right margins because we're going to add 1 space on each side (after/before the text). + // if we don't have a second element, we just remove the left margin + $dashWidth -= $second === '' ? 1 : 2; + + $first = $this->colorizer->line($first, $options['first']); + if ($second !== '') { + $second = $this->colorizer->line($second, $options['second']); + } + + $this->write($first . ' ' . str_repeat((string) $options['sep'], $dashWidth) . ' ' . $second); + + return $this->eol(); + } + /** * Write to stdout or stderr magically. * diff --git a/tests/Output/WriterTest.php b/tests/Output/WriterTest.php index c618e95..1973cfc 100644 --- a/tests/Output/WriterTest.php +++ b/tests/Output/WriterTest.php @@ -11,6 +11,7 @@ namespace Ahc\Cli\Test\Output; +use Ahc\Cli\Helper\Terminal; use Ahc\Cli\Output\Color; use Ahc\Cli\Output\Writer; use Ahc\Cli\Test\CliTestCase; @@ -111,4 +112,24 @@ public function test_colorizer() { $this->assertInstanceOf(Color::class, (new Writer)->colorizer()); } + + public function test_terminal() + { + $this->assertInstanceOf(Terminal::class, (new Writer)->terminal()); + } + + public function test_two_column_detail() + { + $w = new Writer(static::$ou); + + $w->twoColumnDetail('PHP Version', PHP_VERSION, [ + 'sep' => '-' + ]); + + $buffer = trim($this->buffer()); + + $this->assertStringContainsString("PHP Version", $buffer); + $this->assertStringContainsString('---', $buffer); + $this->assertStringContainsString(PHP_VERSION, $buffer); + } } From 2fdabc15cb6e75dfef31a2cae71c41f3c619867b Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Tue, 26 Mar 2024 20:22:53 +0100 Subject: [PATCH 2/5] update readme to add docs of twoColumnDetail method --- README.md | 57 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8bf09ea..9884066 100644 --- a/README.md +++ b/README.md @@ -583,36 +583,55 @@ $writer->table([ ]); ``` +#### Two columns detail (Display setting) + +If you want to display certain configurations (from your .env file for example) a bit like Laravel does (via the php artisan `about` command) you can use the `twoColumnDetail` method. + +```php +$writer->twoColumnDetail('Environment'); +$writer->twoColumnDetail('PHP Version', PHP_VERSION); +$writer->twoColumnDetail('App Version', '1.0.0'); +$writer->twoColumnDetail('Locale', 'en'); +``` + Gives something like: ``` -+--------+------+------+ -| A | B C | C D | -+--------+------+------+ -| apple | ball | cat | -| applet | bee | cute | -+--------+------+------+ +Environment ........................................ +PHP Version .................................. 8.1.4 +App Version .................................. 1.0.0 +Locale .......................................... en ``` -> Designing table look and feel +You can use the `sep` parameter to define the separator to use. -Just pass 2nd param `$styles`: +```php +$writer->twoColumnDetail('Environment', '', ['sep' => '-']); +$writer->twoColumnDetail('PHP Version', PHP_VERSION); +``` + +Gives something like: + +``` +Environment ---------------------------------------- +PHP Version .................................. 8.1.4 +``` + +In addition, the text color, the background color and the thickness of the two texts can be defined via the 3rd argument of this method. ```php -$writer->table([ - ['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'], - ['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'], -], [ - // for => styleName (anything that you would call in $writer instance) - 'head' => 'boldGreen', // For the table heading - 'odd' => 'bold', // For the odd rows (1st row is odd, then 3, 5 etc) - 'even' => 'comment', // For the even rows (2nd row is even, then 4, 6 etc) +$writer->twoColumnDetail('Cache Enable', 'true', [ + 'first' => ['fg' => Ahc\Cli\Output\Color::CYAN], // style of the key + 'second' => ['fg' => Ahc\Cli\Output\Color::GREEN], // style of the value +]); +$writer->twoColumnDetail('Debug Mode', 'false', [ + 'first' => ['fg' => Ahc\Cli\Output\Color::CYAN], // style of the key + 'second' => ['fg' => Ahc\Cli\Output\Color::RED], // style of the value ]); - -// 'head', 'odd', 'even' are all the styles for now -// In future we may support styling a column by its name! ``` +For more details regarding the different color options, see [Custom style](#custom-style) + #### Reader Read and pre process user input. From 8bb6697adc4b4c6bc0516e1564218c61af428fd1 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 27 Mar 2024 10:22:06 +0100 Subject: [PATCH 3/5] patch: restore existing docs --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9884066..b5270df 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ class OtherCommand extends Ahc\Cli\Input\Command { $io = $this->app()->io(); - $io->write('Other command'); + $io->write('Other command'); // more codes ... @@ -583,6 +583,36 @@ $writer->table([ ]); ``` +Gives something like: + +``` ++--------+------+------+ +| A | B C | C D | ++--------+------+------+ +| apple | ball | cat | +| applet | bee | cute | ++--------+------+------+ +``` + +> Designing table look and feel + +Just pass 2nd param `$styles`: + +```php +$writer->table([ + ['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'], + ['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'], +], [ + // for => styleName (anything that you would call in $writer instance) + 'head' => 'boldGreen', // For the table heading + 'odd' => 'bold', // For the odd rows (1st row is odd, then 3, 5 etc) + 'even' => 'comment', // For the even rows (2nd row is even, then 4, 6 etc) +]); + +// 'head', 'odd', 'even' are all the styles for now +// In future we may support styling a column by its name! +``` + #### Two columns detail (Display setting) If you want to display certain configurations (from your .env file for example) a bit like Laravel does (via the php artisan `about` command) you can use the `twoColumnDetail` method. From 24f2e22110b3f4c7210f3c196d069a6cd45fe2c3 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 27 Mar 2024 10:45:50 +0100 Subject: [PATCH 4/5] patch: remove unecessary helper --- src/Helper/Arr.php | 65 ------------------------------------------- src/Output/Writer.php | 13 ++++----- 2 files changed, 6 insertions(+), 72 deletions(-) delete mode 100644 src/Helper/Arr.php diff --git a/src/Helper/Arr.php b/src/Helper/Arr.php deleted file mode 100644 index 5651b3a..0000000 --- a/src/Helper/Arr.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * - * Licensed under MIT license. - */ - -namespace Ahc\Cli\Helper; - -use function current; -use function func_get_args; -use function is_array; -use function is_int; -use function next; - -/** - * helper specializing in table manipulation - * - * @author Dimitri Sitchet Tomkeu - * @license MIT - * - * @link https://github.com/adhocore/cli - */ -class Arr -{ - /** - * This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`. - * - * The difference between this method and the built-in ones, is that if an array key contains another array, then - * Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for - * keys that contain scalar values (unlike `array_merge_recursive`). - * - * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays. - * - * @param array $data Array to be merged - * @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged - * - * @return array Merged array - * - * @credit CakePHP - http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge - */ - public static function merge(array $data, $merge) - { - $args = func_get_args(); - $return = current($args); - - while (($arg = next($args)) !== false) { - foreach ((array) $arg as $key => $val) { - if (! empty($return[$key]) && is_array($return[$key]) && is_array($val)) { - $return[$key] = self::merge($return[$key], $val); - } elseif (is_int($key) && isset($return[$key])) { - $return[] = $val; - } else { - $return[$key] = $val; - } - } - } - - return $return; - } -} diff --git a/src/Output/Writer.php b/src/Output/Writer.php index fc6cd3c..bf7718c 100644 --- a/src/Output/Writer.php +++ b/src/Output/Writer.php @@ -11,7 +11,6 @@ namespace Ahc\Cli\Output; -use Ahc\Cli\Helper\Arr; use Ahc\Cli\Helper\Terminal; use function fopen; @@ -310,17 +309,17 @@ public function table(array $rows, array $styles = []): self * * @param string $first The text to write in left side * @param string|null $second The text to write in right side - * @param array $options Options to use when writing Eg: ['fg' => Color::GREEB, 'bold' => 1, 'sep' => '-'] + * @param array $options Options to use when writing Eg: ['fg' => Color::GREEN, 'bold' => 1, 'sep' => '-'] * * @return self */ public function twoColumnDetail(string $first, ?string $second = null, array $options = []): self { - $options = Arr::merge([ - 'first' => ['bg' => null, 'fg' => Color::WHITE, 'bold' => 0], - 'second' => ['bg' => null, 'fg' => Color::WHITE, 'bold' => 1], - 'sep' => '.', - ], $options); + $options = [ + 'first' => ($options['first'] ?? []) + ['bg' => null, 'fg' => Color::WHITE, 'bold' => 0], + 'second' => ($options['second'] ?? []) + ['bg' => null, 'fg' => Color::WHITE, 'bold' => 1], + 'sep' => $options['sep'] ?? '.', + ]; $second = (string) $second; From 6fa0191c8e7691bb468a422742c7703e64885002 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 27 Mar 2024 10:52:32 +0100 Subject: [PATCH 5/5] refactor: change twoColumnsDetail to justify --- README.md | 20 ++++++++++---------- src/Output/Writer.php | 2 +- tests/Output/WriterTest.php | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b5270df..a8ec82b 100644 --- a/README.md +++ b/README.md @@ -613,15 +613,15 @@ $writer->table([ // In future we may support styling a column by its name! ``` -#### Two columns detail (Display setting) +#### Justify content (Display setting) -If you want to display certain configurations (from your .env file for example) a bit like Laravel does (via the php artisan `about` command) you can use the `twoColumnDetail` method. +If you want to display certain configurations (from your .env file for example) a bit like Laravel does (via the `php artisan about` command) you can use the `justify` method. ```php -$writer->twoColumnDetail('Environment'); -$writer->twoColumnDetail('PHP Version', PHP_VERSION); -$writer->twoColumnDetail('App Version', '1.0.0'); -$writer->twoColumnDetail('Locale', 'en'); +$writer->justify('Environment'); +$writer->justify('PHP Version', PHP_VERSION); +$writer->justify('App Version', '1.0.0'); +$writer->justify('Locale', 'en'); ``` Gives something like: @@ -636,8 +636,8 @@ Locale .......................................... en You can use the `sep` parameter to define the separator to use. ```php -$writer->twoColumnDetail('Environment', '', ['sep' => '-']); -$writer->twoColumnDetail('PHP Version', PHP_VERSION); +$writer->justify('Environment', '', ['sep' => '-']); +$writer->justify('PHP Version', PHP_VERSION); ``` Gives something like: @@ -650,11 +650,11 @@ PHP Version .................................. 8.1.4 In addition, the text color, the background color and the thickness of the two texts can be defined via the 3rd argument of this method. ```php -$writer->twoColumnDetail('Cache Enable', 'true', [ +$writer->justify('Cache Enable', 'true', [ 'first' => ['fg' => Ahc\Cli\Output\Color::CYAN], // style of the key 'second' => ['fg' => Ahc\Cli\Output\Color::GREEN], // style of the value ]); -$writer->twoColumnDetail('Debug Mode', 'false', [ +$writer->justify('Debug Mode', 'false', [ 'first' => ['fg' => Ahc\Cli\Output\Color::CYAN], // style of the key 'second' => ['fg' => Ahc\Cli\Output\Color::RED], // style of the value ]); diff --git a/src/Output/Writer.php b/src/Output/Writer.php index bf7718c..83346be 100644 --- a/src/Output/Writer.php +++ b/src/Output/Writer.php @@ -313,7 +313,7 @@ public function table(array $rows, array $styles = []): self * * @return self */ - public function twoColumnDetail(string $first, ?string $second = null, array $options = []): self + public function justify(string $first, ?string $second = null, array $options = []): self { $options = [ 'first' => ($options['first'] ?? []) + ['bg' => null, 'fg' => Color::WHITE, 'bold' => 0], diff --git a/tests/Output/WriterTest.php b/tests/Output/WriterTest.php index 1973cfc..f2c9950 100644 --- a/tests/Output/WriterTest.php +++ b/tests/Output/WriterTest.php @@ -118,11 +118,11 @@ public function test_terminal() $this->assertInstanceOf(Terminal::class, (new Writer)->terminal()); } - public function test_two_column_detail() + public function test_justify() { $w = new Writer(static::$ou); - $w->twoColumnDetail('PHP Version', PHP_VERSION, [ + $w->justify('PHP Version', PHP_VERSION, [ 'sep' => '-' ]);