Initialize Nest CLI library.
This commit is contained in:
commit
1163a270b4
29 changed files with 1766 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Composer
|
||||
vendor/
|
27
composer.json
Normal file
27
composer.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"name": "nest/cli",
|
||||
"description": "Nest CLI service and engine.",
|
||||
"type": "library",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Madeorsk",
|
||||
"email": "madeorsk@protonmail.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nest\\Cli\\": "src/"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://code.zeptotech.net/Nest/Core"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"nest/core": "dev-main"
|
||||
}
|
||||
}
|
56
composer.lock
generated
Normal file
56
composer.lock
generated
Normal file
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "fc6ba9501cee8e0307bbf953ce6c5ec9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "nest/core",
|
||||
"version": "dev-main",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://code.zeptotech.net/Nest/Core",
|
||||
"reference": "22296b8d7c8b528089189a8d9500395424cb6468"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nest\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/Utils/Array.php",
|
||||
"src/Utils/Paths.php",
|
||||
"src/Utils/Reflection.php",
|
||||
"src/Utils/String.php"
|
||||
]
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Madeorsk",
|
||||
"email": "madeorsk@protonmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Nest framework core.",
|
||||
"time": "2024-11-08T11:12:38+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"nest/core": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
149
src/Cli.php
Normal file
149
src/Cli.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
use Nest\Application;
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Nest\Cli\Commands\CommandDefinition;
|
||||
use Nest\Cli\Commands\FlagDefinition;
|
||||
use Nest\Exceptions\Cli\Command\CommandException;
|
||||
use Nest\Exceptions\Cli\Command\InvalidCommandHandlerException;
|
||||
use Nest\Exceptions\Cli\IncompatibleCliHandlerSubcommands;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* CLI manager and engine.
|
||||
*/
|
||||
class Cli
|
||||
{
|
||||
/**
|
||||
* Associative array of CLI commands.
|
||||
* @var array<string, CommandDefinition>
|
||||
*/
|
||||
protected array $commands;
|
||||
|
||||
/**
|
||||
* The CLI helper.
|
||||
* @var CliHelper
|
||||
*/
|
||||
protected CliHelper $helper;
|
||||
|
||||
/**
|
||||
* @param Application $application The application.
|
||||
* @param CliConfiguration $configuration CLI configuration.
|
||||
*/
|
||||
public function __construct(protected Application $application, protected CliConfiguration $configuration)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new command context for given arguments.
|
||||
* @param string[]|null $argv Command arguments, NULL to get the currently passed command arguments.
|
||||
* @return CommandContext The command context.
|
||||
* @throws CommandException
|
||||
*/
|
||||
public function newCommandContext(?array $argv = null): CommandContext
|
||||
{
|
||||
// Initialize command context with arguments.
|
||||
return new CommandContext($this, $this->commands, $argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception handler for the CLI.
|
||||
* @param Throwable $exception Exception to handle.
|
||||
* @return void
|
||||
*/
|
||||
protected function exceptionHandler(Throwable $exception): void
|
||||
{
|
||||
Out::error("Unhandled exception: {$exception->getMessage()}");
|
||||
Out::error("in file ".Out::ITALIC_ATTRIBUTE."{$exception->getFile()}".Out::RESET_ATTRIBUTES.Color::Red->value." on line {$exception->getLine()}.");
|
||||
echo Color::Red->value."{$exception->getTraceAsString()}".Color::Default->value."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Main engine function.
|
||||
* @return void
|
||||
* @throws InvalidCommandHandlerException
|
||||
* @throws IncompatibleCliHandlerSubcommands
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
// Add exception handler.
|
||||
$this->application->exceptionHandler()->add(fn (Throwable $exception) => $this->exceptionHandler($exception));
|
||||
|
||||
// Define configuration of this CLI.
|
||||
$this->configuration->define($this);
|
||||
|
||||
// Get CLI helper.
|
||||
$helper = $this->getHelper();
|
||||
|
||||
try
|
||||
{
|
||||
// Create and fill command context, then check its validity.
|
||||
$commandContext = $this->newCommandContext();
|
||||
$commandContext->checkValidity();
|
||||
|
||||
if ($commandContext->hasFlag(FlagDefinition::HELP_FLAG))
|
||||
{ // If auto help flag has been provided, showing help no matter the rest.
|
||||
$helper->help($this, $commandContext);
|
||||
}
|
||||
else
|
||||
{ // Execute the command handler with all parameters.
|
||||
$commandContext->getCommandHandler()(...$commandContext->getParameters());
|
||||
}
|
||||
}
|
||||
catch (CommandException $commandException)
|
||||
{ // If any command parse error happen, show it, then show help for the given context.
|
||||
$helper->help($this, $commandException->commandContext, $commandException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CLI helper.
|
||||
* The role of CLI helper is to provide help when needed (either asked by user or after invalid arguments).
|
||||
* @param CliHelper $helper New CLI helper.
|
||||
* @return void
|
||||
*/
|
||||
public function setHelper(CliHelper $helper): void
|
||||
{
|
||||
$this->helper = $helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CLI helper.
|
||||
* If none is set, return a default one.
|
||||
* @return CliHelper CLI helper instance.
|
||||
*/
|
||||
public function getHelper(): CliHelper
|
||||
{
|
||||
// Return defined helper, if there is one.
|
||||
if (!empty($this->helper)) return $this->helper;
|
||||
|
||||
// Return a new default helper.
|
||||
return new DefaultHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get command definition of the given command.
|
||||
* Initialize a new command definition if it didn't exist.
|
||||
* @param string $command A command
|
||||
* @return CommandDefinition
|
||||
*/
|
||||
public function command(string $command): CommandDefinition
|
||||
{
|
||||
if (empty($this->commands[$command]))
|
||||
// Initialize a new command definition because it doesn't exist.
|
||||
$this->commands[$command] = new CommandDefinition();
|
||||
// Return command definition of given command.
|
||||
return $this->commands[$command];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commands definitions for the CLI.
|
||||
* @return array<string, CommandDefinition> Commands definitions.
|
||||
*/
|
||||
public function getCommands(): array
|
||||
{
|
||||
return $this->commands;
|
||||
}
|
||||
}
|
22
src/CliConfiguration.php
Normal file
22
src/CliConfiguration.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
use Nest\Exceptions\Cli\Command\InvalidCommandHandlerException;
|
||||
use Nest\Exceptions\Cli\IncompatibleCliHandlerSubcommands;
|
||||
use Nest\Services\ServiceConfiguration;
|
||||
|
||||
/**
|
||||
* CLI configuration class.
|
||||
*/
|
||||
abstract class CliConfiguration extends ServiceConfiguration
|
||||
{
|
||||
/**
|
||||
* Define commands of the CLI.
|
||||
* @param Cli $cli The CLI to configure.
|
||||
* @return void
|
||||
* @throws InvalidCommandHandlerException
|
||||
* @throws IncompatibleCliHandlerSubcommands
|
||||
*/
|
||||
public abstract function define(Cli $cli): void;
|
||||
}
|
21
src/CliHelper.php
Normal file
21
src/CliHelper.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* CLI helper interface.
|
||||
*/
|
||||
interface CliHelper
|
||||
{
|
||||
/**
|
||||
* Show helper for a given command context in a CLI.
|
||||
* @param Cli $cli CLI of the helper.
|
||||
* @param CommandContext $command Command context which require help.
|
||||
* @param Throwable|null $exception Thrown exception that caused helper to be called.
|
||||
* @return void
|
||||
*/
|
||||
public function help(Cli $cli, CommandContext $command, ?Throwable $exception = null): void;
|
||||
}
|
36
src/CliService.php
Normal file
36
src/CliService.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
use Nest\Exceptions\Services\Configuration\UndefinedServiceConfigurationException;
|
||||
|
||||
/**
|
||||
* CLI Nest service.
|
||||
*/
|
||||
trait CliService
|
||||
{
|
||||
/**
|
||||
* The CLI.
|
||||
* @var Cli
|
||||
*/
|
||||
private Cli $cli;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws UndefinedServiceConfigurationException
|
||||
*/
|
||||
protected function __nest__CliService(): void
|
||||
{
|
||||
// Initialize CLI.
|
||||
$this->cli = new Cli($this, $this->getServiceConfiguration(CliConfiguration::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CLI.
|
||||
* @return Cli CLI instance.
|
||||
*/
|
||||
public function cli(): Cli
|
||||
{
|
||||
return $this->cli;
|
||||
}
|
||||
}
|
27
src/Color.php
Normal file
27
src/Color.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
/**
|
||||
* CLI out colors.
|
||||
*/
|
||||
enum Color: string
|
||||
{
|
||||
case Default = "\e[39m";
|
||||
case Black = "\e[30m";
|
||||
case Red = "\e[31m";
|
||||
case Green = "\e[32m";
|
||||
case Yellow = "\e[33m";
|
||||
case Blue = "\e[34m";
|
||||
case Magenta = "\e[35m";
|
||||
case Cyan = "\e[36m";
|
||||
case LightGray = "\e[37m";
|
||||
case DarkGray = "\e[90m";
|
||||
case LightRed = "\e[91m";
|
||||
case LightGreen = "\e[92m";
|
||||
case LightYellow = "\e[93m";
|
||||
case LightBlue = "\e[94m";
|
||||
case LightMagenta = "\e[95m";
|
||||
case LightCyan = "\e[96m";
|
||||
case White = "\e[97m";
|
||||
}
|
332
src/Commands/CommandContext.php
Normal file
332
src/Commands/CommandContext.php
Normal file
|
@ -0,0 +1,332 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Commands;
|
||||
|
||||
use Nest\Cli\Cli;
|
||||
use Nest\Exceptions\Cli\Command\CommandException;
|
||||
use Nest\Exceptions\Cli\Command\IncompleteCommandException;
|
||||
use Nest\Exceptions\Cli\Command\InvalidCommandException;
|
||||
use Nest\Exceptions\Cli\Command\NotEnoughParametersException;
|
||||
use Nest\Exceptions\Cli\Command\MissingCommandException;
|
||||
use Nest\Exceptions\Cli\Command\MissingRequiredFlagException;
|
||||
use Nest\Exceptions\Cli\Command\UndefinedFlagException;
|
||||
use Nest\Exceptions\Cli\Command\UndefinedShortFlagException;
|
||||
use Nest\Exceptions\Cli\Command\UndefinedSubcommandsException;
|
||||
use Nest\Exceptions\Cli\FlagNotFoundException;
|
||||
|
||||
/**
|
||||
* Class of a command context.
|
||||
*/
|
||||
class CommandContext
|
||||
{
|
||||
/**
|
||||
* Command executable.
|
||||
* @var string
|
||||
*/
|
||||
protected string $executable;
|
||||
|
||||
/**
|
||||
* Commands path (with all subcommands).
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $commandsPath;
|
||||
|
||||
/**
|
||||
* Associated command definition.
|
||||
* @var CommandDefinition
|
||||
*/
|
||||
protected CommandDefinition $commandDefinition;
|
||||
|
||||
/**
|
||||
* Command parameters.
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $parameters = [];
|
||||
|
||||
/**
|
||||
* Flags with their parameters (if there are some).
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected array $flags;
|
||||
|
||||
/**
|
||||
* Command handler callable, determined from command definition.
|
||||
* @var callable(mixed...): ?int
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
/**
|
||||
* Create a new command context and fill it using passed command arguments.
|
||||
* @param Cli $cli The CLI.
|
||||
* @param array<string, CommandDefinition> $commands Commands definitions.
|
||||
* @param array|null $argv Command arguments (with executable at first position), NULL to get the currently passed command arguments.
|
||||
* @throws CommandException
|
||||
*/
|
||||
public function __construct(protected Cli $cli, protected array $commands, ?array $argv = null)
|
||||
{
|
||||
// Parse command.
|
||||
$this->parseCommand($argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse passed command arguments.
|
||||
* @param array|null $args Command arguments, NULL to get the currently passed command arguments.
|
||||
* @return void
|
||||
* @throws CommandException
|
||||
*/
|
||||
protected function parseCommand(?array $args = null): void
|
||||
{
|
||||
// Get command arguments.
|
||||
global $argv;
|
||||
|
||||
// If args are not defined, get it from argv.
|
||||
if (is_null($args)) $args = $argv;
|
||||
|
||||
// Save used executable.
|
||||
$this->executable = $args[0];
|
||||
|
||||
if (empty($args[1]))
|
||||
// No command.
|
||||
throw new MissingCommandException($this);
|
||||
|
||||
// Parse commands path from first argument.
|
||||
$this->commandsPath = array_map(fn (string $command) => trim($command), explode(":", $args[1]));
|
||||
|
||||
// Find associated command definition from commands path.
|
||||
$this->findCommandDefinition();
|
||||
|
||||
if (!empty($args[2]))
|
||||
// Find command flags and parameters, if there are some.
|
||||
$this->findCommandFlagsAndParams(array_slice($args, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find associated command definition from commands path.
|
||||
* @param int $currentIndex Current index in commands path.
|
||||
* @param CommandDefinition|null $currentCommandDefinition Current command definition, for recursive calls.
|
||||
* @return void
|
||||
* @throws CommandException
|
||||
*/
|
||||
protected function findCommandDefinition(int $currentIndex = 0, ?CommandDefinition $currentCommandDefinition = null): void
|
||||
{
|
||||
if (empty($this->commandsPath[$currentIndex]))
|
||||
{ // We reached the end of the commands path, set the current command definition as the current one.
|
||||
$this->commandDefinition = $currentCommandDefinition;
|
||||
|
||||
if ($this->commandDefinition->hasHandler())
|
||||
{ // Check that a command handler is defined for the current command.
|
||||
// Get command handler.
|
||||
$commandHandler = $this->commandDefinition->getHandler();
|
||||
// Instantiate handler class to call the object.
|
||||
$this->handler = new $commandHandler($this->cli, $this);
|
||||
}
|
||||
else
|
||||
// If there is no handler for the current command definition, the given command is incomplete.
|
||||
throw new IncompleteCommandException($this);
|
||||
}
|
||||
else
|
||||
{ // Get the next command definition, if there is one.
|
||||
|
||||
// Get current command part.
|
||||
$commandPart = $this->commandsPath[$currentIndex];
|
||||
|
||||
if (empty($currentCommandDefinition))
|
||||
{ // Try to read root command.
|
||||
if (empty($this->commands[$commandPart]))
|
||||
// Invalid command.
|
||||
throw new InvalidCommandException($commandPart, $this);
|
||||
|
||||
// Recursive call to find the right command definition.
|
||||
$this->findCommandDefinition($currentIndex + 1, $this->commands[$commandPart]);
|
||||
}
|
||||
else
|
||||
{ // Try to read a subcommand.
|
||||
if ($currentCommandDefinition->hasSubcommands())
|
||||
{ // Try to get subcommands.
|
||||
$subcommands = $currentCommandDefinition->getSubcommands();
|
||||
|
||||
if (is_array($subcommands))
|
||||
{ // If it's an array, try to get the right command definition.
|
||||
if (empty($subcommands[$commandPart]))
|
||||
// Invalid command.
|
||||
throw new InvalidCommandException($commandPart, $this);
|
||||
|
||||
// Recursive call to find the right command definition.
|
||||
$this->findCommandDefinition($currentIndex + 1, $subcommands[$commandPart]);
|
||||
}
|
||||
else
|
||||
{ // A subcommands handler is defined, we just need to call it with the right subcommand.
|
||||
$subcommandsHandler = $currentCommandDefinition->getSubcommands();
|
||||
|
||||
// Instantiate handler class to call the object.
|
||||
$callable = new $subcommandsHandler($this->cli, $this);
|
||||
|
||||
// Get remaining subcommands.
|
||||
$subcommandsPath = array_slice($this->commandsPath, $currentIndex);
|
||||
|
||||
// Set current command definition as definitive command definition.
|
||||
$this->commandDefinition = $currentCommandDefinition;
|
||||
// Create a new handler which will pass subcommands path as first parameter of the callable.
|
||||
$this->handler = function (mixed ...$parameters) use ($callable, $subcommandsPath) {
|
||||
return $callable($subcommandsPath, ...$parameters);
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
// No subcommands for the current command, throwing an exception.
|
||||
throw new UndefinedSubcommandsException($commandPart, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find command flags and parameters.
|
||||
* @param string[] $args Raw arguments array.
|
||||
* @return void
|
||||
* @throws UndefinedFlagException
|
||||
*/
|
||||
protected function findCommandFlagsAndParams(array $args): void
|
||||
{
|
||||
// Initialize current flag (full) name.
|
||||
$currentFlagName = null;
|
||||
|
||||
foreach ($args as $arg)
|
||||
{ // Reading each argument one by one, to determine if it's a flag, a flag parameter or a positional parameter.
|
||||
|
||||
if (str_starts_with($arg, "--"))
|
||||
{ // Current argument is a flag.
|
||||
// Get current flag name.
|
||||
$currentFlagName = substr($arg, 2);
|
||||
|
||||
if (empty($this->commandDefinition->getFlagDefinition($currentFlagName)) && $currentFlagName != FlagDefinition::HELP_FLAG)
|
||||
// If the flag is undefined, throw an exception. Explicitly allow auto help flag.
|
||||
throw new UndefinedFlagException($currentFlagName, $this);
|
||||
|
||||
// Initialize flag in the context.
|
||||
$this->flags[$currentFlagName] = [];
|
||||
}
|
||||
elseif (str_starts_with($arg, "-"))
|
||||
{ // Current argument is a (short) flag.
|
||||
// Get current flag name from short flag name.
|
||||
$currentFlagName = $this->commandDefinition->getShortFlagName($shortFlagName = substr($arg, 1));
|
||||
|
||||
if (empty($currentFlagName))
|
||||
// If the short flag is undefined, throw an exception.
|
||||
throw new UndefinedShortFlagException($shortFlagName, $this);
|
||||
|
||||
// Initialize flag in the context.
|
||||
$this->flags[$currentFlagName] = [];
|
||||
}
|
||||
else
|
||||
{ // Current argument is a parameter.
|
||||
if (!empty($currentFlagName) && count($this->flags[$currentFlagName]) < $this->commandDefinition->getFlagDefinition($currentFlagName)->getParametersCount())
|
||||
{ // If there is a current flag and we did not read all its parameters, considering this parameter is a flag parameter.
|
||||
$this->flags[$currentFlagName][] = $arg;
|
||||
}
|
||||
else
|
||||
// No current flag (no current flag or no remaining parameters for it), the parameter is a command parameter.
|
||||
$this->parameters[] = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check command validity: the current command context should fulfill requirements of the command definition.
|
||||
* Throw a CommandException if anything is wrong.
|
||||
* @return void
|
||||
* @throws CommandException
|
||||
*/
|
||||
public function checkValidity(): void
|
||||
{
|
||||
// Check there are enough parameters to match the required count.
|
||||
if (($providedCount = !empty($this->parameters) ? count($this->parameters) : 0) < ($expectedCount = $this->commandDefinition->getRequiredParametersCount()))
|
||||
throw new NotEnoughParametersException($providedCount, $expectedCount, $this);
|
||||
|
||||
// Check that all the required flags are set.
|
||||
foreach ($this->commandDefinition->getRequiredFlags() as $requiredFlag)
|
||||
{ // Check each required flag existence.
|
||||
if (!$this->hasFlag($requiredFlag))
|
||||
// Required flag is not defined, throw an exception.
|
||||
throw new MissingRequiredFlagException($requiredFlag, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get used executable.
|
||||
* @return string Command executable.
|
||||
*/
|
||||
public function getExecutable(): string
|
||||
{
|
||||
return $this->executable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full command (with all its subcommands).
|
||||
* @return string Full command.
|
||||
*/
|
||||
public function getFullCommand(): string
|
||||
{
|
||||
return implode(":", $this->commandsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commands path (all subcommands).
|
||||
* @return string[]
|
||||
*/
|
||||
public function getCommandsPath(): array
|
||||
{
|
||||
return $this->commandsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get associated command definition, or NULL if it couldn't be found.
|
||||
* @return CommandDefinition|null
|
||||
*/
|
||||
public function getCommandDefinition(): ?CommandDefinition
|
||||
{
|
||||
return $this->commandDefinition ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get command handler callable.
|
||||
* @return callable(mixed...): ?int
|
||||
*/
|
||||
public function getCommandHandler(): callable
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get command positional parameters.
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a flag has been provided in the command.
|
||||
* @param string $flag Flag (full) name.
|
||||
* @return bool True if the flag have been provided, false otherwise.
|
||||
*/
|
||||
public function hasFlag(string $flag): bool
|
||||
{
|
||||
return isset($this->flags[$flag]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flag parameters of a flag, if they were provided.
|
||||
* @param string $flag Flag (full) name.
|
||||
* @return array Flag parameters.
|
||||
* @throws FlagNotFoundException
|
||||
*/
|
||||
public function getFlagParameters(string $flag): array
|
||||
{
|
||||
if (!$this->hasFlag($flag))
|
||||
// Flag not provided, throw an exception.
|
||||
throw new FlagNotFoundException($flag);
|
||||
|
||||
return $this->flags[$flag]; // Return flag parameters.
|
||||
}
|
||||
}
|
274
src/Commands/CommandDefinition.php
Normal file
274
src/Commands/CommandDefinition.php
Normal file
|
@ -0,0 +1,274 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Commands;
|
||||
|
||||
use Nest\Exceptions\Cli\Command\InvalidCommandHandlerException;
|
||||
use Nest\Exceptions\Cli\IncompatibleCliHandlerSubcommands;
|
||||
|
||||
/**
|
||||
* A command definition object.
|
||||
*/
|
||||
class CommandDefinition
|
||||
{
|
||||
/**
|
||||
* The command description.
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $description = null;
|
||||
|
||||
/**
|
||||
* Parameters array for the current command.
|
||||
* @var array<string, ParameterDefinition>
|
||||
*/
|
||||
protected array $parameters = [];
|
||||
|
||||
/**
|
||||
* Flags array for the current command.
|
||||
* @var array<string, FlagDefinition>
|
||||
*/
|
||||
protected array $flags = [];
|
||||
|
||||
/**
|
||||
* Short flags matching array.
|
||||
* Associate every short flag with its matching full flag.
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $shortFlags = [];
|
||||
|
||||
/**
|
||||
* Command handler.
|
||||
* @var string
|
||||
*/
|
||||
protected string $handler;
|
||||
|
||||
/**
|
||||
* Subcommands handler or subcommands definitions.
|
||||
* @var string|array<string, CommandDefinition>
|
||||
*/
|
||||
protected string|array $subcommands;
|
||||
|
||||
/**
|
||||
* Set the description of the command.
|
||||
* @param string $description The description of the command.
|
||||
* @return $this
|
||||
*/
|
||||
public function description(string $description): static
|
||||
{
|
||||
// Set description.
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a parameter for the command.
|
||||
* @param string $name Parameter name.
|
||||
* @param (callable(ParameterDefinition): ParameterDefinition)|null $parameterDefinition Parameter definition function.
|
||||
* @return $this
|
||||
*/
|
||||
public function parameter(string $name, ?callable $parameterDefinition = null): static
|
||||
{
|
||||
// Add the given parameter with its definition.
|
||||
$this->parameters[$name] = new ParameterDefinition();
|
||||
|
||||
if (!empty($parameterDefinition))
|
||||
// A parameter definition function is defined, calling it.
|
||||
$this->parameters[$name] = $parameterDefinition($this->parameters[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a flag for the command.
|
||||
* @param string $flag Flag name.
|
||||
* @param (callable(FlagDefinition): FlagDefinition)|null $flagDefinition Flag definition function.
|
||||
* @return $this
|
||||
*/
|
||||
public function flag(string $flag, ?callable $flagDefinition = null): static
|
||||
{
|
||||
// Add the given flag with its definition.
|
||||
$this->flags[$flag] = new FlagDefinition();
|
||||
|
||||
if (!empty($flagDefinition))
|
||||
// A flag definition function is defined, calling it.
|
||||
$this->flags[$flag] = $flagDefinition($this->flags[$flag]);
|
||||
|
||||
if (!empty($short = $this->flags[$flag]->getShort()))
|
||||
// If a short flag is defined, add it to short flag matching array.
|
||||
$this->shortFlags[$short] = $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an handler for the command.
|
||||
* Cannot be used when subcommands are defined.
|
||||
* @param string $handler Command handler class.
|
||||
* @return $this
|
||||
* @throws IncompatibleCliHandlerSubcommands
|
||||
* @throws InvalidCommandHandlerException
|
||||
*/
|
||||
public function handler(string $handler): static
|
||||
{
|
||||
if (!empty($this->subcommands))
|
||||
// Subcommands are already defined, throwing an exception.
|
||||
throw new IncompatibleCliHandlerSubcommands();
|
||||
|
||||
// Check that the command handler has the right type.
|
||||
if (!is_a($handler, CommandHandler::class, true))
|
||||
throw new InvalidCommandHandlerException($handler);
|
||||
|
||||
// Set the current command handler.
|
||||
$this->handler = $handler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define subcommands for the command.
|
||||
* Cannot be used when handler is defined.
|
||||
* @param string|array<string, callable(CommandDefinition): CommandDefinition> $subcommands Subcommands handler or subcommands definitions array.
|
||||
* @return $this
|
||||
* @throws IncompatibleCliHandlerSubcommands
|
||||
* @throws InvalidCommandHandlerException
|
||||
*/
|
||||
public function subcommands(string|array $subcommands): static
|
||||
{
|
||||
if (!empty($this->handler))
|
||||
// A handler is already defined, throwing an exception.
|
||||
throw new IncompatibleCliHandlerSubcommands();
|
||||
|
||||
if (is_string($subcommands))
|
||||
{ // If subcommands is a handler class, check that the subcommands handler has the right type.
|
||||
if (!is_a($subcommands, CommandHandler::class, true))
|
||||
throw new InvalidCommandHandlerException($subcommands);
|
||||
// Set subcommands handler.
|
||||
$this->subcommands = $subcommands;
|
||||
}
|
||||
else
|
||||
{ // Set subcommands definition array.
|
||||
foreach ($subcommands as $subcommand => $definitionCallable)
|
||||
{ // For each subcommand definition, call its definition function.
|
||||
$this->subcommands[$subcommand] = $definitionCallable(new CommandDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a command handler is defined for this command.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasHandler(): bool
|
||||
{
|
||||
return !empty($this->handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get defined command handler class.
|
||||
* @return string Command handler class.
|
||||
*/
|
||||
public function getHandler(): string
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if subcommands are defined for this command.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSubcommands(): bool
|
||||
{
|
||||
return !empty($this->subcommands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subcommands definition.
|
||||
* @return string|array<string, callable(CommandDefinition): CommandDefinition> Subcommands handler or subcommands definitions.
|
||||
*/
|
||||
public function getSubcommands(): string|array
|
||||
{
|
||||
return $this->subcommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required parameters count.
|
||||
* @return int Required parameters count.
|
||||
*/
|
||||
public function getRequiredParametersCount(): int
|
||||
{
|
||||
// Check every parameter to count the required ones.
|
||||
return array_reduce(
|
||||
$this->parameters,
|
||||
// Add 1 everytime we see a required parameter.
|
||||
fn (int $total, ParameterDefinition $parameter) => $total + ($parameter->isRequired() ? 1 : 0),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get command description.
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get defined parameters for the command.
|
||||
* @return array<string, ParameterDefinition> Defined parameters.
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short flag full name.
|
||||
* @param string $shortFlag Short flag name.
|
||||
* @return string|null Flag full name. NULL if undefined.
|
||||
*/
|
||||
public function getShortFlagName(string $shortFlag): ?string
|
||||
{
|
||||
return $this->shortFlags[$shortFlag] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flag definition for the given flag (full) name.
|
||||
* @param string $flag Flag (full) name.
|
||||
* @return FlagDefinition|null Flag definition. NULL if undefined.
|
||||
*/
|
||||
public function getFlagDefinition(string $flag): ?FlagDefinition
|
||||
{
|
||||
return $this->flags[$flag] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required flags list.
|
||||
* @return string[] Required flags names.
|
||||
*/
|
||||
public function getRequiredFlags(): array
|
||||
{
|
||||
// Initialize required flags list.
|
||||
$requiredFlags = [];
|
||||
|
||||
foreach ($this->flags as $flag => $definition)
|
||||
{ // For each flag, if it is required, add it in the list.
|
||||
if ($definition->isRequired())
|
||||
// The current flag is required, adding it to the list.
|
||||
$requiredFlags[] = $flag;
|
||||
}
|
||||
|
||||
return $requiredFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get defined flags for the command.
|
||||
* @return array<string, FlagDefinition> Defined flags.
|
||||
*/
|
||||
public function getFlags(): array
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
}
|
19
src/Commands/CommandHandler.php
Normal file
19
src/Commands/CommandHandler.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Commands;
|
||||
|
||||
use Nest\Cli\Cli;
|
||||
|
||||
/**
|
||||
* A CLI command handler.
|
||||
*/
|
||||
abstract class CommandHandler
|
||||
{
|
||||
/**
|
||||
* Initialize a CLI command handler.
|
||||
* @param Cli $cli The CLI of the command.
|
||||
* @param CommandContext $context Current command context.
|
||||
*/
|
||||
public function __construct(protected Cli $cli, protected CommandContext $context)
|
||||
{}
|
||||
}
|
125
src/Commands/FlagDefinition.php
Normal file
125
src/Commands/FlagDefinition.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Commands;
|
||||
|
||||
/**
|
||||
* A flag definition object.
|
||||
*/
|
||||
class FlagDefinition
|
||||
{
|
||||
/**
|
||||
* Help flag name.
|
||||
*/
|
||||
const string HELP_FLAG = "help";
|
||||
|
||||
/**
|
||||
* Short version of the flag.
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $short = null;
|
||||
|
||||
/**
|
||||
* Flag description.
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $description = null;
|
||||
|
||||
/**
|
||||
* Positional parameters array for the current flag.
|
||||
* @var object{name: string, description: string}[]
|
||||
*/
|
||||
protected array $parameters = [];
|
||||
|
||||
/**
|
||||
* Set if the flag is required or not. A flag is NOT required by default.
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $required = false;
|
||||
|
||||
/**
|
||||
* Set the short version of the flag.
|
||||
* @param string $short Short version of the flag.
|
||||
* @return $this
|
||||
*/
|
||||
public function short(string $short): static
|
||||
{
|
||||
$this->short = $short;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the description of the flag.
|
||||
* @param string $description The description of the flag.
|
||||
* @return $this
|
||||
*/
|
||||
public function description(string $description): static
|
||||
{
|
||||
// Set description.
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a positional parameter to the flag.
|
||||
* @param string $name Parameter name.
|
||||
* @param string $description Parameter description.
|
||||
* @return $this
|
||||
*/
|
||||
public function parameter(string $name, string $description): static
|
||||
{
|
||||
// Add a positional parameter to the next position, with given metadata.
|
||||
$this->parameters[] = (object) [
|
||||
"name" => $name,
|
||||
"description" => $description,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag as required (or not).
|
||||
* @param bool $require True to set the flag as required.
|
||||
* @return $this
|
||||
*/
|
||||
public function require(bool $require = true): static
|
||||
{
|
||||
$this->required = $require;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short version of the flag, if there is one.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getShort(): ?string
|
||||
{
|
||||
return $this->short;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flag description.
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flag parameters count.
|
||||
* @return int
|
||||
*/
|
||||
public function getParametersCount(): int
|
||||
{
|
||||
return count($this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the flag is required or not.
|
||||
* @return bool True if the flag is required.
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
}
|
71
src/Commands/ParameterDefinition.php
Normal file
71
src/Commands/ParameterDefinition.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Commands;
|
||||
|
||||
/**
|
||||
* A parameter definition object.
|
||||
*/
|
||||
class ParameterDefinition
|
||||
{
|
||||
/**
|
||||
* Parameter description.
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $description = null;
|
||||
|
||||
/**
|
||||
* Set if the parameter is optional or not. By default, it is NOT optional.
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $optional = false;
|
||||
|
||||
/**
|
||||
* Set the description of the parameter.
|
||||
* @param string $description The description of the parameter.
|
||||
* @return $this
|
||||
*/
|
||||
public function description(string $description): static
|
||||
{
|
||||
// Set description.
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter as optional (or not).
|
||||
* @param bool $optional True to set the parameter as optional.
|
||||
* @return $this
|
||||
*/
|
||||
public function optional(bool $optional = true): static
|
||||
{
|
||||
$this->optional = $optional;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter as required.
|
||||
* @return $this
|
||||
*/
|
||||
public function require(): static
|
||||
{
|
||||
return $this->optional(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter description.
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the parameter is required or not.
|
||||
* @return bool True if the parameter is required.
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return !$this->optional;
|
||||
}
|
||||
}
|
217
src/DefaultHelper.php
Normal file
217
src/DefaultHelper.php
Normal file
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Nest\Cli\Commands\CommandDefinition;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Default CLI helper.
|
||||
*/
|
||||
class DefaultHelper implements CliHelper
|
||||
{
|
||||
public const string REQUIRED_FIELD_PREFIX = "{";
|
||||
public const string REQUIRED_FIELD_SUFFIX = "}";
|
||||
|
||||
public const string OPTIONAL_FIELD_PREFIX = "[";
|
||||
public const string OPTIONAL_FIELD_SUFFIX = "]";
|
||||
|
||||
/**
|
||||
* The command context.
|
||||
* @var CommandContext
|
||||
*/
|
||||
private CommandContext $command;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[\Override] public function help(Cli $cli, CommandContext $command, ?Throwable $exception = null): void
|
||||
{
|
||||
// Save command context in class property.
|
||||
$this->command = $command;
|
||||
|
||||
if (!empty($exception))
|
||||
// If an exception has been thrown, show its message.
|
||||
Out::error($exception->getMessage());
|
||||
|
||||
echo "\n";
|
||||
|
||||
if (!empty($commandDefinition = $command->getCommandDefinition()))
|
||||
{ // A command definition have been provided. Showing help for this command especially.
|
||||
$this->showHelp($command->getFullCommand(), $commandDefinition, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo Out::BOLD_ATTRIBUTE."Available commands: ".Out::RESET_ATTRIBUTES."\n\n";
|
||||
foreach ($cli->getCommands() as $command => $commandDefinition)
|
||||
{ // Showing help for each command.
|
||||
// Dimmed list tick.
|
||||
echo Out::DIM_ATTRIBUTE;
|
||||
echo " - ";
|
||||
echo Out::RESET_ATTRIBUTES;
|
||||
// Command help.
|
||||
$this->showHelp($command, $commandDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help for a specific command definition.
|
||||
* @param string $command Current command to show.
|
||||
* @param CommandDefinition $commandDefinition Full command definition.
|
||||
* @param bool $details If true, help will show detailed information about parameters and flags.
|
||||
* @return void
|
||||
*/
|
||||
private function showHelp(string $command, CommandDefinition $commandDefinition, bool $details = false): void
|
||||
{
|
||||
if ($details)
|
||||
{ // Details requested.
|
||||
if ($commandDefinition->hasSubcommands())
|
||||
{ // If there are subcommands, showing all subcommands.
|
||||
$subcommands = $commandDefinition->getSubcommands();
|
||||
if (is_array($subcommands))
|
||||
{ // Showing subcommands array details.
|
||||
echo Out::BOLD_ATTRIBUTE." Subcommands: ".Out::RESET_ATTRIBUTES."\n\n";
|
||||
|
||||
foreach ($subcommands as $subcommand => $subcommandDefinition)
|
||||
{
|
||||
// Dimmed list tick.
|
||||
echo Out::DIM_ATTRIBUTE;
|
||||
echo " - ";
|
||||
echo Out::RESET_ATTRIBUTES;
|
||||
// Show subcommand help.
|
||||
$this->showHelp("$command:$subcommand", $subcommandDefinition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // Showing generic subcommands manual.
|
||||
echo "{$this->command->getExecutable()} $command";
|
||||
echo ":{}";
|
||||
echo "\n\n";
|
||||
echo Out::DIM_ATTRIBUTE." Customized subcommands are not documented.".Out::RESET_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
elseif ($commandDefinition->hasHandler())
|
||||
{ // If there is an handler, showing all parameters with details.
|
||||
echo "{$this->command->getExecutable()} $command";
|
||||
|
||||
$this->printParameters($commandDefinition);
|
||||
$this->printFlags($commandDefinition);
|
||||
|
||||
echo "\n\n";
|
||||
|
||||
if (!empty($commandDefinition->getParameters()))
|
||||
{ // Show full parameters details.
|
||||
echo Out::BOLD_ATTRIBUTE." Parameters: ".Out::RESET_ATTRIBUTES."\n\n";
|
||||
foreach ($commandDefinition->getParameters() as $parameter => $parameterDefinition)
|
||||
{ // Show details of each parameter.
|
||||
|
||||
// Dimmed list tick.
|
||||
echo Out::DIM_ATTRIBUTE;
|
||||
if (!$parameterDefinition->isRequired())
|
||||
echo " * (optional) ";
|
||||
else
|
||||
echo " - ";
|
||||
echo Out::RESET_ATTRIBUTES;
|
||||
|
||||
echo "$parameter: ".Out::ITALIC_ATTRIBUTE."{$parameterDefinition->getDescription()}".Out::RESET_ATTRIBUTES;
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
// Parameters end.
|
||||
echo "\n";
|
||||
}
|
||||
else
|
||||
echo Out::DIM_ATTRIBUTE." No parameters.\n".Out::RESET_ATTRIBUTES;
|
||||
|
||||
if (!empty($commandDefinition->getFlags()))
|
||||
{ // Show full flags details.
|
||||
// Show flags in yellow.
|
||||
echo Color::LightYellow->value;
|
||||
|
||||
// Show full flags details.
|
||||
echo Out::BOLD_ATTRIBUTE." Flags: ".Out::RESET_ATTRIBUTES.Color::LightYellow->value."\n";
|
||||
|
||||
foreach ($commandDefinition->getFlags() as $flag => $flagDefinition)
|
||||
{ // Show details of each parameter.
|
||||
|
||||
echo "\n";
|
||||
|
||||
$flagColor = $flagDefinition->isRequired() ? Color::LightRed->value : Color::LightYellow->value;
|
||||
|
||||
// Dimmed list tick.
|
||||
echo $flagColor.Out::DIM_ATTRIBUTE;
|
||||
echo " - ";
|
||||
if ($flagDefinition->isRequired()) echo "(required) ";
|
||||
echo Out::RESET_ATTRIBUTES.$flagColor;
|
||||
|
||||
echo "--$flag";
|
||||
if (!empty($short = $flagDefinition->getShort()))
|
||||
{ // If a short equivalent is defined, showing it.
|
||||
echo " (alt.: -$short)";
|
||||
}
|
||||
echo ": ".Out::ITALIC_ATTRIBUTE."{$flagDefinition->getDescription()}".Out::RESET_ATTRIBUTES.Color::LightYellow->value;
|
||||
}
|
||||
|
||||
// Flags end.
|
||||
echo Color::Default->value;
|
||||
}
|
||||
else
|
||||
echo Out::DIM_ATTRIBUTE." No flags.\n".Out::RESET_ATTRIBUTES;
|
||||
}
|
||||
else
|
||||
{ // Something is wrong with the definition.
|
||||
echo "\n";
|
||||
Out::error("Invalid command definition.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // Details not requested, simply show parameters and flags.
|
||||
echo "{$this->command->getExecutable()} $command";
|
||||
|
||||
$this->printParameters($commandDefinition);
|
||||
$this->printFlags($commandDefinition);
|
||||
|
||||
if (!empty($description = $commandDefinition->getDescription()))
|
||||
{
|
||||
echo "\n";
|
||||
echo Out::ITALIC_ATTRIBUTE." $description".Out::RESET_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Print parameters of a command.
|
||||
* @param CommandDefinition $commandDefinition Command definition.
|
||||
* @return void
|
||||
*/
|
||||
private function printParameters(CommandDefinition $commandDefinition): void
|
||||
{
|
||||
foreach ($commandDefinition->getParameters() as $parameter => $parameterDefinition)
|
||||
{ // Show every parameter according to its definition.
|
||||
echo " ";
|
||||
echo $parameterDefinition->isRequired() ? static::REQUIRED_FIELD_PREFIX : static::OPTIONAL_FIELD_PREFIX;
|
||||
echo $parameter;
|
||||
echo $parameterDefinition->isRequired() ? static::REQUIRED_FIELD_SUFFIX : static::OPTIONAL_FIELD_SUFFIX;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print flags of a command.
|
||||
* @param CommandDefinition $commandDefinition Command definition.
|
||||
* @return void
|
||||
*/
|
||||
private function printFlags(CommandDefinition $commandDefinition): void
|
||||
{
|
||||
foreach ($commandDefinition->getFlags() as $flag => $flagDefinition)
|
||||
{ // Show every flag according to its definition.
|
||||
echo " ";
|
||||
echo $flagDefinition->isRequired() ? static::REQUIRED_FIELD_PREFIX : static::OPTIONAL_FIELD_PREFIX;
|
||||
echo "--$flag";
|
||||
echo $flagDefinition->isRequired() ? static::REQUIRED_FIELD_SUFFIX : static::OPTIONAL_FIELD_SUFFIX;
|
||||
}
|
||||
}
|
||||
}
|
9
src/Exceptions/CliException.php
Normal file
9
src/Exceptions/CliException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions;
|
||||
|
||||
use Nest\Exceptions\Exception;
|
||||
|
||||
class CliException extends Exception
|
||||
{
|
||||
}
|
24
src/Exceptions/Command/CommandException.php
Normal file
24
src/Exceptions/Command/CommandException.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Nest\Cli\Exceptions\CliException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when parsing a command.
|
||||
*/
|
||||
class CommandException extends CliException
|
||||
{
|
||||
/**
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param string $message [optional] The Exception message to throw.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public CommandContext $commandContext, string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
22
src/Exceptions/Command/IncompleteCommandException.php
Normal file
22
src/Exceptions/Command/IncompleteCommandException.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when an incomplete command has been used.
|
||||
*/
|
||||
class IncompleteCommandException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Incomplete command.", $code, $previous);
|
||||
}
|
||||
}
|
23
src/Exceptions/Command/InvalidCommandException.php
Normal file
23
src/Exceptions/Command/InvalidCommandException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when an invalid command has been used.
|
||||
*/
|
||||
class InvalidCommandException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param string $invalidCommand Invalid command in context.
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $invalidCommand, CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Invalid command $this->invalidCommand in this context.", $code, $previous);
|
||||
}
|
||||
}
|
23
src/Exceptions/Command/InvalidCommandHandlerException.php
Normal file
23
src/Exceptions/Command/InvalidCommandHandlerException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandHandler;
|
||||
use Nest\Exceptions\Cli\CliException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a given command handler is not of the right type.
|
||||
*/
|
||||
class InvalidCommandHandlerException extends CliException
|
||||
{
|
||||
/**
|
||||
* @param string $commandHandlerClass Class of command handler with invalid type.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $commandHandlerClass, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("$this->commandHandlerClass must be of type ".CommandHandler::class, $code, $previous);
|
||||
}
|
||||
}
|
23
src/Exceptions/Command/InvalidParametersException.php
Normal file
23
src/Exceptions/Command/InvalidParametersException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when invalid command parameters have been provided.
|
||||
*/
|
||||
class InvalidParametersException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param string $message [optional] The Exception message to throw.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(CommandContext $commandContext, string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Invalid parameters".(!empty($message) ? ": $message" : "."), $code, $previous);
|
||||
}
|
||||
}
|
22
src/Exceptions/Command/MissingCommandException.php
Normal file
22
src/Exceptions/Command/MissingCommandException.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when no command have been provided.
|
||||
*/
|
||||
class MissingCommandException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Please provide a command to execute.", $code, $previous);
|
||||
}
|
||||
}
|
23
src/Exceptions/Command/MissingRequiredFlagException.php
Normal file
23
src/Exceptions/Command/MissingRequiredFlagException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a required flag is missing.
|
||||
*/
|
||||
class MissingRequiredFlagException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param string $flag Missing flag.
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $flag, CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "The required flag $this->flag is missing.", $code, $previous);
|
||||
}
|
||||
}
|
24
src/Exceptions/Command/NotEnoughParametersException.php
Normal file
24
src/Exceptions/Command/NotEnoughParametersException.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when not enough parameters have been provided.
|
||||
*/
|
||||
class NotEnoughParametersException extends InvalidParametersException
|
||||
{
|
||||
/**
|
||||
* @param int $providedCount Provided parameters count.
|
||||
* @param int $expectedCount Expected parameters count.
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public int $providedCount, public int $expectedCount, CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Not enough parameters: $this->providedCount provided, expected $this->expectedCount.", $code, $previous);
|
||||
}
|
||||
}
|
23
src/Exceptions/Command/UndefinedFlagException.php
Normal file
23
src/Exceptions/Command/UndefinedFlagException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when an undefined flag has been provided.
|
||||
*/
|
||||
class UndefinedFlagException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param string $flag Undefined flag.
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $flag, CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "Provided flag $this->flag is not defined for this command.", $code, $previous);
|
||||
}
|
||||
}
|
10
src/Exceptions/Command/UndefinedShortFlagException.php
Normal file
10
src/Exceptions/Command/UndefinedShortFlagException.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
/**
|
||||
* Exception thrown when an undefined short flag has been provided.
|
||||
*/
|
||||
class UndefinedShortFlagException extends UndefinedFlagException
|
||||
{
|
||||
}
|
23
src/Exceptions/Command/UndefinedSubcommandsException.php
Normal file
23
src/Exceptions/Command/UndefinedSubcommandsException.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions\Command;
|
||||
|
||||
use Nest\Cli\Commands\CommandContext;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when no subcommands are defined for the given command.
|
||||
*/
|
||||
class UndefinedSubcommandsException extends CommandException
|
||||
{
|
||||
/**
|
||||
* @param string $invalidCommand Command with no subcommands.
|
||||
* @param CommandContext $commandContext Command context.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $invalidCommand, CommandContext $commandContext, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($commandContext, "No defined subcommands for $this->invalidCommand.", $code, $previous);
|
||||
}
|
||||
}
|
21
src/Exceptions/FlagNotFoundException.php
Normal file
21
src/Exceptions/FlagNotFoundException.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a requested flag is not found.
|
||||
*/
|
||||
class FlagNotFoundException extends CliException
|
||||
{
|
||||
/**
|
||||
* @param string $flag Request flag (full) name.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $flag, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("$this->flag was not provided in the command.", $code, $previous);
|
||||
}
|
||||
}
|
20
src/Exceptions/IncompatibleCliHandlerSubcommands.php
Normal file
20
src/Exceptions/IncompatibleCliHandlerSubcommands.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a handler and subcommands are defined at the same time in a command definition.
|
||||
*/
|
||||
class IncompatibleCliHandlerSubcommands extends CliException
|
||||
{
|
||||
/**
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Cannot define a command handler and subcommands for the same command.", $code, $previous);
|
||||
}
|
||||
}
|
94
src/Out.php
Normal file
94
src/Out.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Cli;
|
||||
|
||||
/**
|
||||
* CLI out functions.
|
||||
*/
|
||||
class Out
|
||||
{
|
||||
/**
|
||||
* Reset attributes special character.
|
||||
*/
|
||||
public const string RESET_ATTRIBUTES = "\e[0m";
|
||||
/**
|
||||
* Bold attribute special character.
|
||||
*/
|
||||
public const string BOLD_ATTRIBUTE = "\e[1m";
|
||||
/**
|
||||
* Italic attribute special character.
|
||||
*/
|
||||
public const string ITALIC_ATTRIBUTE = "\e[3m";
|
||||
/**
|
||||
* Dim attribute special character.
|
||||
*/
|
||||
public const string DIM_ATTRIBUTE = "\e[2m";
|
||||
|
||||
/**
|
||||
* Print a message.
|
||||
* @param string $message Message to print.
|
||||
* @param string $symbol Prefix symbol.
|
||||
* @param Color $color Text color.
|
||||
* @param bool $bold Set text to bold.
|
||||
* @param bool $dim Set text to dim.
|
||||
* @return void
|
||||
*/
|
||||
public static function print(string $message, string $symbol = "❯", Color $color = Color::Default, bool $bold = false, bool $dim = false): void
|
||||
{
|
||||
echo
|
||||
// Set color and attributes.
|
||||
$color->value.($bold ? self::BOLD_ATTRIBUTE : "").($dim ? self::DIM_ATTRIBUTE : "").
|
||||
// Print message with its symbol.
|
||||
"$symbol $message".
|
||||
// Reset colors and attributes.
|
||||
Color::Default->value.self::RESET_ATTRIBUTES."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an informational message.
|
||||
* @param string $message Message to print.
|
||||
* @param bool $bold Set text to bold.
|
||||
* @param bool $dim Set text to dim.
|
||||
* @return void
|
||||
*/
|
||||
public static function info(string $message, bool $bold = false, bool $dim = false): void
|
||||
{
|
||||
self::print($message, "ℹ", Color::Cyan, $bold, $dim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a success message.
|
||||
* @param string $message Message to print.
|
||||
* @param bool $bold Set text to bold.
|
||||
* @param bool $dim Set text to dim.
|
||||
* @return void
|
||||
*/
|
||||
public static function success(string $message, bool $bold = false, bool $dim = false): void
|
||||
{
|
||||
self::print($message, "✔", Color::Green, $bold, $dim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a warning message.
|
||||
* @param string $message Message to print.
|
||||
* @param bool $bold Set text to bold.
|
||||
* @param bool $dim Set text to dim.
|
||||
* @return void
|
||||
*/
|
||||
public static function warning(string $message, bool $bold = false, bool $dim = false): void
|
||||
{
|
||||
self::print($message, "!", Color::Yellow, $bold, $dim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an error message.
|
||||
* @param string $message Message to print.
|
||||
* @param bool $bold Set text to bold.
|
||||
* @param bool $dim Set text to dim.
|
||||
* @return void
|
||||
*/
|
||||
public static function error(string $message, bool $bold = false, bool $dim = false): void
|
||||
{
|
||||
self::print($message, "‼", Color::Red, $bold, $dim);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue