Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 76 additions & 18 deletions src/ArgvParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ 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;
$this->_allowUnknown = $allowUnknown;

$this->option('-h, --help', 'Show help')->on([$this, 'showHelp']);
$this->option('-V, --version', 'Show version')->on([$this, 'showVersion']);

$this->onExit(function () {
exit(0);
});
}

/**
Expand All @@ -60,6 +64,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.
*
Expand Down Expand Up @@ -137,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) {
Expand Down Expand Up @@ -197,38 +225,68 @@ public function args(): array

protected function showHelp()
{
echo "{$this->_name}, version {$this->_version}" . PHP_EOL;
$args = $this->_arguments ? ' [ARGUMENTS]' : '';
$opts = $this->_options ? ' [OPTIONS]' : '';

($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);

if ($args) {
$this->showArguments($w);
}

if ($opts) {
$this->showOptions($w);
}

$w->eol()->yellow('Note: <required> [optional]')->eol();

return $this->emit('_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);

// @todo: build help msg!
echo "help\n";
$maxLen = \max(\array_map('strlen', \array_keys($this->_options)));

_exit();
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;
(new Writer)->bold($this->_version, true);

_exit();
return $this->emit('_exit');
}

protected function emit(string $event)
public function emit(string $event)
{
if (empty($this->_events[$event])) {
return;
}

$callback = $this->_events[$event];

$callback();
}
}

// @codeCoverageIgnoreStart
if (!\function_exists(__NAMESPACE__ . '\\_exit')) {
function _exit($code = 0)
{
exit($code);
return $callback();
}
}
// @codeCoverageIgnoreEnd
2 changes: 1 addition & 1 deletion src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
7 changes: 6 additions & 1 deletion src/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
27 changes: 24 additions & 3 deletions src/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
22 changes: 1 addition & 21 deletions tests/ArgvParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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']);
Expand All @@ -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);

Expand Down
49 changes: 49 additions & 0 deletions tests/CliTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Ahc\Cli\Test;

use PHPUnit\Framework\TestCase;

/**
* To test console output.
*/
class CliTestCase extends TestCase
{
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()
{
ob_start();
StreamInterceptor::$buffer = '';
}

public function tearDown()
{
ob_end_clean();
}

public function buffer()
{
return 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;
}
}
47 changes: 47 additions & 0 deletions tests/DefaultOptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Ahc\Cli\Test;

use Ahc\Cli\ArgvParser;

class DefaultOptionTest extends CliTestCase
{
public function test_version()
{
$p = $this->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)->onExit(function () {
return false;
});
}
}
Loading