diff --git a/.gitignore b/.gitignore index 9af8130..c7ffd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ vendor/ composer.lock -var/ \ No newline at end of file +var/ diff --git a/.travis.yml b/.travis.yml index 47134ff..07342d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,8 @@ language: php php: - - '7.3' - - '7.4' + - '8.0' before_script: - - yes '' | pecl install yaml - composer install script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5db8c..d360056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.0.0 - 2021-04-01 +### Added +- Support for `grizz-it/command` and `grizz-it/cli`. +- Absorbed remainder of `ulrack/command` package. +- Support for PHP8 + ## 1.2.1 - 2020-08-30 ### Fixed - Accidental move of dev packages. @@ -29,8 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The initial implementation of the package. # Versions -- [1.2.1 > Unreleased](https://github.com/ulrack/kernel/compare/1.2.1...HEAD) -- [1.2.0 > 1.2.1](https://github.com/ulrack/kernel/compare/1.2.0...1.2.1) -- [1.1.0 > 1.2.0](https://github.com/ulrack/kernel/compare/1.1.0...1.2.0) -- [1.0.1 > 1.1.0](https://github.com/ulrack/kernel/compare/1.0.1...1.1.0) -- [1.0.0 > 1.0.1](https://github.com/ulrack/kernel/compare/1.0.0...1.0.1) +- [2.0.0 > Unreleased](https://github.com/ulrack/cli-application/compare/2.0.0...HEAD) +- [1.2.1 > 2.0.0](https://github.com/ulrack/cli-application/compare/1.2.1...2.0.0) +- [1.2.0 > 1.2.1](https://github.com/ulrack/cli-application/compare/1.2.0...1.2.1) +- [1.1.0 > 1.2.0](https://github.com/ulrack/cli-application/compare/1.1.0...1.2.0) +- [1.0.1 > 1.1.0](https://github.com/ulrack/cli-application/compare/1.0.1...1.1.0) +- [1.0.0 > 1.0.1](https://github.com/ulrack/cli-application/compare/1.0.0...1.0.1) diff --git a/README.md b/README.md index d656e09..cb1d5f8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This package contains an application for the [Ulrack Kernel](https://github.com/ulrack/kernel) which implements the -[Ulrack Command](https://github.com/ulrack/command) package. +[Grizz-IT Command](https://github.com/grizz-it/command) package. ## Installation diff --git a/composer.json b/composer.json index 07a2a98..f446dca 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,13 @@ "prefer-stable": true, "minimum-stability": "stable", "require": { - "php": "^7.3", - "grizz-it/configuration": "^1.1", - "ulrack/command": "^2.0", - "ulrack/kernel": "^1.3" + "php": "^8.0", + "grizz-it/cli": "^1.0", + "grizz-it/command": "^1.0", + "grizz-it/configuration": "^1.3", + "grizz-it/services": "^1.0", + "grizz-it/validator": "^1.1", + "ulrack/kernel": "^2.0" }, "authors": [ { @@ -52,7 +55,7 @@ ] }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.5" } } diff --git a/configuration/parameters/cli-parameters.json b/configuration/parameters/cli-parameters.json deleted file mode 100644 index 09029f7..0000000 --- a/configuration/parameters/cli-parameters.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cli-theme": "${CLI_THEME}" -} \ No newline at end of file diff --git a/configuration/schema/command.schema.json b/configuration/schema/command.schema.json new file mode 100644 index 0000000..b0c67fb --- /dev/null +++ b/configuration/schema/command.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "command.schema.json", + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "service": { + "type": "string" + }, + "description": { + "type": "string" + }, + "flags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "long": { + "type": "string" + }, + "short": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "anyOf": [ + { + "required": ["long"] + }, + { + "required": ["short"] + } + ] + } + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "long": { + "type": "string" + }, + "short": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["string", "number", "array"] + }, + "hidden": { + "type": "boolean", + "default": false + }, + "options": { + "type": "array" + }, + "required": { + "type": "boolean", + "default": false + } + }, + "anyOf": [ + { + "required": ["long", "type"] + }, + { + "required": ["short", "type"] + } + ] + } + } + }, + "required": ["command", "service"] +} \ No newline at end of file diff --git a/configuration/services/command-core.services.json b/configuration/services/command-core.services.json index 50a640b..5a6ce51 100644 --- a/configuration/services/command-core.services.json +++ b/configuration/services/command-core.services.json @@ -1,78 +1,134 @@ { - "cli.input-factory": { - "class": "\\Ulrack\\Command\\Factory\\InputFactory" - }, - "cli.io-factory": { - "class": "\\Ulrack\\Cli\\Factory\\IoFactory" - }, - "cli.default-theme": { - "class": "\\Ulrack\\Cli\\Component\\Theme\\DefaultTheme", - "parameters": { - "themeGenerator": "@{services.cli.theme-generator}" - } - }, - "cli.theme-generator": { - "class": "\\Ulrack\\Cli\\Generator\\ThemeGenerator", - "parameters": { - "themeFactory": "@{services.cli.theme-factory}" - } - }, - "cli.theme-factory": { - "class": "\\Ulrack\\Cli\\Factory\\ThemeFactory", - "parameters": { - "ioFactory": "@{services.cli.io-factory}" - } - }, - "cli.element-factory": { - "class": "\\Ulrack\\Cli\\Factory\\ElementFactory", - "parameters": { - "theme": "@{services.cli.theme}", - "ioFactory": "@{services.cli.io-factory}" - } - }, - "cli.error-element-factory": { - "class": "\\Ulrack\\Cli\\Factory\\ElementFactory", - "parameters": { - "theme": "@{services.cli.theme}", - "ioFactory": "@{services.cli.io-factory}", - "useStderr": true - } - }, - "cli.form-factory": { - "class": "\\Ulrack\\Cli\\Factory\\FormFactory", - "parameters": { - "theme": "@{services.cli.theme}", - "ioFactory": "@{services.cli.io-factory}" - } - }, - "cli.form-generator": { - "class": "\\Ulrack\\Cli\\Generator\\FormGenerator", - "parameters": { - "formFactory": "@{services.cli.form-factory}", - "elementFactory": "@{services.cli.element-factory}" + "parameters": { + "cli-theme": "services.cli.default-theme", + "service.configuration.validation.services": { + "key": "services", + "schema": "services.schema.json" + }, + "service.configuration.validation.invocations": { + "key": "invocations", + "schema": "invocations.schema.json" + }, + "service.configuration.validation.tags": { + "key": "tags", + "schema": "tags.schema.json" + }, + "service.configuration.validation.triggers": { + "key": "triggers", + "schema": "triggers.schema.json" } }, - "cli.command-configuration": { - "class": "\\Ulrack\\Command\\Dao\\CommandConfiguration" + "triggers": { + "service.configuration.validation": {} }, - "cli.output": { - "class": "\\Ulrack\\Command\\Component\\Command\\Output", - "parameters": { - "formGenerator": "@{services.cli.form-generator}", - "ioFactory": "@{services.cli.io-factory}", - "theme": "@{services.cli.theme}", - "elementFactory": "@{services.cli.element-factory}" + "tags": { + "add.service.validation.services": { + "service": "parameters.service.configuration.validation.services", + "trigger": "triggers.service.configuration.validation" + }, + "add.service.validation.invocations": { + "service": "parameters.service.configuration.validation.invocations", + "trigger": "triggers.service.configuration.validation" + }, + "add.service.validation.tags": { + "service": "parameters.service.configuration.validation.tags", + "trigger": "triggers.service.configuration.validation" + }, + "add.service.validation.triggers": { + "service": "parameters.service.configuration.validation.triggers", + "trigger": "triggers.service.configuration.validation" } }, - "cli.command-router": { - "class": "\\Ulrack\\Command\\Component\\Router\\CommandRouter", - "parameters": { - "commandConfiguration": "@{services.cli.command-configuration}", - "serviceFactory": "@{services.cli.service-factory}", - "errorElementFactory": "@{services.cli.error-element-factory}", - "ioFactory": "@{services.cli.io-factory}", - "output": "@{services.cli.output}", - "formGenerator": "@{services.cli.form-generator}" + "services": { + "cli.input-factory": { + "class": "\\GrizzIt\\Command\\Factory\\InputFactory" + }, + "cli.io-factory": { + "class": "\\GrizzIt\\Cli\\Factory\\IoFactory" + }, + "cli.default-theme": { + "class": "\\GrizzIt\\Cli\\Component\\Theme\\DefaultTheme", + "parameters": { + "themeGenerator": "@{services.cli.theme-generator}" + } + }, + "cli.theme-generator": { + "class": "\\GrizzIt\\Cli\\Generator\\ThemeGenerator", + "parameters": { + "themeFactory": "@{services.cli.theme-factory}" + } + }, + "cli.theme-factory": { + "class": "\\GrizzIt\\Cli\\Factory\\ThemeFactory", + "parameters": { + "ioFactory": "@{services.cli.io-factory}" + } + }, + "cli.element-factory": { + "class": "\\GrizzIt\\Cli\\Factory\\ElementFactory", + "parameters": { + "theme": "@{internal.cli.theme}", + "ioFactory": "@{services.cli.io-factory}" + } + }, + "cli.error-element-factory": { + "class": "\\GrizzIt\\Cli\\Factory\\ElementFactory", + "parameters": { + "theme": "@{internal.cli.theme}", + "ioFactory": "@{services.cli.io-factory}", + "useStderr": true + } + }, + "cli.form-factory": { + "class": "\\GrizzIt\\Cli\\Factory\\FormFactory", + "parameters": { + "theme": "@{internal.cli.theme}", + "ioFactory": "@{services.cli.io-factory}" + } + }, + "cli.form-generator": { + "class": "\\GrizzIt\\Cli\\Generator\\FormGenerator", + "parameters": { + "formFactory": "@{services.cli.form-factory}", + "elementFactory": "@{services.cli.element-factory}" + } + }, + "cli.command-configuration": { + "class": "\\Ulrack\\CliApplication\\Dao\\CommandConfiguration" + }, + "cli.output": { + "class": "\\GrizzIt\\Command\\Component\\Command\\Output", + "parameters": { + "formGenerator": "@{services.cli.form-generator}", + "ioFactory": "@{services.cli.io-factory}", + "theme": "@{internal.cli.theme}", + "elementFactory": "@{services.cli.element-factory}" + } + }, + "cli.command-router": { + "class": "\\Ulrack\\CliApplication\\Component\\Router\\CommandRouter", + "parameters": { + "commandConfiguration": "@{services.cli.command-configuration}", + "serviceFactory": "@{internal.core.service.factory}", + "errorElementFactory": "@{services.cli.error-element-factory}", + "ioFactory": "@{services.cli.io-factory}", + "output": "@{services.cli.output}", + "formGenerator": "@{services.cli.form-generator}" + } + }, + "command.cache.clear": { + "class": "\\Ulrack\\CliApplication\\Command\\CacheClearCommand", + "parameters": { + "cacheManager": "@{internal.core.cache.manager}" + } + }, + "command.validate.configuration": { + "class": "\\Ulrack\\CliApplication\\Command\\ValidateConfigurationCommand", + "parameters": { + "configurationManager": "@{internal.core.configuration.manager}", + "validationManager": "@{internal.core.validation.manager}", + "additionalValidation": "@{triggers.service.configuration.validation}" + } } } } diff --git a/configuration/services/command.cache.clear.json b/configuration/services/command.cache.clear.json deleted file mode 100644 index 4a86c6a..0000000 --- a/configuration/services/command.cache.clear.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command.cache.clear": { - "class": "\\Ulrack\\CliApplication\\Command\\CacheClearCommand", - "parameters": { - "cacheManager": "@{services.core.cache.manager}" - } - } -} \ No newline at end of file diff --git a/configuration/services/command.validate.configuration.json b/configuration/services/command.validate.configuration.json deleted file mode 100644 index 46f4bb2..0000000 --- a/configuration/services/command.validate.configuration.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "command.validate.configuration": { - "class": "\\Ulrack\\CliApplication\\Command\\ValidateConfigurationCommand", - "parameters": { - "configurationManager": "@{services.core.configuration.manager}", - "validationManager": "@{services.core.validation.manager}" - } - } -} \ No newline at end of file diff --git a/src/Command/CacheClearCommand.php b/src/Command/CacheClearCommand.php index 51fd9b4..04d7292 100644 --- a/src/Command/CacheClearCommand.php +++ b/src/Command/CacheClearCommand.php @@ -7,9 +7,9 @@ namespace Ulrack\CliApplication\Command; -use Ulrack\Command\Common\Command\InputInterface; -use Ulrack\Command\Common\Command\OutputInterface; -use Ulrack\Command\Common\Command\CommandInterface; +use GrizzIt\Command\Common\Command\InputInterface; +use GrizzIt\Command\Common\Command\OutputInterface; +use GrizzIt\Command\Common\Command\CommandInterface; use Ulrack\Kernel\Common\Manager\CacheManagerInterface; class CacheClearCommand implements CommandInterface diff --git a/src/Command/HelpCommand.php b/src/Command/HelpCommand.php new file mode 100644 index 0000000..d95408c --- /dev/null +++ b/src/Command/HelpCommand.php @@ -0,0 +1,148 @@ +commandConfiguration = $commandConfiguration; + } + + /** + * Executes the command. + * + * @param InputInterface $input + * @param OutputInterface $output + * + * @return void + */ + public function __invoke( + InputInterface $input, + OutputInterface $output + ): void { + $output->outputText( + 'Command: ' . implode(' ', $input->getCommand()), + true, + 'title' + ); + + $description = $this->commandConfiguration->getDescription(); + if (!empty($description)) { + $output->outputText('Description: ' . $description); + } + + if (count($this->commandConfiguration->getParameters()) > 0) { + $output->writeLine(''); + + $output->outputText( + 'Parameters: ', + true, + 'title' + ); + + $output->outputExplainedList( + $this->constructParametersList() + ); + } + + if (count($this->commandConfiguration->getFlags()) > 0) { + $output->writeLine(''); + + $output->outputText( + 'Flags: ', + true, + 'title' + ); + + $output->outputExplainedList( + $this->constructFlagsList() + ); + } + } + + /** + * Constructs the explained flags list. + * + * @return array + */ + private function constructFlagsList(): array + { + $list = []; + + foreach ($this->commandConfiguration->getFlags() as $flag) { + $options = []; + + if (isset($flag['long'])) { + $options[] = $flag['long']; + } + + if (isset($flag['short'])) { + $options[] = $flag['short']; + } + + $list[sprintf( + '[%s]', + implode('|', $options) + )] = $flag['description'] ?? ''; + } + + return $list; + } + + /** + * Constructs the explained parameters list. + * + * @return array + */ + private function constructParametersList(): array + { + $list = []; + + foreach ($this->commandConfiguration->getParameters() as $parameter) { + $options = []; + + if (isset($parameter['long'])) { + $options[] = $parameter['long']; + } + + if (isset($parameter['short'])) { + $options[] = $parameter['short']; + } + + $list[sprintf( + '[%s](%s)%s', + implode('|', $options), + $parameter['type'], + isset($parameter['required']) && $parameter['required'] + ? '*' + : '' + )] = $parameter['description'] ?? ''; + } + + return $list; + } +} diff --git a/src/Command/ListCommandsCommand.php b/src/Command/ListCommandsCommand.php new file mode 100644 index 0000000..c35e10c --- /dev/null +++ b/src/Command/ListCommandsCommand.php @@ -0,0 +1,183 @@ +commandConfiguration = $commandConfiguration; + } + + /** + * Executes the command. + * + * @param InputInterface $input + * @param OutputInterface $output + * + * @return void + */ + public function __invoke( + InputInterface $input, + OutputInterface $output + ): void { + $output->outputText( + 'Command list: ' . implode(' ', $input->getCommand()), + true, + 'title' + ); + + $description = $this->commandConfiguration->getDescription(); + if (!empty($description)) { + $output->outputText('Description: ' . $description); + } + + // Output an empty line. + $output->writeLine(''); + + $output->outputExplainedList( + $this->constructCommandList($this->commandConfiguration), + 'command-explained-list-key', + 'command-explained-list-description' + ); + + if (count($this->commandConfiguration->getParameters()) > 0) { + $output->writeLine(''); + + $output->outputText( + 'Parameters: ', + true, + 'title' + ); + + $output->outputExplainedList( + $this->constructParametersList() + ); + } + + if (count($this->commandConfiguration->getFlags()) > 0) { + $output->writeLine(''); + + $output->outputText( + 'Flags: ', + true, + 'title' + ); + + $output->outputExplainedList( + $this->constructFlagsList() + ); + } + } + + /** + * Constructs the explained flags list. + * + * @return array + */ + private function constructFlagsList(): array + { + $list = []; + + foreach ($this->commandConfiguration->getFlags() as $flag) { + $options = []; + + if (isset($flag['long'])) { + $options[] = $flag['long']; + } + + if (isset($flag['short'])) { + $options[] = $flag['short']; + } + + $list[sprintf( + '[%s]', + implode('|', $options) + )] = $flag['description'] ?? ''; + } + + return $list; + } + + /** + * Constructs the explained parameters list. + * + * @return array + */ + private function constructParametersList(): array + { + $list = []; + + foreach ($this->commandConfiguration->getParameters() as $parameter) { + $options = []; + + if (isset($parameter['long'])) { + $options[] = $parameter['long']; + } + + if (isset($parameter['short'])) { + $options[] = $parameter['short']; + } + + $list[sprintf( + '[%s](%s)%s', + implode('|', $options), + $parameter['type'], + isset($parameter['required']) && $parameter['required'] + ? '*' + : '' + )] = $parameter['description'] ?? ''; + } + + return $list; + } + + /** + * Constructs the command list from the configuration. + * + * @param CommandConfigurationInterface $configuration + * @param string $prefix + * + * @return array + */ + private function constructCommandList( + CommandConfigurationInterface $configuration, + string $prefix = '' + ): array { + $list = []; + + foreach ($configuration->getCommands() as $command) { + $subConfiguration = $configuration->getCommand($command); + $list[$prefix . $command] = $subConfiguration->getDescription(); + $list = array_merge($list, $this->constructCommandList( + $subConfiguration, + $prefix . $command . '.' + )); + } + + return $list; + } +} diff --git a/src/Command/ValidateConfigurationCommand.php b/src/Command/ValidateConfigurationCommand.php index 5d9670c..645f605 100644 --- a/src/Command/ValidateConfigurationCommand.php +++ b/src/Command/ValidateConfigurationCommand.php @@ -7,9 +7,9 @@ namespace Ulrack\CliApplication\Command; -use Ulrack\Command\Common\Command\InputInterface; -use Ulrack\Command\Common\Command\OutputInterface; -use Ulrack\Command\Common\Command\CommandInterface; +use GrizzIt\Command\Common\Command\InputInterface; +use GrizzIt\Command\Common\Command\OutputInterface; +use GrizzIt\Command\Common\Command\CommandInterface; use Ulrack\Kernel\Common\Manager\ValidationManagerInterface; use Ulrack\Kernel\Common\Manager\ConfigurationManagerInterface; use Ulrack\CliApplication\Exception\UnpassedValidationException; @@ -21,14 +21,21 @@ class ValidateConfigurationCommand implements CommandInterface * * @var ConfigurationManagerInterface */ - private $configurationManager; + private ConfigurationManagerInterface $configurationManager; /** * Contains the validation manager. * * @var ValidationManagerInterface */ - private $validationManager; + private ValidationManagerInterface $validationManager; + + /** + * Contains the additional validation configuration. + * + * @var array[] + */ + private array $additionalValidation = []; /** * Constructor. @@ -38,10 +45,12 @@ class ValidateConfigurationCommand implements CommandInterface */ public function __construct( ConfigurationManagerInterface $configurationManager, - ValidationManagerInterface $validationManager + ValidationManagerInterface $validationManager, + array $additionalValidation ) { $this->configurationManager = $configurationManager; $this->validationManager = $validationManager; + $this->additionalValidation = $additionalValidation; } /** @@ -106,22 +115,22 @@ public function __invoke( } } - $errorMessages = array_merge($errorMessages, $this->specialValidation( - 'parameters', - 'parameters.schema.json', - $configRegistry, - $output - ), $this->specialValidation( - 'services', - 'services.schema.json', - $configRegistry, - $output - ), $this->specialValidation( - 'preferences', - 'preferences.schema.json', - $configRegistry, - $output - )); + foreach ($this->additionalValidation as $validation) { + if ( + is_array($validation) && + isset($validation['key'], $validation['schema']) + ) { + $errorMessages = array_merge( + $errorMessages, + $this->specialValidation( + $validation['key'], + $validation['schema'], + $configRegistry, + $output + ) + ); + } + } if (count($errorMessages) > 0) { throw new UnpassedValidationException(...$errorMessages); @@ -158,8 +167,8 @@ private function specialValidation( $validator = $this->validationManager->getValidatorFactory() ->createFromRemoteFile($schema); - if (isset($configRegistry[$key])) { - foreach ($configRegistry[$key] as $entryKey => $entry) { + if (isset($configRegistry['services'][$key])) { + foreach ($configRegistry['services'][$key] as $entryKey => $entry) { $output->writeLine( sprintf('Checking validation for: %s', $entryKey), 'text', diff --git a/src/Common/Dao/CommandConfigurationInterface.php b/src/Common/Dao/CommandConfigurationInterface.php new file mode 100644 index 0000000..4da7afb --- /dev/null +++ b/src/Common/Dao/CommandConfigurationInterface.php @@ -0,0 +1,77 @@ +getServiceFactory(); $themeKey = $serviceFactory->create('parameters.cli-theme'); - if ($themeKey === '${CLI_THEME}') { - $themeKey = 'services.cli.default-theme'; - } - $theme = $serviceFactory->create($themeKey)->getTheme(); $serviceManager->registerService('cli.theme', $theme); - $serviceManager->registerService('cli.service-factory', $serviceFactory); $this->loadCommands( $serviceFactory->create('services.cli.command-configuration'), - $serviceFactory->create('services.core.configuration.manager') + $serviceFactory->create('internal.core.configuration.manager') ->getConfigRegistry() ); diff --git a/src/Component/Command/Input.php b/src/Component/Command/Input.php new file mode 100644 index 0000000..f0751f3 --- /dev/null +++ b/src/Component/Command/Input.php @@ -0,0 +1,163 @@ +input = $input; + } + + /** + * Loads configuration into the input. + * + * @param CommandConfigurationInterface $commandConfiguration + * + * @return void + */ + public function loadConfiguration( + CommandConfigurationInterface $commandConfiguration + ): void { + $this->commandConfiguration = $commandConfiguration; + } + + /** + * Checks whether the flag is set. + * + * @param string $flag + * + * @return bool + */ + public function hasParameter(string $parameter): bool + { + if ($this->commandConfiguration !== null) { + foreach ( + $this->commandConfiguration + ->getParameters() as $parameterInput + ) { + $long = $parameterInput['long'] ?? ''; + $short = $parameterInput['short'] ?? ''; + if ($long === $parameter || $short === $parameter) { + return $this->input->hasParameter($long) + || $this->input->hasParameter($short); + } + } + } + + return $this->input->hasParameter($parameter); + } + + /** + * Sets the value of a parameter. + * + * @param string $parameter + * @param mixed $value + * + * @return void + */ + public function setParameter(string $parameter, $value): void + { + $this->input->setParameter($parameter, $value); + } + + /** + * Retrieves the parameter from the input. + * + * @param string $parameter + * + * @return mixed + */ + public function getParameter(string $parameter) + { + if ($this->commandConfiguration !== null) { + foreach ($this->commandConfiguration->getParameters() as $parameterInput) { + $long = $parameterInput['long'] ?? ''; + $short = $parameterInput['short'] ?? ''; + if ($long === $parameter || $short === $parameter) { + if ($this->input->hasParameter($long)) { + return $this->input->getParameter($long); + } elseif ($this->input->hasParameter($short)) { + return $this->input->getParameter($short); + } + } + } + } + + return $this->input->getParameter($parameter); + } + + /** + * Checks whether the flag is set. + * + * @param string $flag + * + * @return bool + */ + public function isSetFlag(string $flag): bool + { + if ($this->commandConfiguration !== null) { + foreach ($this->commandConfiguration->getFlags() as $configFlag) { + if ( + $configFlag['long'] === $flag + || $configFlag['short'] === $flag + ) { + return $this->input->isSetFlag($configFlag['long']) + || $this->input->isSetFlag($configFlag['short']); + } + } + } + + return $this->input->isSetFlag($flag); + } + + /** + * Returns the command. + * + * @return string[] + */ + public function getCommand(): array + { + return $this->input->getCommand(); + } + + /** + * Returns the parameters. + * + * @return array + */ + public function getParameters(): array + { + return $this->input->getParameters(); + } + + /** + * Returns the flags. + * + * @return array + */ + public function getFlags(): array + { + return $this->input->getFlags(); + } +} diff --git a/src/Component/Router/CommandRouter.php b/src/Component/Router/CommandRouter.php new file mode 100644 index 0000000..7efe80c --- /dev/null +++ b/src/Component/Router/CommandRouter.php @@ -0,0 +1,419 @@ +commandConfiguration = $commandConfiguration; + $this->serviceFactory = $serviceFactory; + $this->errorElementFactory = $errorElementFactory; + $this->ioFactory = $ioFactory; + $this->output = $output; + $this->formGenerator = $formGenerator; + } + + /** + * Resolves the input to a command, executes it and returns the exit code. + * + * @param InputInterface $input + * + * @return int + */ + public function __invoke(InputInterface $input): int + { + $command = $input->getCommand(); + $originalCommand = $command; + try { + $command = $this->findCommand($originalCommand); + if ($input instanceof Input) { + $input->loadConfiguration($command); + } + + if ($input->isSetFlag('verbose')) { + $this->output->setOutputMode( + OutputModeEnum::OUTPUT_MODE_VERBOSE() + ); + } + + if ($input->isSetFlag('quiet')) { + $this->output->setOutputMode( + OutputModeEnum::OUTPUT_MODE_QUIET() + ); + } + + if ($input->isSetFlag('no-interaction')) { + $this->ioFactory->setAllowReading(false); + } + + $serviceKey = $command->getService(); + if ($input->isSetFlag('help')) { + (new HelpCommand($command))->__invoke($input, $this->output); + + return 0; + } + + if ($serviceKey !== '') { + try { + foreach ( + $this->getMissingParameters( + $input, + $command + ) as $key => $value + ) { + $input->setParameter($key, $value); + } + } catch (MisconfiguredCommandException $exception) { + throw new CommandCanNotExecuteException( + $originalCommand, + $exception->getMessage() + ); + } + + /** @var CommandInterface $command */ + $this->serviceFactory + ->create($serviceKey) + ->__invoke($input, $this->output); + + return 0; + } + + if (count($command->getCommands()) > 0) { + (new ListCommandsCommand($command))->__invoke( + $input, + $this->output + ); + + return 0; + } + + throw new CommandCanNotExecuteException( + $originalCommand, + 'Service definition not configured.' + ); + } catch (Throwable $exception) { + $this->errorElementFactory->createBlock( + substr($exception->getMessage(), 0, 1000), + 'error-block' + )->render(); + + $code = $exception->getCode(); + + $trace = $exception->getTraceAsString(); + $i = 0; + $this->output->writeLine( + 'Previous exceptions:', + 'text', + true + ); + + while ($exception = $exception->getPrevious()) { + $i++; + $this->output->writeLine( + sprintf( + 'Previous %d: %s', + $i, + $exception->getMessage() + ), + 'text', + true + ); + } + + $this->output->writeLine( + '', + 'text', + true + ); + + $this->output->writeLine( + sprintf('Trace: %s', $trace), + 'text', + true + ); + + return is_string($code) || !$code ? 1 : $code; + } + } + + /** + * Resolves the command iteratively. + * + * @param array $command + * + * @return CommandConfigurationInterface + * + * @throws CommandNotFoundException When the command can not be found. + */ + private function findCommand( + array $command + ): CommandConfigurationInterface { + $configuration = $this->commandConfiguration; + $originalCommand = $command; + foreach ($command as $key => $item) { + if ($configuration->hasCommand($item)) { + $configuration = $configuration->getCommand($item); + + continue; + } + + throw new CommandNotFoundException($originalCommand, $key); + } + + return $configuration; + } + + /** + * Generates a form asks the user to fill in the blanks. + * + * @param InputInterface $input + * @param CommandConfigurationInterface $configuration + * + * @return array + * + * @throws MisconfiguredCommandException When the command configuration is incorrect. + */ + private function getMissingParameters( + InputInterface $input, + CommandConfigurationInterface $configuration + ): array { + $missing = false; + $this->formGenerator->init( + 'Missing parameters', + 'The following parameters were required and missing. ' . + 'Please fill them in before execution procceeds.' + ); + + foreach ($configuration->getParameters() as $parameter) { + if ( + isset($parameter['required']) + && $parameter['required'] + ) { + if ( + !$input->hasParameter( + $parameter['long'] ?? $parameter['short'] + ) + ) { + $missing = true; + if ( + isset($parameter['hidden']) + && $parameter['hidden'] + ) { + $this->createHiddenField($parameter); + + continue; + } elseif ( + isset($parameter['options']) + && is_array($parameter['options']) + ) { + $this->createAutocompletingField($parameter); + + continue; + } + + $this->createOpenField($parameter); + } + } + } + + $form = $this->formGenerator->getForm(); + + if ($missing) { + $form->render(); + + return $form->getInput(); + } + + return []; + } + + /** + * Creates the hidden field for the form generator. + * + * @param array $parameter + * + * @return void + * + * @throws MisconfiguredCommandException When the parameter is not configured correctly. + */ + private function createHiddenField(array $parameter): void + { + if ($parameter['type'] === 'array') { + $this->formGenerator->addHiddenArrayField( + $parameter['long'] ?? $parameter['short'], + true + ); + + return; + } elseif (in_array($parameter['type'], ['string', 'number'])) { + $this->formGenerator->addHiddenField( + $parameter['long'] ?? $parameter['short'], + true, + sprintf( + 'This field is required, and must be a %s', + $parameter['type'] + ), + $parameter['type'] === 'string' + ? new StringValidator() + : new PatternValidator('[0-9]+') + ); + + return; + } + + throw new MisconfiguredCommandException(); + } + + /** + * Creates the autocompleting field for the form generator. + * + * @param array $parameter + * + * @return void + * + * @throws MisconfiguredCommandException When the parameter is not configured correctly. + */ + private function createAutocompletingField(array $parameter): void + { + if ($parameter['type'] === 'array') { + $this->formGenerator->addAutocompletingArrayField( + $parameter['long'] ?? $parameter['short'], + $parameter['options'], + true + ); + + return; + } elseif (in_array($parameter['type'], ['string', 'number'])) { + $this->formGenerator->addAutocompletingField( + $parameter['long'] ?? $parameter['short'], + $parameter['options'], + true + ); + + return; + } + + throw new MisconfiguredCommandException(); + } + + /** + * Creates the autocompleting field for the form generator. + * + * @param array $parameter + * + * @return void + * + * @throws MisconfiguredCommandException When the parameter is not configured correctly. + */ + private function createOpenField(array $parameter): void + { + if ($parameter['type'] === 'array') { + $this->formGenerator->addOpenArrayField( + $parameter['long'] ?? $parameter['short'], + true + ); + + return; + } elseif (in_array($parameter['type'], ['string', 'number'])) { + $this->formGenerator->addOpenField( + $parameter['long'] ?? $parameter['short'], + true, + sprintf( + 'This field is required, and must be a %s', + $parameter['type'] + ), + $parameter['type'] === 'string' + ? new StringValidator() + : new NumberValidator() + ); + + return; + } + + throw new MisconfiguredCommandException(); + } +} diff --git a/src/Dao/CommandConfiguration.php b/src/Dao/CommandConfiguration.php new file mode 100644 index 0000000..cf820b5 --- /dev/null +++ b/src/Dao/CommandConfiguration.php @@ -0,0 +1,181 @@ +service = $service; + $this->description = $description; + $this->parameters = $parameters; + $this->flags = array_merge( + $flags, + [ + [ + 'long' => 'help', + 'short' => 'h', + 'description' => 'Explains the command.' + ], + [ + 'long' => 'no-interaction', + 'short' => 'ni', + 'description' => 'Prevents interaction during the execution of a command.' + ], + [ + 'long' => 'verbose', + 'short' => 'v', + 'description' => 'Displays verbose output for a command.' + ], + [ + 'long' => 'quiet', + 'short' => 'q', + 'description' => 'Silences all output.' + ] + ] + ); + } + + /** + * Adds a nested command configuration to the configuration. + * + * @param string $command + * @param CommandConfigurationInterface $configuration + * + * @return void + */ + public function addCommandConfiguration( + string $command, + CommandConfigurationInterface $configuration + ): void { + $this->commands[$command] = $configuration; + } + + /** + * Retrieves the allowed flags for the command. + * + * @return array + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * Retrieves the allowed parameters. + * + * @return array + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * Retrieves a command configuration nested inside the configuration. + * + * @param string $command + * + * @return CommandConfigurationInterface + */ + public function getCommand(string $command): CommandConfigurationInterface + { + return $this->commands[$command]; + } + + /** + * Retrieves a command configuration nested inside the configuration. + * + * @param string $command + * + * @return bool + */ + public function hasCommand(string $command): bool + { + return isset($this->commands[$command]); + } + + /** + * Retrieves the service key. + * + * @return string + */ + public function getService(): string + { + return $this->service; + } + + /** + * Retrieves all nested commands. + * + * @return string[] + */ + public function getCommands(): array + { + return array_keys($this->commands); + } + + /** + * Retrieves the description for the command configuration. + * + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/src/Exception/CommandCanNotExecuteException.php b/src/Exception/CommandCanNotExecuteException.php new file mode 100644 index 0000000..7c82a3b --- /dev/null +++ b/src/Exception/CommandCanNotExecuteException.php @@ -0,0 +1,31 @@ +createMock(CommandConfigurationInterface::class); + $subject = new HelpCommand($commandConfiguration); + + $input = $this->createMock(InputInterface::class); + $output = $this->createMock(OutputInterface::class); + + $commandConfiguration->expects(static::once()) + ->method('getDescription') + ->willReturn('foo'); + + $commandConfiguration->expects(static::exactly(2)) + ->method('getParameters') + ->willReturn([ + [ + 'long' => 'foo', + 'short' => 'f', + 'type' => 'string', + 'required' => true, + 'description' => 'foo description' + ], + [ + 'long' => 'bar', + 'short' => 'b', + 'type' => 'number', + 'required' => false, + 'description' => 'foo description' + ] + ]); + + $commandConfiguration->expects(static::exactly(2)) + ->method('getFlags') + ->willReturn([ + [ + 'long' => 'baz', + 'short' => 'b', + 'description' => 'baz description' + ], + [ + 'long' => 'qux', + 'short' => 'q', + 'description' => 'qux description' + ] + ]); + + $subject->__invoke($input, $output); + } +} diff --git a/tests/Command/ListCommandsCommandTest.php b/tests/Command/ListCommandsCommandTest.php new file mode 100644 index 0000000..647e29e --- /dev/null +++ b/tests/Command/ListCommandsCommandTest.php @@ -0,0 +1,95 @@ +createMock(CommandConfigurationInterface::class); + $subject = new ListCommandsCommand($commandConfiguration); + + $input = $this->createMock(InputInterface::class); + $output = $this->createMock(OutputInterface::class); + + $commandConfiguration->expects(static::once()) + ->method('getDescription') + ->willReturn('foo'); + + $commandConfiguration->expects(static::once()) + ->method('getCommands') + ->willReturn(['foo']); + + $commandConfiguration->expects(static::once()) + ->method('getCommand') + ->with('foo') + ->willReturn( + $this->createConfiguredMock( + CommandConfigurationInterface::class, + [ + 'getDescription' => 'foo description', + 'getCommands' => [] + ] + ) + ); + + $commandConfiguration->expects(static::exactly(2)) + ->method('getParameters') + ->willReturn([ + [ + 'long' => 'foo', + 'short' => 'f', + 'type' => 'string', + 'required' => true, + 'description' => 'foo description' + ], + [ + 'long' => 'bar', + 'short' => 'b', + 'type' => 'number', + 'required' => false, + 'description' => 'foo description' + ] + ]); + + $commandConfiguration->expects(static::exactly(2)) + ->method('getFlags') + ->willReturn([ + [ + 'long' => 'baz', + 'short' => 'b', + 'description' => 'baz description' + ], + [ + 'long' => 'qux', + 'short' => 'q', + 'description' => 'qux description' + ] + ]); + + $subject->__invoke($input, $output); + } +} diff --git a/tests/Command/ValidateConfigurationCommandTest.php b/tests/Command/ValidateConfigurationCommandTest.php index 5363e92..6db09bc 100644 --- a/tests/Command/ValidateConfigurationCommandTest.php +++ b/tests/Command/ValidateConfigurationCommandTest.php @@ -9,11 +9,11 @@ use PHPUnit\Framework\TestCase; use GrizzIt\Validator\Common\ValidatorInterface; -use Ulrack\Command\Common\Command\InputInterface; -use Ulrack\Command\Common\Command\OutputInterface; +use GrizzIt\Command\Common\Command\InputInterface; +use GrizzIt\Command\Common\Command\OutputInterface; use GrizzIt\Configuration\Common\RegistryInterface; use Ulrack\Kernel\Common\Manager\ValidationManagerInterface; -use Ulrack\JsonSchema\Common\SchemaValidatorFactoryInterface; +use GrizzIt\JsonSchema\Common\SchemaValidatorFactoryInterface; use Ulrack\CliApplication\Command\ValidateConfigurationCommand; use Ulrack\Kernel\Common\Manager\ConfigurationManagerInterface; use Ulrack\CliApplication\Exception\UnpassedValidationException; @@ -35,10 +35,19 @@ public function testCommand(): void { $input = $this->createMock(InputInterface::class); $output = $this->createMock(OutputInterface::class); - $configurationManager = $this->createMock(ConfigurationManagerInterface::class); - $validationManager = $this->createMock(ValidationManagerInterface::class); + $configurationManager = $this->createMock( + ConfigurationManagerInterface::class + ); + + $validationManager = $this->createMock( + ValidationManagerInterface::class + ); + $configRegistry = $this->createMock(RegistryInterface::class); - $validatorFactory = $this->createMock(SchemaValidatorFactoryInterface::class); + $validatorFactory = $this->createMock( + SchemaValidatorFactoryInterface::class + ); + $validator = $this->createMock(ValidatorInterface::class); $configurationManager->expects(static::once()) @@ -48,30 +57,40 @@ public function testCommand(): void $configRegistry->expects(static::once()) ->method('toArray') ->willReturn([ - 'foo' => [ - [ - '$schema' => 'foo.json', - 'bar' => 'baz' + 'services' => [ + 'foo' => [ + [ + '$schema' => 'foo.json', + 'bar' => 'baz' + ] ] ] ]); $subject = new ValidateConfigurationCommand( $configurationManager, - $validationManager + $validationManager, + [ + [ + 'key' => 'foo', + 'schema' => 'foo.json' + ], + [ + 'key' => 'services', + 'schema' => 'services.schema.json' + ] + ] ); - $validationManager->expects(static::exactly(4)) + $validationManager->expects(static::exactly(3)) ->method('getValidatorFactory') ->willReturn($validatorFactory); - $validatorFactory->expects(static::exactly(4)) + $validatorFactory->expects(static::exactly(2)) ->method('createFromRemoteFile') ->withConsecutive( ['foo.json'], - ['parameters.schema.json'], - ['services.schema.json'], - ['preferences.schema.json'] + ['services.schema.json'] )->willReturn($validator); $validator->expects(static::once()) @@ -95,9 +114,20 @@ public function testCommandException(): void { $input = $this->createMock(InputInterface::class); $output = $this->createMock(OutputInterface::class); - $configurationManager = $this->createMock(ConfigurationManagerInterface::class); - $validationManager = $this->createMock(ValidationManagerInterface::class); + $configurationManager = $this->createMock( + ConfigurationManagerInterface::class + ); + + $validationManager = $this->createMock( + ValidationManagerInterface::class + ); + + $validatorFactory = $this->createMock( + SchemaValidatorFactoryInterface::class + ); + $configRegistry = $this->createMock(RegistryInterface::class); + $validator = $this->createMock(ValidatorInterface::class); $configurationManager->expects(static::once()) ->method('getConfigRegistry') @@ -106,8 +136,10 @@ public function testCommandException(): void $configRegistry->expects(static::once()) ->method('toArray') ->willReturn([ - 'parameters' => [ - ['foo' => 'bar'] + 'services' => [ + 'parameters' => [ + ['foo' => 'bar'] + ] ], 'foo' => [ [ @@ -117,9 +149,30 @@ public function testCommandException(): void ] ]); + $validationManager->expects(static::exactly(2)) + ->method('getValidatorFactory') + ->willReturn($validatorFactory); + + $validatorFactory->expects(static::exactly(2)) + ->method('createFromRemoteFile') + ->withConsecutive( + ['foo.json'], + ['parameters.schema.json'] + )->willReturn($validator); + + $validator->expects(static::exactly(2)) + ->method('__invoke') + ->willReturn(false); + $subject = new ValidateConfigurationCommand( $configurationManager, - $validationManager + $validationManager, + [ + [ + 'key' => 'parameters', + 'schema' => 'parameters.schema.json' + ] + ] ); $this->expectException(UnpassedValidationException::class); diff --git a/tests/Component/Application/CliApplicationTest.php b/tests/Component/Application/CliApplicationTest.php index 20d29d2..103dae8 100644 --- a/tests/Component/Application/CliApplicationTest.php +++ b/tests/Component/Application/CliApplicationTest.php @@ -8,15 +8,15 @@ namespace Ulrack\CliApplication\Tests\Component\Application; use PHPUnit\Framework\TestCase; -use Ulrack\Command\Factory\InputFactory; -use Ulrack\Command\Common\Router\RouterInterface; +use GrizzIt\Command\Factory\InputFactory; use GrizzIt\Configuration\Common\RegistryInterface; -use Ulrack\Services\Common\ServiceFactoryInterface; -use Ulrack\Cli\Common\Theme\ApplicationThemeInterface; +use GrizzIt\Cli\Common\Theme\ApplicationThemeInterface; +use Ulrack\CliApplication\Common\Router\RouterInterface; use Ulrack\Kernel\Common\Manager\ServiceManagerInterface; -use Ulrack\Command\Common\Dao\CommandConfigurationInterface; +use GrizzIt\Services\Common\Factory\ServiceFactoryInterface; use Ulrack\CliApplication\Component\Application\CliApplication; use Ulrack\Kernel\Common\Manager\ConfigurationManagerInterface; +use Ulrack\CliApplication\Common\Dao\CommandConfigurationInterface; /** * @coversDefaultClass \Ulrack\CliApplication\Component\Application\CliApplication @@ -50,11 +50,11 @@ public function testApplication(): void ['parameters.cli-theme'], ['services.cli.default-theme'], ['services.cli.command-configuration'], - ['services.core.configuration.manager'], + ['internal.core.configuration.manager'], ['services.cli.command-router'], ['services.cli.input-factory'] )->willReturnOnConsecutiveCalls( - '${CLI_THEME}', + 'services.cli.default-theme', $this->createMock(ApplicationThemeInterface::class), $this->createMock(CommandConfigurationInterface::class), $configurationManager, diff --git a/tests/Component/Application/Command/InputTest.php b/tests/Component/Application/Command/InputTest.php new file mode 100644 index 0000000..33d14c5 --- /dev/null +++ b/tests/Component/Application/Command/InputTest.php @@ -0,0 +1,134 @@ + 'baz']; + $flags = ['qux']; + $subject = new Input(new GrizzItInput($command, $parameters, $flags)); + + $this->assertEquals($command, $subject->getCommand()); + $this->assertEquals($parameters, $subject->getParameters()); + $this->assertEquals($flags, $subject->getFlags()); + $this->assertEquals(true, $subject->isSetFlag('qux')); + $this->assertEquals(true, $subject->hasParameter('bar')); + $this->assertEquals('baz', $subject->getParameter('bar')); + } + + /** + * Test if the variable is going to be set correctly. + * + * @covers ::hasParameter + * @covers ::setParameter + * @covers ::__construct + * + * @return void + */ + public function testSetter(): void + { + $subject = new Input(new GrizzItInput([], [], [])); + + $this->assertEquals(false, $subject->hasParameter('baz')); + $subject->setParameter('baz', 'foo'); + $this->assertEquals(true, $subject->hasParameter('baz')); + } + + /** + * Test if the command configuration works as expected. + * + * @covers ::loadConfiguration + * @covers ::hasParameter + * @covers ::isSetFlag + * @covers ::getParameter + * @covers ::__construct + * + * @return void + */ + public function testLoadingCommandConfiguration(): void + { + $command = ['foo']; + $parameters = ['bar' => 'baz', 'ba' => 'b']; + $flags = ['qux']; + $subject = new Input(new GrizzItInput($command, $parameters, $flags)); + + // Pre configuration checks. + $this->assertEquals(false, $subject->hasParameter('b')); + $this->assertEquals(null, $subject->getParameter('b')); + $this->assertEquals(false, $subject->hasParameter('baz')); + $this->assertEquals(null, $subject->getParameter('baz')); + $this->assertEquals(false, $subject->isSetFlag('q')); + + // Add the configuration. + $commandConfiguration = $this->createMock( + CommandConfigurationInterface::class + ); + + $commandConfiguration->expects(static::exactly(4)) + ->method('getParameters') + ->willReturn([ + [ + 'long' => 'bar', + 'short' => 'b', + 'type' => 'number', + 'required' => false, + 'description' => 'bar description' + ], + [ + 'long' => 'baz', + 'short' => 'ba', + 'type' => 'string', + 'required' => true, + 'description' => 'baz description' + ] + ]); + + $commandConfiguration->expects(static::once()) + ->method('getFlags') + ->willReturn([ + [ + 'long' => 'qux', + 'short' => 'q', + 'description' => 'qux description' + ] + ]); + + $subject->loadConfiguration($commandConfiguration); + + // Verify that the aliases can now be resolved. + $this->assertEquals(true, $subject->hasParameter('b')); + $this->assertEquals(true, $subject->hasParameter('baz')); + $this->assertEquals(true, $subject->isSetFlag('q')); + $this->assertEquals('baz', $subject->getParameter('b')); + $this->assertEquals('b', $subject->getParameter('baz')); + } +} diff --git a/tests/Component/Application/Router/CommandRouterTest.php b/tests/Component/Application/Router/CommandRouterTest.php new file mode 100644 index 0000000..ff0f0f2 --- /dev/null +++ b/tests/Component/Application/Router/CommandRouterTest.php @@ -0,0 +1,263 @@ +createMock(ServiceFactoryInterface::class); + $errorElementFactory = $this->createMock(ElementFactoryInterface::class); + $ioFactory = $this->createMock(IoFactoryInterface::class); + $output = $this->createMock(OutputInterface::class); + $formGenerator = $this->createMock(FormGeneratorInterface::class); + $subject = new CommandRouter( + $commandConfiguration, + $serviceFactory, + $errorElementFactory, + $ioFactory, + $output, + $formGenerator + ); + + $form = $this->createMock(FormInterface::class); + $formGenerator->method('getForm')->willReturn($form); + $form->method('getInput')->willReturn(['foo' => 'bar']); + $serviceFactory->method('create')->willReturn( + $this->createMock(CommandInterface::class) + ); + + $this->assertEquals($expected, $subject->__invoke($input)); + } + + /** + * Provides configurations for resolving routes. + * + * @return array + */ + public function routingProvider(): array + { + $fooConfig = new CommandConfiguration(); + $fooConfig->addCommandConfiguration( + 'foo', + new CommandConfiguration() + ); + + return [ + // Command not execute test + [ + $this->createMock(UlrackInput::class), + $this->createMock(CommandConfigurationInterface::class), + 126 + ], + // Show help command + [ + new UlrackInput(new Input([], [], ['help', 'verbose'])), + $this->createMock(CommandConfigurationInterface::class), + 0 + ], + // Show list command + [ + new Input(), + $fooConfig, + 0 + ], + // Command not found in command + [ + new UlrackInput(new Input(['foo', 'bar'], [], ['quiet'])), + $fooConfig, + 127 + ], + // Missing open parameter with form. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'foo', + 'required' => true + ]]), + 126 + ], + // Missing hidden parameter with form. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'foo', + 'required' => true, + 'hidden' => true, + ]]), + 126 + ], + // Missing option parameter with form. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'foo', + 'required' => true, + 'options' => [], + ]]), + 126 + ], + // Missing option no interaction. + [ + new UlrackInput(new Input(['foo'], [], ['no-interaction', 'quiet'])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'number', + 'required' => true, + ]]), + 1 + ], + // Missing open option. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'string', + 'required' => true, + ]]), + 0 + ], + // Missing array option. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'array', + 'required' => true, + ]]), + 0 + ], + // Missing autocompleting option. + [ + new Input(['foo'], [], []), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'string', + 'required' => true, + 'options' => [] + ]]), + 0 + ], + // Missing array autocompleting option. + [ + new Input(['foo'], [], []), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'array', + 'required' => true, + 'options' => [] + ]]), + 0 + ], + // Missing hidden option. + [ + new Input(['foo'], [], []), + $this->createCommandConfiguration('foo', 'bar', [[ + 'long' => 'foo', + 'type' => 'string', + 'required' => true, + 'hidden' => true + ]]), + 0 + ], + // Missing hidden array option. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'short' => 'foo', + 'type' => 'array', + 'required' => true, + 'hidden' => true + ]]), + 0 + ], + // Nothing missing. + [ + new UlrackInput(new Input(['foo'], [], [])), + $this->createCommandConfiguration('foo', 'bar', [[ + 'short' => 'foo', + 'type' => 'array', + 'required' => false, + 'hidden' => true + ]]), + 0 + ], + ]; + } + + /** + * Creates a command configuration. + * + * @param string $name + * @param string $service + * @param array $config + * + * @return CommandConfigurationInterface + */ + private function createCommandConfiguration( + string $name, + string $service, + array $configuration + ): CommandConfigurationInterface { + $config = new CommandConfiguration(); + $config->addCommandConfiguration( + $name, + new CommandConfiguration( + $service, + 'description', + $configuration + ) + ); + + return $config; + } +} diff --git a/tests/Dao/CommandConfigurationTest.php b/tests/Dao/CommandConfigurationTest.php new file mode 100644 index 0000000..89bb8d2 --- /dev/null +++ b/tests/Dao/CommandConfigurationTest.php @@ -0,0 +1,67 @@ + 'value']; + $flags = [ + [ + 'long' => 'flag', + 'short' => 'f', + 'description' => 'A flag' + ] + ]; + + $allFlags = array_merge( + $flags, + (new CommandConfiguration())->getFlags() + ); + + $subject = new CommandConfiguration($service, $description, $parameters, $flags); + + $this->assertEquals($allFlags, $subject->getFlags()); + $this->assertEquals($parameters, $subject->getParameters()); + $this->assertEquals(false, $subject->hasCommand('command')); + $this->assertEquals($service, $subject->getService()); + $this->assertEquals([], $subject->getCommands()); + $this->assertEquals($description, $subject->getDescription()); + + $command = 'command'; + $configuration = $this->createMock(CommandConfigurationInterface::class); + $subject->addCommandConfiguration($command, $configuration); + + $this->assertEquals(true, $subject->hasCommand('command')); + $this->assertEquals($configuration, $subject->getCommand('command')); + $this->assertEquals([$command], $subject->getCommands()); + } +}