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…
	
	Add table
		
		Reference in a new issue