From 775d36ce1b17b02f9f3d4505dbe5c0bcd2a0bb24 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 18:38:57 +0700 Subject: [PATCH 01/13] feat: add getters for name/desc --- src/ArgvParser.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ArgvParser.php b/src/ArgvParser.php index f6c2837..439a3a9 100644 --- a/src/ArgvParser.php +++ b/src/ArgvParser.php @@ -36,7 +36,7 @@ class ArgvParser extends Parser * @param string $desc * @param bool $allowUnknown */ - public function __construct(string $name, string $desc = null, bool $allowUnknown = false) + public function __construct(string $name, string $desc = '', bool $allowUnknown = false) { $this->_name = $name; $this->_desc = $desc; @@ -60,6 +60,16 @@ public function version(string $version): self return $this; } + public function getName(): string + { + return $this->_name; + } + + public function getDesc(): string + { + return $this->_desc; + } + /** * Registers argument definitions (all at once). Only last one can be variadic. * From b8292ef509031ca46b863a0cc6d9c30981733844 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 18:40:54 +0700 Subject: [PATCH 02/13] feat: writer now supports magic calls and eol() --- src/Writer.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Writer.php b/src/Writer.php index d1ceebc..c43e9b3 100644 --- a/src/Writer.php +++ b/src/Writer.php @@ -45,14 +45,35 @@ public function __get(string $name): self * @param string $text * @param bool $eol * - * @return void + * @return self */ - public function write(string $text, bool $eol = false) + public function write(string $text, bool $eol = false): self { list($method, $this->method) = [$this->method ?: 'line', '']; $stream = \stripos($method, 'error') !== false ? \STDERR : \STDOUT; - \fwrite($stream, $this->colorizer->{$method}($text, [], $eol)); + if ($method === 'eol') { + \fwrite($stream, PHP_EOL); + } else { + \fwrite($stream, $this->colorizer->{$method}($text, [], $eol)); + } + + return $this; + } + + /** + * Write to stdout or stderr magically. + * + * @param string $method + * @param array $arguments + * + * @return self + */ + public function __call(string $method, array $arguments): self + { + $this->method = $method; + + return $this->write($arguments[0] ?? '', $arguments[1] ?? false); } } From c629b1dd17143698b7c74e5cf1324eb9a1dfe19f Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 19:49:26 +0700 Subject: [PATCH 03/13] feat: add desc(), ensure desc is always string --- src/Parameter.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Parameter.php b/src/Parameter.php index e57406f..2e91d64 100644 --- a/src/Parameter.php +++ b/src/Parameter.php @@ -28,7 +28,7 @@ abstract class Parameter protected $variadic = false; - public function __construct(string $raw, string $desc = null, $default = null) + public function __construct(string $raw, string $desc = '', $default = null) { $this->raw = $raw; $this->desc = $desc; @@ -52,6 +52,11 @@ public function name(): string return $this->name; } + public function desc(): string + { + return $this->desc; + } + public function attributeName(): string { return $this->toCamelCase($this->name); From 5585dfb721e2d97d1993f9c7f8582c2b717d6e99 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 19:50:02 +0700 Subject: [PATCH 04/13] fix: ensure desc is always string --- src/Option.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Option.php b/src/Option.php index e6a9877..6a293a3 100644 --- a/src/Option.php +++ b/src/Option.php @@ -18,7 +18,7 @@ class Option extends Parameter protected $filter; - public function __construct(string $raw, string $desc = null, $default = null, callable $filter = null) + public function __construct(string $raw, string $desc = '', $default = null, callable $filter = null) { $this->filter = $filter; From ffdc5c7ef6c98330d0c6d72db119bcf186e3fa0b Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 19:52:39 +0700 Subject: [PATCH 05/13] feat: add return in _exit(), add help info --- src/ArgvParser.php | 51 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/ArgvParser.php b/src/ArgvParser.php index 439a3a9..bdc71d6 100644 --- a/src/ArgvParser.php +++ b/src/ArgvParser.php @@ -207,19 +207,58 @@ public function args(): array protected function showHelp() { - echo "{$this->_name}, version {$this->_version}" . PHP_EOL; + $args = $this->_arguments ? ' [ARGUMENTS]' : ''; + $opts = $this->_options ? ' [OPTIONS]' : ''; - // @todo: build help msg! - echo "help\n"; + ($w = new Writer) + ->bold("Command {$this->_name}, version {$this->_version}", true)->eol() + ->comment($this->_desc, true)->eol() + ->bold('Usage: ')->yellow("{$this->_name}{$args}{$opts}", true); - _exit(); + if ($args) { + $this->showArguments($w); + } + + if ($opts) { + $this->showOptions($w); + } + + $w->eol()->yellow('Note: [optional]')->eol(); + + return _exit(); + } + + protected function showArguments(Writer $w) + { + $w->eol()->boldGreen('Arguments:', true); + + $maxLen = \max(\array_map('strlen', \array_keys($this->_arguments))); + + foreach ($this->_arguments as $arg) { + $name = $arg->name(); + $name = $arg->required() ? "<$name>" : "[$name]"; + $w->bold(' ' . \str_pad($name, $maxLen + 4))->comment($arg->desc(), true); + } + } + + protected function showOptions(Writer $w) + { + $w->eol()->boldGreen('Options:', true); + + $maxLen = \max(\array_map('strlen', \array_keys($this->_options))); + + foreach ($this->_options as $opt) { + $name = $opt->short() . '|' . $opt->long(); + $name = $opt->required() ? "<$name>" : "[$name]"; + $w->bold(' ' . \str_pad($name, $maxLen + 9))->comment($opt->desc(), true); + } } protected function showVersion() { echo $this->_version . PHP_EOL; - _exit(); + return _exit(); } protected function emit(string $event) @@ -239,6 +278,8 @@ protected function emit(string $event) function _exit($code = 0) { exit($code); + + return false; } } // @codeCoverageIgnoreEnd From cb7d2b49c0fcc827cdce945925a40e6c6adc4054 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 20:29:52 +0700 Subject: [PATCH 06/13] fix: show version using writer --- src/ArgvParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArgvParser.php b/src/ArgvParser.php index bdc71d6..59cea98 100644 --- a/src/ArgvParser.php +++ b/src/ArgvParser.php @@ -256,7 +256,7 @@ protected function showOptions(Writer $w) protected function showVersion() { - echo $this->_version . PHP_EOL; + (new Writer)->bold($this->_version, true); return _exit(); } From eb72d27005c96f2d8788d429258404427d1c6639 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 20:30:26 +0700 Subject: [PATCH 07/13] test: add cli test case for std output tests --- tests/CliTestCase.php | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/CliTestCase.php diff --git a/tests/CliTestCase.php b/tests/CliTestCase.php new file mode 100644 index 0000000..c5ec171 --- /dev/null +++ b/tests/CliTestCase.php @@ -0,0 +1,50 @@ +data; + } + + return PSFS_PASS_ON; + } +} + From 612d60113b6e6a5b93cefd427cc43eaa8420bc6b Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 20:30:56 +0700 Subject: [PATCH 08/13] test: default option test (help/version) --- tests/DefaultOptionTest.php | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/DefaultOptionTest.php diff --git a/tests/DefaultOptionTest.php b/tests/DefaultOptionTest.php new file mode 100644 index 0000000..db7c540 --- /dev/null +++ b/tests/DefaultOptionTest.php @@ -0,0 +1,45 @@ +newParser('v1.0.1')->parse(['php', '--version']); + $this->assertContains('v1.0.1', $this->buffer(), 'Long'); + } + + public function test_V() + { + $p = $this->newParser('v2.0.1')->parse(['php', '-V']); + $this->assertContains('v2.0.1', $this->buffer(), 'Short'); + } + + public function test_help() + { + $p = $this->newParser() + ->arguments('[arg]') + ->option('-o --option') + ->parse(['php', '--help']); + + $this->assertContains('ArgvParserTest', $buffer = $this->buffer()); + $this->assertContains('[arg]', $buffer); + $this->assertContains('[-o|--option]', $buffer); + } + + public function test_help_unknown() + { + $p = $this->newParser()->arguments('[apple]')->parse(['php', '--unknown', '1']); + $this->assertContains('[apple]', $this->buffer(), 'Show help'); + } + + protected function newParser(string $version = '0.0.1', string $desc = '', bool $allowUnknown = false) + { + $p = new ArgvParser('ArgvParserTest', $desc, $allowUnknown); + + return $p->version($version); + } +} From cadb81f6ced7cff137ef14e57617e555af5129f6 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 20:31:19 +0700 Subject: [PATCH 09/13] test: extract std output tests --- tests/ArgvParserTest.php | 22 +------------------- tests/WriterTest.php | 45 ++++++++-------------------------------- 2 files changed, 10 insertions(+), 57 deletions(-) diff --git a/tests/ArgvParserTest.php b/tests/ArgvParserTest.php index 2346cad..978088e 100644 --- a/tests/ArgvParserTest.php +++ b/tests/ArgvParserTest.php @@ -97,10 +97,6 @@ public function test_options_unknown() $p = $this->newParser('', '', true)->parse(['php', '--hot-path', '/path']); $this->assertSame('/path', $p->hotPath, 'Allow unknown'); - ob_start(); - $p = $this->newParser()->parse(['php', '--unknown', '1']); - $this->assertContains('help', ob_get_clean(), 'Show help'); - $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Option "--random" not registered'); @@ -180,22 +176,6 @@ public function test_event() $this->assertSame('hello event', ob_get_clean()); } - public function test_default_options() - { - ob_start(); - $p = $this->newParser('v1.0.1')->parse(['php', '--version']); - $this->assertContains('v1.0.1', ob_get_clean(), 'Long'); - - ob_start(); - $p = $this->newParser('v2.0.1')->parse(['php', '-V']); - $this->assertContains('v2.0.1', ob_get_clean(), 'Short'); - - ob_start(); - $p = $this->newParser()->parse(['php', '--help']); - $this->assertContains('ArgvParserTest', $buffer = ob_get_clean()); - $this->assertContains('help', $buffer); - } - public function test_no_value() { $p = $this->newParser()->option('-x --xyz')->parse(['php', '-x']); @@ -211,7 +191,7 @@ public function test_args() $this->assertSame(['a' => 'A', 'b' => 'B', 'C', 'D'], $p->args()); } - protected function newParser(string $version = '0.0.1', string $desc = null, bool $allowUnknown = false) + protected function newParser(string $version = '0.0.1', string $desc = '', bool $allowUnknown = false) { $p = new ArgvParser('ArgvParserTest', $desc, $allowUnknown); diff --git a/tests/WriterTest.php b/tests/WriterTest.php index 9116b10..eafd153 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -5,64 +5,37 @@ use Ahc\Cli\Writer; use PHPUnit\Framework\TestCase; -class WriterTest extends TestCase +class WriterTest extends CliTestCase { - public static function setUpBeforeClass() - { - // Thanks: https://stackoverflow.com/a/39785995 - stream_filter_register('intercept', StreamInterceptor::class); - stream_filter_append(\STDOUT, 'intercept'); - stream_filter_append(\STDERR, 'intercept'); - } - - public function setUp() - { - StreamInterceptor::$buffer = ''; - } - public function test_simple_write() { (new Writer)->write('Hey'); - $this->assertContains('Hey', StreamInterceptor::$buffer); - $this->assertSame("\033[0;37mHey\033[0m", StreamInterceptor::$buffer); + $this->assertContains('Hey', $this->buffer()); + $this->assertSame("\033[0;37mHey\033[0m", $this->buffer()); } public function test_write_error() { (new Writer)->error->write('Something wrong'); - $this->assertContains('Something wrong', StreamInterceptor::$buffer); - $this->assertSame("\033[0;31mSomething wrong\033[0m", StreamInterceptor::$buffer); + $this->assertContains('Something wrong', $this->buffer()); + $this->assertSame("\033[0;31mSomething wrong\033[0m", $this->buffer()); } public function test_write_with_newline() { (new Writer)->write('Hello', true); - $this->assertContains('Hello', StreamInterceptor::$buffer); - $this->assertSame("\033[0;37mHello\033[0m" . PHP_EOL, StreamInterceptor::$buffer); + $this->assertContains('Hello', $this->buffer()); + $this->assertSame("\033[0;37mHello\033[0m" . PHP_EOL, $this->buffer()); } public function test_write_bold_red_bggreen() { (new Writer)->bold->red->bgGreen->write('bold->red->bgGreen'); - $this->assertContains('bold->red->bgGreen', StreamInterceptor::$buffer); - $this->assertSame("\033[1;31;42mbold->red->bgGreen\033[0m", StreamInterceptor::$buffer); - } -} - -class StreamInterceptor extends \php_user_filter -{ - public static $buffer = ''; - - public function filter($in, $out, &$consumed, $closing) - { - while ($bucket = stream_bucket_make_writeable($in)) { - static::$buffer .= $bucket->data; - } - - return PSFS_PASS_ON; + $this->assertContains('bold->red->bgGreen', $this->buffer()); + $this->assertSame("\033[1;31;42mbold->red->bgGreen\033[0m", $this->buffer()); } } From a83352a0e7503cfbad4e314879dde748f40966a9 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 20:41:08 +0700 Subject: [PATCH 10/13] cleanup return --- src/ArgvParser.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ArgvParser.php b/src/ArgvParser.php index 59cea98..3c53756 100644 --- a/src/ArgvParser.php +++ b/src/ArgvParser.php @@ -278,8 +278,6 @@ protected function emit(string $event) function _exit($code = 0) { exit($code); - - return false; } } // @codeCoverageIgnoreEnd From e29faca707a535fb375a61e8edfa943238221503 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 13:44:07 +0000 Subject: [PATCH 11/13] Apply fixes from StyleCI --- tests/CliTestCase.php | 3 +-- tests/WriterTest.php | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/CliTestCase.php b/tests/CliTestCase.php index c5ec171..68b06e4 100644 --- a/tests/CliTestCase.php +++ b/tests/CliTestCase.php @@ -5,7 +5,7 @@ use PHPUnit\Framework\TestCase; /** - * To test console output + * To test console output. */ class CliTestCase extends TestCase { @@ -47,4 +47,3 @@ public function filter($in, $out, &$consumed, $closing) return PSFS_PASS_ON; } } - diff --git a/tests/WriterTest.php b/tests/WriterTest.php index eafd153..dff8594 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -3,7 +3,6 @@ namespace Ahc\Cli\Test; use Ahc\Cli\Writer; -use PHPUnit\Framework\TestCase; class WriterTest extends CliTestCase { From 5e19768c36bb5899a1c024927fad810542ee3a8c Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 21:07:19 +0700 Subject: [PATCH 12/13] feat: add exit handler instead of func override --- src/ArgvParser.php | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ArgvParser.php b/src/ArgvParser.php index 3c53756..a105718 100644 --- a/src/ArgvParser.php +++ b/src/ArgvParser.php @@ -44,6 +44,10 @@ public function __construct(string $name, string $desc = '', bool $allowUnknown $this->option('-h, --help', 'Show help')->on([$this, 'showHelp']); $this->option('-V, --version', 'Show version')->on([$this, 'showVersion']); + + $this->onExit(function () { + exit(0); + }); } /** @@ -147,6 +151,20 @@ public function on(callable $fn): self return $this; } + /** + * Register exit handler. + * + * @param callable $fn + * + * @return self + */ + public function onExit(callable $fn): self + { + $this->_events['_exit'] = $fn; + + return $this; + } + protected function handleUnknown(string $arg, string $value = null) { if ($this->_allowUnknown) { @@ -225,7 +243,7 @@ protected function showHelp() $w->eol()->yellow('Note: [optional]')->eol(); - return _exit(); + return $this->emit('_exit'); } protected function showArguments(Writer $w) @@ -258,10 +276,10 @@ protected function showVersion() { (new Writer)->bold($this->_version, true); - return _exit(); + return $this->emit('_exit'); } - protected function emit(string $event) + public function emit(string $event) { if (empty($this->_events[$event])) { return; @@ -269,15 +287,6 @@ protected function emit(string $event) $callback = $this->_events[$event]; - $callback(); - } -} - -// @codeCoverageIgnoreStart -if (!\function_exists(__NAMESPACE__ . '\\_exit')) { - function _exit($code = 0) - { - exit($code); + return $callback(); } } -// @codeCoverageIgnoreEnd From 2dbc719ea0f63b3d80fc6561e0bda5e6bf35ac33 Mon Sep 17 00:00:00 2001 From: Jitendra Adhikari Date: Sun, 1 Jul 2018 21:07:53 +0700 Subject: [PATCH 13/13] test: use exit handler instead of func override --- tests/DefaultOptionTest.php | 4 +++- tests/bootstrap.php | 7 ------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/DefaultOptionTest.php b/tests/DefaultOptionTest.php index db7c540..663fdea 100644 --- a/tests/DefaultOptionTest.php +++ b/tests/DefaultOptionTest.php @@ -40,6 +40,8 @@ protected function newParser(string $version = '0.0.1', string $desc = '', bool { $p = new ArgvParser('ArgvParserTest', $desc, $allowUnknown); - return $p->version($version); + return $p->version($version)->onExit(function () { + return false; + }); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 385ce9c..6c8c4f5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,10 +1,3 @@