Skip to content

Commit 734d84e

Browse files
authored
Merge pull request #19 from adhocore/develop
Develop
2 parents f03e360 + c92f7d8 commit 734d84e

File tree

15 files changed

+947
-199
lines changed

15 files changed

+947
-199
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,14 @@ $app
136136
})
137137
;
138138

139-
// Maybe it could be named `handle()` or `run()`, but again we keep legacy of `commander.js`
139+
// Parse only parses input but doesnt invoke action
140140
$app->parse(['git', 'add', 'path1', 'path2', 'path3', '-f']);
141+
142+
// Handle will do both parse and invoke action.
143+
$app->handle(['git', 'add', 'path1', 'path2', 'path3', '-f']);
141144
// Will produce: Add path1, path2, path3 with force
142145

143-
$app->parse(['git', 'co', '-b', 'master-2', '-f']);
146+
$app->handle(['git', 'co', '-b', 'master-2', '-f']);
144147
// Will produce: Checkout to new master-2 with force
145148
```
146149

src/Application.php

Lines changed: 199 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Ahc\Cli\Helper\OutputHelper;
66
use Ahc\Cli\Input\Command;
7-
use Ahc\Cli\Output\Writer;
7+
use Ahc\Cli\IO\Interactor;
88

99
/**
1010
* A cli application.
@@ -31,45 +31,128 @@ class Application
3131
/** @var string App version */
3232
protected $version = '0.0.1';
3333

34+
/** @var string Ascii art logo */
35+
protected $logo = '';
36+
37+
protected $default = '__default__';
38+
39+
/** @var Interactor */
40+
protected $io;
41+
3442
public function __construct(string $name, string $version = '', callable $onExit = null)
3543
{
3644
$this->name = $name;
3745
$this->version = $version;
3846

3947
// @codeCoverageIgnoreStart
40-
$this->onExit = $onExit ?? function () {
41-
exit(0);
48+
$this->onExit = $onExit ?? function ($exitCode = 0) {
49+
exit($exitCode);
4250
};
4351
// @codeCoverageIgnoreEnd
4452

45-
$this->command = $this->command('__default__', 'Default command', '', true)
46-
->on([$this, 'showHelp'], 'help');
47-
48-
unset($this->commands['__default__']);
53+
$this->command('__default__', 'Default command', '', true)->on([$this, 'showHelp'], 'help');
4954
}
5055

56+
/**
57+
* Get the name.
58+
*
59+
* @return string
60+
*/
5161
public function name(): string
5262
{
5363
return $this->name;
5464
}
5565

66+
/**
67+
* Get the version.
68+
*
69+
* @return string
70+
*/
5671
public function version(): string
5772
{
5873
return $this->version;
5974
}
6075

76+
/**
77+
* Get the commands.
78+
*
79+
* @return Command[]
80+
*/
6181
public function commands(): array
6282
{
63-
return $this->commands;
83+
$commands = $this->commands;
84+
85+
unset($commands['__default__']);
86+
87+
return $commands;
6488
}
6589

90+
/**
91+
* Get the raw argv.
92+
*
93+
* @return array
94+
*/
6695
public function argv(): array
6796
{
6897
return $this->argv;
6998
}
7099

71-
public function command(string $name, string $desc = '', string $alias = '', bool $allowUnknown = false): Command
100+
/**
101+
* Sets or gets the ASCII art logo.
102+
*
103+
* @param string|null $logo
104+
*
105+
* @return string|self
106+
*/
107+
public function logo(string $logo = null)
72108
{
109+
if (\func_num_args() === 0) {
110+
return $this->logo;
111+
}
112+
113+
$this->logo = $logo;
114+
115+
return $this;
116+
}
117+
118+
/**
119+
* Add a command by its name desc alias etc.
120+
*
121+
* @param string $name
122+
* @param string $desc
123+
* @param string $alias
124+
* @param bool $allowUnknown
125+
* @param bool $default
126+
*
127+
* @return Command
128+
*/
129+
public function command(
130+
string $name,
131+
string $desc = '',
132+
string $alias = '',
133+
bool $allowUnknown = false,
134+
bool $default = false
135+
): Command {
136+
$command = new Command($name, $desc, $allowUnknown, $this);
137+
138+
$this->add($command, $alias, $default);
139+
140+
return $command;
141+
}
142+
143+
/**
144+
* Add a prepred command.
145+
*
146+
* @param Command $command
147+
* @param string $alias
148+
* @param bool $default
149+
*
150+
* @return self
151+
*/
152+
public function add(Command $command, string $alias = '', bool $default = false): self
153+
{
154+
$name = $command->name();
155+
73156
if ($this->commands[$name] ?? $this->aliases[$name] ?? $this->commands[$alias] ?? $this->aliases[$alias] ?? null) {
74157
throw new \InvalidArgumentException(\sprintf('Command "%s" already added', $name));
75158
}
@@ -78,11 +161,22 @@ public function command(string $name, string $desc = '', string $alias = '', boo
78161
$this->aliases[$alias] = $name;
79162
}
80163

81-
$command = (new Command($name, $desc, $allowUnknown, $this))->version($this->version)->onExit($this->onExit);
164+
if ($default) {
165+
$this->default = $name;
166+
}
167+
168+
$this->commands[$name] = $command->version($this->version)->onExit($this->onExit)->bind($this);
82169

83-
return $this->commands[$name] = $command;
170+
return $this;
84171
}
85172

173+
/**
174+
* Gets matching command for given argv.
175+
*
176+
* @param array $argv
177+
*
178+
* @return Command
179+
*/
86180
public function commandFor(array $argv): Command
87181
{
88182
$argv += [null, null, null];
@@ -93,9 +187,36 @@ public function commandFor(array $argv): Command
93187
// cmd alias
94188
?? $this->commands[$this->aliases[$argv[1]] ?? null]
95189
// default.
96-
?? $this->command;
190+
?? $this->commands[$this->default];
97191
}
98192

193+
/**
194+
* Gets or sets io.
195+
*
196+
* @param Interactor|null $io
197+
*
198+
* @return Interactor|self
199+
*/
200+
public function io(Interactor $io = null)
201+
{
202+
if ($io || !$this->io) {
203+
$this->io = $io ?? new Interactor;
204+
}
205+
206+
if (\func_num_args() === 0) {
207+
return $this->io;
208+
}
209+
210+
return $this;
211+
}
212+
213+
/**
214+
* Parse the arguments via the matching command but dont execute action..
215+
*
216+
* @param array $argv Cli arguments/options.
217+
*
218+
* @return Command The matched and parsed command (or default)
219+
*/
99220
public function parse(array $argv): Command
100221
{
101222
$this->argv = $argv;
@@ -114,13 +235,41 @@ public function parse(array $argv): Command
114235
}
115236
}
116237

117-
$command->parse($argv);
238+
return $command->parse($argv);
239+
}
118240

119-
$this->doAction($command);
241+
/**
242+
* Handle the request, invoke action and call exit handler.
243+
*
244+
* @param array $argv
245+
*
246+
* @return mixed
247+
*/
248+
public function handle(array $argv)
249+
{
250+
$io = $this->io();
120251

121-
return $command;
252+
try {
253+
$exitCode = 0;
254+
$command = $this->parse($argv);
255+
256+
$this->doAction($command);
257+
} catch (\Throwable $e) {
258+
$exitCode = 255;
259+
$location = 'At file ' . $e->getFile() . '#' . $e->getLine();
260+
$io->error($e->getMessage(), true)->bgRed($location, true);
261+
}
262+
263+
return ($this->onExit)($exitCode);
122264
}
123265

266+
/**
267+
* Get aliases for given command.
268+
*
269+
* @param Command $command
270+
*
271+
* @return array
272+
*/
124273
protected function aliasesFor(Command $command): array
125274
{
126275
$aliases = [$name = $command->name()];
@@ -135,28 +284,60 @@ protected function aliasesFor(Command $command): array
135284
return $aliases;
136285
}
137286

138-
public function showHelp(Writer $writer = null)
287+
/**
288+
* Show help of all commands.
289+
*
290+
* @return mixed
291+
*/
292+
public function showHelp()
139293
{
294+
$writer = $this->io()->writer();
295+
$helper = new OutputHelper($writer);
296+
140297
$header = "{$this->name}, version {$this->version}";
141298
$footer = 'Run `<command> --help` for specific help';
142299

143-
(new OutputHelper($writer))->showCommandsHelp($this->commands, $header, $footer);
300+
if ($this->logo) {
301+
$writer->write($this->logo, true);
302+
}
303+
304+
$helper->showCommandsHelp($this->commands(), $header, $footer);
144305

145306
return ($this->onExit)();
146307
}
147308

309+
/**
310+
* Invoke command action.
311+
*
312+
* @param Command $command
313+
*
314+
* @return mixed
315+
*/
148316
protected function doAction(Command $command)
149317
{
150318
if (null === $action = $command->action()) {
151319
return;
152320
}
153321

322+
// Let the command collect more data (if mising or needs confirmation)
323+
$command->interact($this->io());
324+
154325
$params = [];
155326
$values = $command->values();
156-
foreach ((new \ReflectionFunction($action))->getParameters() as $param) {
327+
328+
foreach ($this->getActionParameters($action) as $param) {
157329
$params[] = $values[$param->getName()] ?? null;
158330
}
159331

160332
return $action(...$params);
161333
}
334+
335+
protected function getActionParameters(callable $action): array
336+
{
337+
$reflex = \is_array($action)
338+
? (new \ReflectionClass($action[0]))->getMethod($action[1])
339+
: new \ReflectionFunction($action);
340+
341+
return $reflex->getParameters();
342+
}
162343
}

src/Helper/InflectsString.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
*/
1313
trait InflectsString
1414
{
15+
/**
16+
* Convert a string to camel case.
17+
*
18+
* @param string $string
19+
*
20+
* @return string
21+
*/
1522
public function toCamelCase(string $string): string
1623
{
1724
$words = \str_replace(['-', '_'], ' ', $string);

src/Helper/OutputHelper.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ protected function showHelp(string $for, array $items, int $space, string $heade
9393
*/
9494
protected function sortItems(array $items, &$max = 0): array
9595
{
96-
$max = 0;
96+
$first = reset($items);
97+
$max = \strlen($first->name());
9798

9899
\uasort($items, function ($a, $b) use (&$max) {
99100
$max = \max(\strlen($a->name()), \strlen($b->name()), $max);
@@ -106,6 +107,10 @@ protected function sortItems(array $items, &$max = 0): array
106107

107108
/**
108109
* Prepare name for different items.
110+
*
111+
* @param Parameter|Command $item
112+
*
113+
* @return string
109114
*/
110115
protected function getName($item): string
111116
{

0 commit comments

Comments
 (0)