Skip to content

Commit

Permalink
Merge pull request #72 from adhocore/68-command-group
Browse files Browse the repository at this point in the history
  • Loading branch information
adhocore authored Oct 8, 2022
2 parents 9efc076 + e3d1cad commit b194cff
Show file tree
Hide file tree
Showing 30 changed files with 566 additions and 244 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,27 @@ $app->logo('Ascii art logo of your app');
$app->handle($_SERVER['argv']); // if argv[1] is `i` or `init` it executes InitCommand
```

#### Grouping commands

Grouped commands are listed together in commands list. Explicit grouping a command is optional.
By default if a command name has a colon `:` then the part before it is taken as a group,
else `*` is taken as a group.

> Example: command name `app:env` has a default group `app`, command name `appenv` has group `*`.
```php
// Add grouped commands:
$app->group('Configuration', function ($app) {
$app->add(new ConfigSetCommand);
$app->add(new ConfigListCommand);
});

// Alternatively, set group one by one in each commands:
$app->add((new ConfigSetCommand)->inGroup('Config'));
$app->add((new ConfigListCommand)->inGroup('Config'));
...
```

#### App help

It can be triggered manually with `$app->showHelp()` or automatic when `-h` or `--help` option is passed to `$app->parse()`.
Expand Down
32 changes: 16 additions & 16 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
65 changes: 49 additions & 16 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@
use Ahc\Cli\Helper\OutputHelper;
use Ahc\Cli\Input\Command;
use Ahc\Cli\IO\Interactor;
use ReflectionClass;
use ReflectionFunction;
use Throwable;
use function array_diff_key;
use function array_fill_keys;
use function array_keys;
use function count;
use function func_num_args;
use function in_array;
use function is_array;
use function is_int;
use function method_exists;
use function sprintf;

/**
* A cli application.
Expand Down Expand Up @@ -48,7 +61,7 @@ class Application

public function __construct(protected string $name, protected string $version = '0.0.1', callable $onExit = null)
{
$this->onExit = $onExit ?? fn (int $exitCode = 0) => exit($exitCode);
$this->onExit = $onExit ?? static fn (int $exitCode = 0) => exit($exitCode);

$this->command('__default__', 'Default command', '', true)->on([$this, 'showHelp'], 'help');
}
Expand Down Expand Up @@ -100,7 +113,7 @@ public function argv(): array
*/
public function logo(string $logo = null)
{
if (\func_num_args() === 0) {
if (func_num_args() === 0) {
return $this->logo;
}

Expand Down Expand Up @@ -140,7 +153,7 @@ public function add(Command $command, string $alias = '', bool $default = false)
$this->aliases[$alias] ??
null
) {
throw new InvalidArgumentException(\sprintf('Command "%s" already added', $name));
throw new InvalidArgumentException(sprintf('Command "%s" already added', $name));
}

if ($alias) {
Expand All @@ -157,6 +170,26 @@ public function add(Command $command, string $alias = '', bool $default = false)
return $this;
}

/**
* Groups commands set within the callable.
*
* @param string $group The group name
* @param callable $fn The callable that recieves Application instance and adds commands.
*
* @return self
*/
public function group(string $group, callable $fn): self
{
$old = array_fill_keys(array_keys($this->commands), true);

$fn($this);
foreach (array_diff_key($this->commands, $old) as $cmd) {
$cmd->inGroup($group);
}

return $this;
}

/**
* Gets matching command for given argv.
*/
Expand Down Expand Up @@ -186,7 +219,7 @@ public function io(Interactor $io = null)
$this->io = $io ?? new Interactor;
}

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

Expand All @@ -209,7 +242,7 @@ public function parse(array $argv): Command

// Eat the cmd name!
foreach ($argv as $i => $arg) {
if (\in_array($arg, $aliases)) {
if (in_array($arg, $aliases)) {
unset($argv[$i]);

break;
Expand All @@ -228,7 +261,7 @@ public function parse(array $argv): Command
*/
public function handle(array $argv): mixed
{
if (\count($argv) < 2) {
if (count($argv) < 2) {
return $this->showHelp();
}

Expand All @@ -237,8 +270,8 @@ public function handle(array $argv): mixed
try {
$command = $this->parse($argv);
$result = $this->doAction($command);
$exitCode = \is_int($result) ? $result : 0;
} catch (\Throwable $e) {
$exitCode = is_int($result) ? $result : 0;
} catch (Throwable $e) {
$this->outputHelper()->printTrace($e);
}

Expand All @@ -252,10 +285,10 @@ protected function aliasesFor(Command $command): array
{
$aliases = [$name = $command->name()];

foreach ($this->aliases as $alias => $command) {
if (\in_array($name, [$alias, $command])) {
foreach ($this->aliases as $alias => $cmd) {
if (in_array($name, [$alias, $cmd], true)) {
$aliases[] = $alias;
$aliases[] = $command;
$aliases[] = $cmd;
}
}

Expand Down Expand Up @@ -299,7 +332,7 @@ protected function doAction(Command $command): mixed
// Let the command collect more data (if missing or needs confirmation)
$command->interact($this->io());

if (!$command->action() && !\method_exists($command, 'execute')) {
if (!$command->action() && !method_exists($command, 'execute')) {
return null;
}

Expand All @@ -320,17 +353,17 @@ protected function doAction(Command $command): mixed
*/
protected function notFound(): mixed
{
$available = \array_keys($this->commands() + $this->aliases);
$available = array_keys($this->commands() + $this->aliases);
$this->outputHelper()->showCommandNotFound($this->argv[1], $available);

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

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

return $reflex->getParameters();
}
Expand Down
4 changes: 3 additions & 1 deletion src/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

namespace Ahc\Cli;

interface Exception extends \Throwable
use Throwable;

interface Exception extends Throwable
{
// ;)
}
15 changes: 10 additions & 5 deletions src/Helper/InflectsString.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

namespace Ahc\Cli\Helper;

use function lcfirst;
use function str_replace;
use function trim;
use function ucwords;

/**
* Performs inflection on strings.
*
Expand All @@ -26,20 +31,20 @@ trait InflectsString
*/
public function toCamelCase(string $string): string
{
$words = \str_replace(['-', '_'], ' ', $string);
$words = str_replace(['-', '_'], ' ', $string);

$words = \str_replace(' ', '', \ucwords($words));
$words = str_replace(' ', '', ucwords($words));

return \lcfirst($words);
return lcfirst($words);
}

/**
* Convert a string to capitalized words.
*/
public function toWords(string $string): string
{
$words = \trim(\str_replace(['-', '_'], ' ', $string));
$words = trim(str_replace(['-', '_'], ' ', $string));

return \ucwords($words);
return ucwords($words);
}
}
20 changes: 13 additions & 7 deletions src/Helper/Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@

use Ahc\Cli\Input\Option;
use Ahc\Cli\Input\Parameter;
use function array_merge;
use function explode;
use function implode;
use function ltrim;
use function preg_match;
use function str_split;

/**
* Internal value &/or argument normalizer. Has little to no usefulness as public api.
Expand All @@ -32,13 +38,13 @@ public function normalizeArgs(array $args): array
$normalized = [];

foreach ($args as $arg) {
if (\preg_match('/^\-\w=/', $arg)) {
$normalized = \array_merge($normalized, explode('=', $arg));
} elseif (\preg_match('/^\-\w{2,}/', $arg)) {
$splitArg = \implode(' -', \str_split(\ltrim($arg, '-')));
$normalized = \array_merge($normalized, \explode(' ', '-' . $splitArg));
} elseif (\preg_match('/^\-\-([^\s\=]+)\=/', $arg)) {
$normalized = \array_merge($normalized, explode('=', $arg));
if (preg_match('/^\-\w=/', $arg)) {
$normalized = array_merge($normalized, explode('=', $arg));
} elseif (preg_match('/^\-\w{2,}/', $arg)) {
$splitArg = implode(' -', str_split(ltrim($arg, '-')));
$normalized = array_merge($normalized, explode(' ', '-' . $splitArg));
} elseif (preg_match('/^\-\-([^\s\=]+)\=/', $arg)) {
$normalized = array_merge($normalized, explode('=', $arg));
} else {
$normalized[] = $arg;
}
Expand Down
Loading

0 comments on commit b194cff

Please sign in to comment.