Skip to content

Commit 91c17c9

Browse files
authored
Merge pull request #5 from adhocore/develop
Develop
2 parents 858dee8 + 2dbc719 commit 91c17c9

File tree

9 files changed

+213
-88
lines changed

9 files changed

+213
-88
lines changed

src/ArgvParser.php

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@ class ArgvParser extends Parser
3636
* @param string $desc
3737
* @param bool $allowUnknown
3838
*/
39-
public function __construct(string $name, string $desc = null, bool $allowUnknown = false)
39+
public function __construct(string $name, string $desc = '', bool $allowUnknown = false)
4040
{
4141
$this->_name = $name;
4242
$this->_desc = $desc;
4343
$this->_allowUnknown = $allowUnknown;
4444

4545
$this->option('-h, --help', 'Show help')->on([$this, 'showHelp']);
4646
$this->option('-V, --version', 'Show version')->on([$this, 'showVersion']);
47+
48+
$this->onExit(function () {
49+
exit(0);
50+
});
4751
}
4852

4953
/**
@@ -60,6 +64,16 @@ public function version(string $version): self
6064
return $this;
6165
}
6266

67+
public function getName(): string
68+
{
69+
return $this->_name;
70+
}
71+
72+
public function getDesc(): string
73+
{
74+
return $this->_desc;
75+
}
76+
6377
/**
6478
* Registers argument definitions (all at once). Only last one can be variadic.
6579
*
@@ -137,6 +151,20 @@ public function on(callable $fn): self
137151
return $this;
138152
}
139153

154+
/**
155+
* Register exit handler.
156+
*
157+
* @param callable $fn
158+
*
159+
* @return self
160+
*/
161+
public function onExit(callable $fn): self
162+
{
163+
$this->_events['_exit'] = $fn;
164+
165+
return $this;
166+
}
167+
140168
protected function handleUnknown(string $arg, string $value = null)
141169
{
142170
if ($this->_allowUnknown) {
@@ -197,38 +225,68 @@ public function args(): array
197225

198226
protected function showHelp()
199227
{
200-
echo "{$this->_name}, version {$this->_version}" . PHP_EOL;
228+
$args = $this->_arguments ? ' [ARGUMENTS]' : '';
229+
$opts = $this->_options ? ' [OPTIONS]' : '';
230+
231+
($w = new Writer)
232+
->bold("Command {$this->_name}, version {$this->_version}", true)->eol()
233+
->comment($this->_desc, true)->eol()
234+
->bold('Usage: ')->yellow("{$this->_name}{$args}{$opts}", true);
235+
236+
if ($args) {
237+
$this->showArguments($w);
238+
}
239+
240+
if ($opts) {
241+
$this->showOptions($w);
242+
}
243+
244+
$w->eol()->yellow('Note: <required> [optional]')->eol();
245+
246+
return $this->emit('_exit');
247+
}
248+
249+
protected function showArguments(Writer $w)
250+
{
251+
$w->eol()->boldGreen('Arguments:', true);
252+
253+
$maxLen = \max(\array_map('strlen', \array_keys($this->_arguments)));
254+
255+
foreach ($this->_arguments as $arg) {
256+
$name = $arg->name();
257+
$name = $arg->required() ? "<$name>" : "[$name]";
258+
$w->bold(' ' . \str_pad($name, $maxLen + 4))->comment($arg->desc(), true);
259+
}
260+
}
261+
262+
protected function showOptions(Writer $w)
263+
{
264+
$w->eol()->boldGreen('Options:', true);
201265

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

205-
_exit();
268+
foreach ($this->_options as $opt) {
269+
$name = $opt->short() . '|' . $opt->long();
270+
$name = $opt->required() ? "<$name>" : "[$name]";
271+
$w->bold(' ' . \str_pad($name, $maxLen + 9))->comment($opt->desc(), true);
272+
}
206273
}
207274

208275
protected function showVersion()
209276
{
210-
echo $this->_version . PHP_EOL;
277+
(new Writer)->bold($this->_version, true);
211278

212-
_exit();
279+
return $this->emit('_exit');
213280
}
214281

215-
protected function emit(string $event)
282+
public function emit(string $event)
216283
{
217284
if (empty($this->_events[$event])) {
218285
return;
219286
}
220287

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

223-
$callback();
224-
}
225-
}
226-
227-
// @codeCoverageIgnoreStart
228-
if (!\function_exists(__NAMESPACE__ . '\\_exit')) {
229-
function _exit($code = 0)
230-
{
231-
exit($code);
290+
return $callback();
232291
}
233292
}
234-
// @codeCoverageIgnoreEnd

src/Option.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Option extends Parameter
1818

1919
protected $filter;
2020

21-
public function __construct(string $raw, string $desc = null, $default = null, callable $filter = null)
21+
public function __construct(string $raw, string $desc = '', $default = null, callable $filter = null)
2222
{
2323
$this->filter = $filter;
2424

src/Parameter.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ abstract class Parameter
2828

2929
protected $variadic = false;
3030

31-
public function __construct(string $raw, string $desc = null, $default = null)
31+
public function __construct(string $raw, string $desc = '', $default = null)
3232
{
3333
$this->raw = $raw;
3434
$this->desc = $desc;
@@ -52,6 +52,11 @@ public function name(): string
5252
return $this->name;
5353
}
5454

55+
public function desc(): string
56+
{
57+
return $this->desc;
58+
}
59+
5560
public function attributeName(): string
5661
{
5762
return $this->toCamelCase($this->name);

src/Writer.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,35 @@ public function __get(string $name): self
4545
* @param string $text
4646
* @param bool $eol
4747
*
48-
* @return void
48+
* @return self
4949
*/
50-
public function write(string $text, bool $eol = false)
50+
public function write(string $text, bool $eol = false): self
5151
{
5252
list($method, $this->method) = [$this->method ?: 'line', ''];
5353

5454
$stream = \stripos($method, 'error') !== false ? \STDERR : \STDOUT;
5555

56-
\fwrite($stream, $this->colorizer->{$method}($text, [], $eol));
56+
if ($method === 'eol') {
57+
\fwrite($stream, PHP_EOL);
58+
} else {
59+
\fwrite($stream, $this->colorizer->{$method}($text, [], $eol));
60+
}
61+
62+
return $this;
63+
}
64+
65+
/**
66+
* Write to stdout or stderr magically.
67+
*
68+
* @param string $method
69+
* @param array $arguments
70+
*
71+
* @return self
72+
*/
73+
public function __call(string $method, array $arguments): self
74+
{
75+
$this->method = $method;
76+
77+
return $this->write($arguments[0] ?? '', $arguments[1] ?? false);
5778
}
5879
}

tests/ArgvParserTest.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,6 @@ public function test_options_unknown()
9797
$p = $this->newParser('', '', true)->parse(['php', '--hot-path', '/path']);
9898
$this->assertSame('/path', $p->hotPath, 'Allow unknown');
9999

100-
ob_start();
101-
$p = $this->newParser()->parse(['php', '--unknown', '1']);
102-
$this->assertContains('help', ob_get_clean(), 'Show help');
103-
104100
$this->expectException(\RuntimeException::class);
105101
$this->expectExceptionMessage('Option "--random" not registered');
106102

@@ -180,22 +176,6 @@ public function test_event()
180176
$this->assertSame('hello event', ob_get_clean());
181177
}
182178

183-
public function test_default_options()
184-
{
185-
ob_start();
186-
$p = $this->newParser('v1.0.1')->parse(['php', '--version']);
187-
$this->assertContains('v1.0.1', ob_get_clean(), 'Long');
188-
189-
ob_start();
190-
$p = $this->newParser('v2.0.1')->parse(['php', '-V']);
191-
$this->assertContains('v2.0.1', ob_get_clean(), 'Short');
192-
193-
ob_start();
194-
$p = $this->newParser()->parse(['php', '--help']);
195-
$this->assertContains('ArgvParserTest', $buffer = ob_get_clean());
196-
$this->assertContains('help', $buffer);
197-
}
198-
199179
public function test_no_value()
200180
{
201181
$p = $this->newParser()->option('-x --xyz')->parse(['php', '-x']);
@@ -211,7 +191,7 @@ public function test_args()
211191
$this->assertSame(['a' => 'A', 'b' => 'B', 'C', 'D'], $p->args());
212192
}
213193

214-
protected function newParser(string $version = '0.0.1', string $desc = null, bool $allowUnknown = false)
194+
protected function newParser(string $version = '0.0.1', string $desc = '', bool $allowUnknown = false)
215195
{
216196
$p = new ArgvParser('ArgvParserTest', $desc, $allowUnknown);
217197

tests/CliTestCase.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Ahc\Cli\Test;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
/**
8+
* To test console output.
9+
*/
10+
class CliTestCase extends TestCase
11+
{
12+
public static function setUpBeforeClass()
13+
{
14+
// Thanks: https://stackoverflow.com/a/39785995
15+
stream_filter_register('intercept', StreamInterceptor::class);
16+
stream_filter_append(\STDOUT, 'intercept');
17+
stream_filter_append(\STDERR, 'intercept');
18+
}
19+
20+
public function setUp()
21+
{
22+
ob_start();
23+
StreamInterceptor::$buffer = '';
24+
}
25+
26+
public function tearDown()
27+
{
28+
ob_end_clean();
29+
}
30+
31+
public function buffer()
32+
{
33+
return StreamInterceptor::$buffer;
34+
}
35+
}
36+
37+
class StreamInterceptor extends \php_user_filter
38+
{
39+
public static $buffer = '';
40+
41+
public function filter($in, $out, &$consumed, $closing)
42+
{
43+
while ($bucket = stream_bucket_make_writeable($in)) {
44+
static::$buffer .= $bucket->data;
45+
}
46+
47+
return PSFS_PASS_ON;
48+
}
49+
}

tests/DefaultOptionTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Ahc\Cli\Test;
4+
5+
use Ahc\Cli\ArgvParser;
6+
7+
class DefaultOptionTest extends CliTestCase
8+
{
9+
public function test_version()
10+
{
11+
$p = $this->newParser('v1.0.1')->parse(['php', '--version']);
12+
$this->assertContains('v1.0.1', $this->buffer(), 'Long');
13+
}
14+
15+
public function test_V()
16+
{
17+
$p = $this->newParser('v2.0.1')->parse(['php', '-V']);
18+
$this->assertContains('v2.0.1', $this->buffer(), 'Short');
19+
}
20+
21+
public function test_help()
22+
{
23+
$p = $this->newParser()
24+
->arguments('[arg]')
25+
->option('-o --option')
26+
->parse(['php', '--help']);
27+
28+
$this->assertContains('ArgvParserTest', $buffer = $this->buffer());
29+
$this->assertContains('[arg]', $buffer);
30+
$this->assertContains('[-o|--option]', $buffer);
31+
}
32+
33+
public function test_help_unknown()
34+
{
35+
$p = $this->newParser()->arguments('[apple]')->parse(['php', '--unknown', '1']);
36+
$this->assertContains('[apple]', $this->buffer(), 'Show help');
37+
}
38+
39+
protected function newParser(string $version = '0.0.1', string $desc = '', bool $allowUnknown = false)
40+
{
41+
$p = new ArgvParser('ArgvParserTest', $desc, $allowUnknown);
42+
43+
return $p->version($version)->onExit(function () {
44+
return false;
45+
});
46+
}
47+
}

0 commit comments

Comments
 (0)