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
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