Skip to content

Commit

Permalink
Merge pull request #19 from adhocore/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
adhocore authored Jul 12, 2018
2 parents f03e360 + c92f7d8 commit 734d84e
Show file tree
Hide file tree
Showing 15 changed files with 947 additions and 199 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,14 @@ $app
})
;

// Maybe it could be named `handle()` or `run()`, but again we keep legacy of `commander.js`
// Parse only parses input but doesnt invoke action
$app->parse(['git', 'add', 'path1', 'path2', 'path3', '-f']);

// Handle will do both parse and invoke action.
$app->handle(['git', 'add', 'path1', 'path2', 'path3', '-f']);
// Will produce: Add path1, path2, path3 with force

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

Expand Down
217 changes: 199 additions & 18 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Ahc\Cli\Helper\OutputHelper;
use Ahc\Cli\Input\Command;
use Ahc\Cli\Output\Writer;
use Ahc\Cli\IO\Interactor;

/**
* A cli application.
Expand All @@ -31,45 +31,128 @@ class Application
/** @var string App version */
protected $version = '0.0.1';

/** @var string Ascii art logo */
protected $logo = '';

protected $default = '__default__';

/** @var Interactor */
protected $io;

public function __construct(string $name, string $version = '', callable $onExit = null)
{
$this->name = $name;
$this->version = $version;

// @codeCoverageIgnoreStart
$this->onExit = $onExit ?? function () {
exit(0);
$this->onExit = $onExit ?? function ($exitCode = 0) {
exit($exitCode);
};
// @codeCoverageIgnoreEnd

$this->command = $this->command('__default__', 'Default command', '', true)
->on([$this, 'showHelp'], 'help');

unset($this->commands['__default__']);
$this->command('__default__', 'Default command', '', true)->on([$this, 'showHelp'], 'help');
}

/**
* Get the name.
*
* @return string
*/
public function name(): string
{
return $this->name;
}

/**
* Get the version.
*
* @return string
*/
public function version(): string
{
return $this->version;
}

/**
* Get the commands.
*
* @return Command[]
*/
public function commands(): array
{
return $this->commands;
$commands = $this->commands;

unset($commands['__default__']);

return $commands;
}

/**
* Get the raw argv.
*
* @return array
*/
public function argv(): array
{
return $this->argv;
}

public function command(string $name, string $desc = '', string $alias = '', bool $allowUnknown = false): Command
/**
* Sets or gets the ASCII art logo.
*
* @param string|null $logo
*
* @return string|self
*/
public function logo(string $logo = null)
{
if (\func_num_args() === 0) {
return $this->logo;
}

$this->logo = $logo;

return $this;
}

/**
* Add a command by its name desc alias etc.
*
* @param string $name
* @param string $desc
* @param string $alias
* @param bool $allowUnknown
* @param bool $default
*
* @return Command
*/
public function command(
string $name,
string $desc = '',
string $alias = '',
bool $allowUnknown = false,
bool $default = false
): Command {
$command = new Command($name, $desc, $allowUnknown, $this);

$this->add($command, $alias, $default);

return $command;
}

/**
* Add a prepred command.
*
* @param Command $command
* @param string $alias
* @param bool $default
*
* @return self
*/
public function add(Command $command, string $alias = '', bool $default = false): self
{
$name = $command->name();

if ($this->commands[$name] ?? $this->aliases[$name] ?? $this->commands[$alias] ?? $this->aliases[$alias] ?? null) {
throw new \InvalidArgumentException(\sprintf('Command "%s" already added', $name));
}
Expand All @@ -78,11 +161,22 @@ public function command(string $name, string $desc = '', string $alias = '', boo
$this->aliases[$alias] = $name;
}

$command = (new Command($name, $desc, $allowUnknown, $this))->version($this->version)->onExit($this->onExit);
if ($default) {
$this->default = $name;
}

$this->commands[$name] = $command->version($this->version)->onExit($this->onExit)->bind($this);

return $this->commands[$name] = $command;
return $this;
}

/**
* Gets matching command for given argv.
*
* @param array $argv
*
* @return Command
*/
public function commandFor(array $argv): Command
{
$argv += [null, null, null];
Expand All @@ -93,9 +187,36 @@ public function commandFor(array $argv): Command
// cmd alias
?? $this->commands[$this->aliases[$argv[1]] ?? null]
// default.
?? $this->command;
?? $this->commands[$this->default];
}

/**
* Gets or sets io.
*
* @param Interactor|null $io
*
* @return Interactor|self
*/
public function io(Interactor $io = null)
{
if ($io || !$this->io) {
$this->io = $io ?? new Interactor;
}

if (\func_num_args() === 0) {
return $this->io;
}

return $this;
}

/**
* Parse the arguments via the matching command but dont execute action..
*
* @param array $argv Cli arguments/options.
*
* @return Command The matched and parsed command (or default)
*/
public function parse(array $argv): Command
{
$this->argv = $argv;
Expand All @@ -114,13 +235,41 @@ public function parse(array $argv): Command
}
}

$command->parse($argv);
return $command->parse($argv);
}

$this->doAction($command);
/**
* Handle the request, invoke action and call exit handler.
*
* @param array $argv
*
* @return mixed
*/
public function handle(array $argv)
{
$io = $this->io();

return $command;
try {
$exitCode = 0;
$command = $this->parse($argv);

$this->doAction($command);
} catch (\Throwable $e) {
$exitCode = 255;
$location = 'At file ' . $e->getFile() . '#' . $e->getLine();
$io->error($e->getMessage(), true)->bgRed($location, true);
}

return ($this->onExit)($exitCode);
}

/**
* Get aliases for given command.
*
* @param Command $command
*
* @return array
*/
protected function aliasesFor(Command $command): array
{
$aliases = [$name = $command->name()];
Expand All @@ -135,28 +284,60 @@ protected function aliasesFor(Command $command): array
return $aliases;
}

public function showHelp(Writer $writer = null)
/**
* Show help of all commands.
*
* @return mixed
*/
public function showHelp()
{
$writer = $this->io()->writer();
$helper = new OutputHelper($writer);

$header = "{$this->name}, version {$this->version}";
$footer = 'Run `<command> --help` for specific help';

(new OutputHelper($writer))->showCommandsHelp($this->commands, $header, $footer);
if ($this->logo) {
$writer->write($this->logo, true);
}

$helper->showCommandsHelp($this->commands(), $header, $footer);

return ($this->onExit)();
}

/**
* Invoke command action.
*
* @param Command $command
*
* @return mixed
*/
protected function doAction(Command $command)
{
if (null === $action = $command->action()) {
return;
}

// Let the command collect more data (if mising or needs confirmation)
$command->interact($this->io());

$params = [];
$values = $command->values();
foreach ((new \ReflectionFunction($action))->getParameters() as $param) {

foreach ($this->getActionParameters($action) as $param) {
$params[] = $values[$param->getName()] ?? null;
}

return $action(...$params);
}

protected function getActionParameters(callable $action): array
{
$reflex = \is_array($action)
? (new \ReflectionClass($action[0]))->getMethod($action[1])
: new \ReflectionFunction($action);

return $reflex->getParameters();
}
}
7 changes: 7 additions & 0 deletions src/Helper/InflectsString.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
*/
trait InflectsString
{
/**
* Convert a string to camel case.
*
* @param string $string
*
* @return string
*/
public function toCamelCase(string $string): string
{
$words = \str_replace(['-', '_'], ' ', $string);
Expand Down
7 changes: 6 additions & 1 deletion src/Helper/OutputHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ protected function showHelp(string $for, array $items, int $space, string $heade
*/
protected function sortItems(array $items, &$max = 0): array
{
$max = 0;
$first = reset($items);
$max = \strlen($first->name());

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

/**
* Prepare name for different items.
*
* @param Parameter|Command $item
*
* @return string
*/
protected function getName($item): string
{
Expand Down
Loading

0 comments on commit 734d84e

Please sign in to comment.