diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1ac0c..d1fac78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,66 +3,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [1.2.2][] -#### Changed -- Minor improvement to logic in `AbstractInputHandler::validateInput()`. Ensures that an input with a validator, but with a default value and no user suplied input, will have the default value used. - -## [1.2.1][] -#### Changed -- Updated `InputHandlerFactory` and `InputTypeFactory` to work with changes in `pointybeard/helpers-foundation-factory` 1.0.2 - -## [1.2.0][] -#### Added -- Added `InputTypeFilterIterator` class -- Added `UnrecognisedInputException` exception - -#### Changed -- `InputCollection` now implements `Iterator` and `Countable` (implementing required methods) -- Removed use of `$type` in `InputCollection` -- Added `InputCollection::getItemsExcludeByType()` -- `InputCollection::getItemsByType()` and `InputCollection::getItems()` now returns an `Iterator` -- Renamed `InputCollection::append()` to `add()` and added `$position` flag -- Added `POSITION_APPEND` and `POSITION_PREPEND` flags to `InputCollection` -- Made `getCollection()`, `getInput()`, `find()`, and `validate()` in `AbstractInputHandler` final -- Removed all categorisation of items by type in `AbstractInputHandler::$input` -- Abstracted most of `AbstractInputHandler::validate()` into it's own protected method called `validateInput()` -- Removed `$skipValidation` argument from `AbstractInputHandler::bind()` and relaced with `$flags` -- Added `FLAG_BIND_SKIP_VALIDATION`, `FLAG_VALIDATION_SKIP_REQUIRED`, `FLAG_VALIDATION_SKIP_CUSTOM`, and `FLAG_VALIDATION_SKIP_UNRECOGNISED` flags to `AbstractInputHandler` -- Added check in `AbstractInputHandler::validate()` to look for unrecognised options and arguments -- Removed `InputHandlerFactory::FLAG_SKIP_VALIDATION` from `InputHandlerFactory` -- Passing flags in call from `InputHandlerFactory::build()` to `AbstractInputHandler::bind()` -- Updated `InputHandlerInterface::bind()` and `validate()` methods to support flags - -## [1.1.4][] -#### Fixed -- Fixed misnamed variable in `InputCollection::merge()` - -## [1.1.3][] -#### Fixed -- Fixed logic bug that prevented `$index` and `$type` from being set in `InputCollection::append()`. This means replaceing items in an `InputCollection` now works as expected - -## [1.1.2][] -#### Added -- Added `InputValidationFailedException` exception -- Added `InputTypeInterface::getDisplayName()` method to standardise how the name of an `InputTypeInterface` class wants to display it's name - -#### Changed -- Updated validation logic for inputs that have a validator, no default, and are not set -- Throwing `InputValidationFailedException` exception when validation fails -- Updated `RequiredInputMissingException` and `RequiredInputMissingValueException` exceptions to use `InputTypeInterface::getDisplayName()` when producing their message -- Removed unused `RequiredArgumentMissingException` exception - -## [1.1.1][] -#### Changed -- `AbstractInputHandler::find()` returns NULL if it cannot find any input with the supplied name. It is easier to test for NULL than it is to catch an exception +**View all [Unreleased][] changes here** ## [1.1.0][] #### Added -- Expanded input types to include `Flag`, `IncrementingFlag`, and `LongOption` +- Expanded input types to include `Flag`, `IncrementingFlag`, and `LongOption`. - Added `InputTypeFactory` to help with loading input type classes #### Changed -- Updated to work with more than just `Argument` and `Option` input types. Makes use of `InputTypeFactory` to allow addition of new types as needed +- Updated to work with more than just `Argument` and `Option` input types. Makes use of `InputTypeFactory` to allow addition of new types as needed. ## [1.0.2][] #### Changed @@ -81,13 +30,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). #### Added - Initial release -[1.2.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.2.1...1.2.2 -[1.2.1]: https://github.com/pointybeard/helpers-functions-cli/compare/1.2.0...1.2.1 -[1.2.0]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.4...1.2.0 -[1.1.4]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.3...1.1.4 -[1.1.3]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.2...1.1.3 -[1.1.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.1...1.1.2 -[1.1.1]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.0...1.1.1 +[Unreleased]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.0...integration [1.1.0]: https://github.com/pointybeard/helpers-functions-cli/compare/1.0.3...1.1.0 [1.0.3]: https://github.com/pointybeard/helpers-functions-cli/compare/1.0.2...1.0.3 [1.0.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.0.1...1.0.2 diff --git a/README.md b/README.md index be26831..5a592d4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PHP Helpers: Command-line Input and Input Type Handlers -- Version: v1.2.2 -- Date: Aug 06 2019 +- Version: v1.1.0 +- Date: May 24 2019 - [Release notes](https://github.com/pointybeard/helpers-cli-input/blob/master/CHANGELOG.md) - [GitHub repository](https://github.com/pointybeard/helpers-cli-input) @@ -9,7 +9,7 @@ Collection of classes for handling argv (and other) input when calling command-l ## Installation -This library is installed via [Composer](http://getcomposer.org/). To install, use `composer require pointybeard/helpers-cli-input` or add `"pointybeard/helpers-cli-input": "~1.2.0"` to your `composer.json` file. +This library is installed via [Composer](http://getcomposer.org/). To install, use `composer require pointybeard/helpers-cli-input` or add `"pointybeard/helpers-cli-input": "~1.1"` to your `composer.json` file. And run composer to update your dependencies: @@ -26,7 +26,7 @@ To include all the [PHP Helpers](https://github.com/pointybeard/helpers) package Include this library in your PHP files with `use pointybeard\Helpers\Cli`. See example code in `example/example.php`. The example code can be run with the following command: - php -f example/example.php -- -vvv -d example/example.json import + php -f example/example.php -- -vvvs -d example/example.json import ## Support diff --git a/composer.json b/composer.json index 8eea99b..b88f0d1 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "pointybeard/helpers-cli-input", + "version": "1.1.0", "description": "Collection of classes for handling argv (and other) input when calling command-line scripts. Helps with parsing, collecting and validating arguments, options, and flags.", "homepage": "https://github.com/pointybeard/helpers-cli-input", "license": "MIT", @@ -13,8 +14,14 @@ ], "require": { "php": ">=7.2", - "pointybeard/helpers-foundation-factory": "~1.0", - "pointybeard/helpers-functions-flags": "~1.0" + "pointybeard/helpers-foundation-factory": "~1", + "pointybeard/helpers-functions-flags": "~1" + }, + "require-dev": { + "phpunit/phpunit": "^8", + "pointybeard/helpers-functions-strings": "^1", + "pointybeard/helpers-cli-colour": "^1", + "pointybeard/helpers-functions-cli": "^1" }, "support": { "issues": "https://github.com/pointybeard/helpers-cli-input/issues", diff --git a/example/example.php b/example/example.php index 1695bf1..f0b0002 100644 --- a/example/example.php +++ b/example/example.php @@ -4,17 +4,18 @@ declare(strict_types=1); include __DIR__.'/../vendor/autoload.php'; use pointybeard\Helpers\Cli\Input; +use pointybeard\Helpers\Cli\Colour\Colour; use pointybeard\Helpers\Functions\Cli; // Define what we are expecting to get from the command line $collection = (new Input\InputCollection()) - ->add( + ->append( Input\InputTypeFactory::build('Argument') ->name('action') ->flags(Input\AbstractInputType::FLAG_REQUIRED) ->description('The name of the action to perform') ) - ->add( + ->append( Input\InputTypeFactory::build('IncrementingFlag') ->name('v') ->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING) @@ -22,11 +23,11 @@ $collection = (new Input\InputCollection()) ->validator(new Input\Validator( function (Input\AbstractInputType $input, Input\AbstractInputHandler $context) { // Make sure verbosity level never goes above 3 - return min(3, (int) $context->find('v')); + return min(3, (int)$context->find('v')); } )) ) - ->add( + ->append( Input\InputTypeFactory::build('LongOption') ->name('data') ->short('d') @@ -56,29 +57,66 @@ $collection = (new Input\InputCollection()) // Get the supplied input. Passing the collection will make the handler bind values // and validate the input according to our collection -try { +try{ $argv = Input\InputHandlerFactory::build('Argv', $collection); -} catch (\Exception $ex) { - echo 'Error when attempting to bind values to collection. Returned: '.$ex->getMessage().PHP_EOL; +} catch(\Exception $ex) { + echo "Error when attempting to bind values to collection. Returned: " . $ex->getMessage() . PHP_EOL; exit; } -var_dump($argv->find('action')); -// string(6) "import" +// Display the manual in green text +echo Cli\manpage( + basename(__FILE__), + '1.0.2', + 'An example script for the PHP Helpers: Command-line Input and Input Type Handlers composer library (pointybeard/helpers-cli-input).', + $collection, + Colour::FG_GREEN, + Colour::FG_WHITE, + [ + 'Examples' => 'php -f example/example.php -- -vvv -d example/example.json import' + ] +).PHP_EOL.PHP_EOL; -var_dump($argv->find('v')); -//int(3) +// example.php 1.0.2, An example script for the PHP Helpers: Command-line Input +// and Input Type Handlers composer library (pointybeard/helpers-cli-input). +// Usage: example.php [OPTIONS]... ACTION... +// +// Arguments: +// ACTION The name of the action to perform +// +// Options: +// -v verbosity level. -v (errors only), -vv +// (warnings and errors), -vvv (everything). +// -d, --data=VALUE Path to the input JSON data +// +// Examples: +// php -f example/example.php -- -vvvs -d example/example.json import -var_dump($argv->find('data')); -// class stdClass#11 (1) { -// public $fruit => -// array(2) { -// [0] => -// string(5) "apple" -// [1] => -// string(6) "banana" -// } -// } +try{ -var_dump($argv->find('nope-doesnt-exist')); -// NULL + var_dump($argv->find('action')); + // string(6) "import" + + var_dump($argv->find('v')); + //int(3) + + var_dump($argv->find('s')); + //bool(true) + + var_dump($argv->find('data')); + // class stdClass#11 (1) { + // public $fruit => + // array(2) { + // [0] => + // string(5) "apple" + // [1] => + // string(6) "banana" + // } + // } + + var_dump($argv->find('nope-doesnt-exist')); + +} catch(\Exception $ex) { + echo "Error: " . $ex->getMessage() . PHP_EOL; +} +//Error trying to access input. Returned: Input nope-doesnt-exist could not be found. diff --git a/src/Input/AbstractInputHandler.php b/src/Input/AbstractInputHandler.php index e0c5e13..89d4c00 100644 --- a/src/Input/AbstractInputHandler.php +++ b/src/Input/AbstractInputHandler.php @@ -5,40 +5,15 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input; use pointybeard\Helpers\Functions\Flags; -use pointybeard\Helpers\Functions\Debug; abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface { - /** - * Will skip all validation when bind() is executed. Ignores all other flags - * @var integer - */ - const FLAG_BIND_SKIP_VALIDATION = 0x0001; - - /** - * Will skip the required input and required values check - * @var integer - */ - const FLAG_VALIDATION_SKIP_REQUIRED = 0x0002; - - /** - * Will skip running custom validators - * @var integer - */ - const FLAG_VALIDATION_SKIP_CUSTOM = 0x0004; - - /** - * Will skip checking if an input is in the collection - * @var integer - */ - const FLAG_VALIDATION_SKIP_UNRECOGNISED = 0x0008; - protected $input = []; protected $collection = null; abstract protected function parse(): bool; - final public function bind(InputCollection $inputCollection, ?int $flags = null): bool + public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool { // Do the binding stuff here $this->input = []; @@ -46,8 +21,8 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface $this->parse(); - if (!Flags\is_flag_set($flags, self::FLAG_BIND_SKIP_VALIDATION)) { - $this->validate($flags); + if (true !== $skipValidation) { + $this->validate(); } return true; @@ -64,86 +39,61 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface } } - protected function validateInput(AbstractInputType $input, ?int $flags) { - if(!Flags\is_flag_set($flags, self::FLAG_VALIDATION_SKIP_REQUIRED)) { - self::checkRequiredAndRequiredValue($input, $this->input); - } - // There is a default value and input has not been set. Assign the - // default value to the result. - if ( - null !== $input->default() && - null === $this->find($input->name()) - ) { - $result = $input->default(); - - // Input has been set AND it has a validator. Run the validator over the - // input. Note, this will be skipped if FLAG_VALIDATION_SKIP_CUSTOM is - // set - } elseif (null !== $this->find($input->name()) && null !== $input->validator() && !Flags\is_flag_set($flags, self::FLAG_VALIDATION_SKIP_CUSTOM)) { - $validator = $input->validator(); - - if ($validator instanceof \Closure) { - $validator = new Validator($validator); - } elseif (!($validator instanceof Validator)) { - throw new \Exception("Validator for '{$input->name()}' must be NULL or an instance of either Closure or Input\Validator."); - } - - try { - $result = $validator->validate($input, $this); - } catch (\Exception $ex) { - throw new Exceptions\InputValidationFailedException($input, 0, $ex); - } - - // No default, but may or may not have been set so assign whatever value - // it might have to the result - } else { - $result = $this->find($input->name()); - } - - return $result; - } - - protected function isInputRecognised(string $name): bool { - return null === $this->collection->find($name) ? false : true; - } - - final public function validate(?int $flags = null): void + public function validate(): void { - if(!Flags\is_flag_set($flags, self::FLAG_VALIDATION_SKIP_UNRECOGNISED)) { - foreach($this->input as $name => $value) { - if(false == static::isInputRecognised((string)$name)) { - throw new Exceptions\UnrecognisedInputException("'{$name}' is not recognised"); + foreach ($this->collection->getItems() as $type => $items) { + foreach($items as $input) { + self::checkRequiredAndRequiredValue($input, $this->input); + + if( + null !== $input->default() && + null === $this->find($input->name()) && + null === $input->validator() + ) { + $result = $input->default(); + } elseif(null !== $input->validator()) { + $validator = $input->validator(); + + if ($validator instanceof \Closure) { + $validator = new Validator($validator); + } elseif (!($validator instanceof Validator)) { + throw new \Exception("Validator for '{$input->name()}' must be NULL or an instance of either Closure or Input\Validator."); + } + + $result = $validator->validate($input, $this); + } else { + $result = $this->find($input->name()); } - } - } - foreach ($this->collection->getItems() as $input) { - $this->input[$input->name()] = static::validateInput($input, $flags); + $this->input[$input->name()] = $result; + } } } - final public function find(string $name) + public function find(string $name) { - if (isset($this->input[$name])) { + if(isset($this->input[$name])) { return $this->input[$name]; } // Check the collection to see if anything responds to $name - foreach ($this->collection->getItems() as $item) { - if ($item->respondsTo($name) && isset($this->input[$item->name()])) { - return $this->input[$item->name()]; + foreach($this->collection->getItems() as $type => $items) { + foreach($items as $ii) { + if($ii->respondsTo($name) && isset($this->input[$ii->name()])) { + return $this->input[$ii->name()]; + } } } - return null; + throw new Exceptions\InputNotFoundException($name); } - final public function getInput(): array + public function getInput(): array { return $this->input; } - final public function getCollection(): ?InputCollection + public function getCollection(): ?InputCollection { return $this->collection; } diff --git a/src/Input/AbstractInputType.php b/src/Input/AbstractInputType.php index 0d5f1af..be3999c 100644 --- a/src/Input/AbstractInputType.php +++ b/src/Input/AbstractInputType.php @@ -25,7 +25,7 @@ abstract class AbstractInputType implements Interfaces\InputTypeInterface $this->default = $default; } - public function __call($name, array $args = []) + public function __call($name, array $args=[]) { if (empty($args)) { return $this->$name; @@ -43,7 +43,7 @@ abstract class AbstractInputType implements Interfaces\InputTypeInterface public function respondsTo(string $name): bool { - return $name == $this->name; + return ($name == $this->name); } public function getType(): string diff --git a/src/Input/Exceptions/InputValidationFailedException.php b/src/Input/Exceptions/InputValidationFailedException.php deleted file mode 100644 index 6c3414c..0000000 --- a/src/Input/Exceptions/InputValidationFailedException.php +++ /dev/null @@ -1,15 +0,0 @@ -getDisplayName(), $previous->getMessage()), $code, $previous); - } -} diff --git a/src/Input/Exceptions/RequiredArgumentMissingException.php b/src/Input/Exceptions/RequiredArgumentMissingException.php new file mode 100644 index 0000000..d2f6c7a --- /dev/null +++ b/src/Input/Exceptions/RequiredArgumentMissingException.php @@ -0,0 +1,22 @@ +argument = strtoupper($argument); + + return parent::__construct("missing argument {$this->argument}.", $code, $previous); + } + + public function getArgumentName(): string + { + return $this->argument; + } +} diff --git a/src/Input/Exceptions/RequiredInputMissingException.php b/src/Input/Exceptions/RequiredInputMissingException.php index d7c4b43..c13bfd4 100644 --- a/src/Input/Exceptions/RequiredInputMissingException.php +++ b/src/Input/Exceptions/RequiredInputMissingException.php @@ -15,8 +15,10 @@ class RequiredInputMissingException extends \Exception $this->input = $input; return parent::__construct(sprintf( - 'missing %s', - $input->getDisplayName() + 'missing %s %s%s', + $input->getType(), + 'option' == $input->getType() ? '-' : '', + 'option' == $input->getType() ? $input->name() : strtoupper($input->name()) ), $code, $previous); } diff --git a/src/Input/Exceptions/RequiredInputMissingValueException.php b/src/Input/Exceptions/RequiredInputMissingValueException.php index 62be79b..b04c292 100644 --- a/src/Input/Exceptions/RequiredInputMissingValueException.php +++ b/src/Input/Exceptions/RequiredInputMissingValueException.php @@ -15,8 +15,10 @@ class RequiredInputMissingValueException extends \Exception $this->input = $input; return parent::__construct(sprintf( - 'a value is required for %s', - $input->getDisplayName() + '%s %s%s is missing a value', + $input->getType(), + 'option' == $input->getType() ? '-' : '', + 'option' == $input->getType() ? $input->name() : strtoupper($input->name()) ), $code, $previous); } diff --git a/src/Input/Exceptions/UnrecognisedInputException.php b/src/Input/Exceptions/UnrecognisedInputException.php deleted file mode 100644 index 22258f4..0000000 --- a/src/Input/Exceptions/UnrecognisedInputException.php +++ /dev/null @@ -1,9 +0,0 @@ -collection->find($name); - if(!($option instanceof Input\AbstractInputType)) { - return null; - } - - return $option; - } - - protected function findArgumentInCollection(int $index, string $token): ?Input\AbstractInputType - { - $arguments = $this->collection->getItemsByType('Argument'); - $position = 0; - foreach($arguments as $a) { - if($position == $index) { - return $a; - } - $position++; - } - return null; - } - protected function parse(): bool { + // So some parsing here. $it = new \ArrayIterator($this->argv); $position = 0; @@ -106,7 +83,7 @@ class Argv extends Input\AbstractInputHandler $value = true; } - $o = $this->findOptionInCollection($name); + $o = $this->collection->find($name); $this->input[ $o instanceof Input\AbstractInputType @@ -122,7 +99,7 @@ class Argv extends Input\AbstractInputHandler // Determine if we're expecting a value. // It also might have a long option equivalent, so we need // to look for that too. - $o = $this->findOptionInCollection($name); + $o = $this->collection->find($name); // This could also be an incrementing value // and needs to be added up. E.g. e.g. -vvv or -v -v -v @@ -165,14 +142,13 @@ class Argv extends Input\AbstractInputHandler // Arguments are positional, so we need to keep a track // of the index and look at the collection for an argument // with the same index - $a = $this->findArgumentInCollection($argumentCount, $token); - + $a = $this->collection->getItemByIndex('Argument', $argumentCount); $this->input[ $a instanceof Input\AbstractInputType ? $a->name() : $argumentCount ] = $token; - ++$argumentCount; + $argumentCount++; break; } $it->next(); diff --git a/src/Input/InputCollection.php b/src/Input/InputCollection.php index 49db36f..d307217 100644 --- a/src/Input/InputCollection.php +++ b/src/Input/InputCollection.php @@ -4,150 +4,96 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input; -class InputCollection implements \Iterator, \Countable +class InputCollection { private $items = []; - private $position = 0; - - public const POSITION_APPEND = 0x0001; - public const POSITION_PREPEND = 0x0002; // Prevents the class from being instanciated public function __construct() { - $this->position = 0; } - public function current(): mixed + public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self { - return $this->items[$this->position]; - } + $class = new \ReflectionClass($input); - public function key(): scalar - { - return $this->position; - } + $index = null; + $type = null; - public function next(): void - { - ++$this->position; - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return isset($this->items[$this->position]); - } - - public function count() : int { - return count($this->items); - } - - public function exists(string $name, &$index=null): bool { - return (null !== $this->find($name, null, null, $index)); - } - - public function remove(string $name): self { - if(!$this->exists($name, $index)) { - throw new \Exception("Input '{$name}' does not exist in this collection"); - } - unset($this->items[$index]); - return $this; - } - - public function add(Interfaces\InputTypeInterface $input, bool $replace = false, int $position=self::POSITION_APPEND): self - { - if($this->exists($input->name(), $index) && !$replace) { - throw new \Exception( - (new \ReflectionClass($input))->getShortName()." '{$input->name()}' already exists in this collection" - ); + if (!$replace && null !== $this->find($input->name(), null, null, $index, $type)) { + throw new \Exception("{$class->getShortName()} '{$input->name()}' already exists in this collection"); } if (true == $replace && null !== $index) { - $this->items[$index] = $input; + $this->items[$class->getShortName()][$index] = $argument; } else { - if($position == self::POSITION_PREPEND) { - array_unshift($this->items, $input); - } else { - array_push($this->items, $input); - } + $this->items[$class->getShortName()][] = $input; } return $this; } - public function find(string $name, array $restrictToType = null, array $excludeType = null, &$index = null): ?AbstractInputType + public function find(string $name, array $restrictToType=null, array $excludeType=null, &$type = null, &$index = null): ?AbstractInputType { - foreach ($this->items as $index => $input) { + foreach($this->items as $type => $items) { + // Check if we're restricting to or excluding specific types - if (null !== $restrictToType && !in_array($input->getType(), $restrictToType)) { + if(null !== $restrictToType && !in_array($type, $restrictToType)) { continue; - } elseif (null !== $excludeType && in_array($input->getType(), $excludeType)) { + + } elseif(null !== $excludeType && in_array($type, $excludeType)) { continue; } - if ($input->respondsTo($name)) { - return $input; + foreach($items as $index => $item) { + if($item->respondsTo($name)) { + return $item; + } } - } + $type = null; $index = null; return null; } - public function getTypes(): array - { - $types = []; - foreach($this->items as $input) { - $types[] = $input->getType(); - } - return array_unique($types); + public function getTypes(): array { + return array_keys($this->items); } - public function getItems(): \Iterator - { - return (new \ArrayObject($this->items))->getIterator(); + public function getItems(): array { + return $this->items; } - public function getItemsByType(string $type): \Iterator - { - return new InputTypeFilterIterator( - $this->getItems(), - [$type], - InputTypeFilterIterator::FILTER_INCLUDE - ); + public function getItemsByType(string $type): array { + return $this->items[$type] ?? []; } - public function getItemsExcludeByType(string $type): \Iterator - { - return new InputTypeFilterIterator( - $this->getItems(), - [$type], - InputTypeFilterIterator::FILTER_EXCLUDE - ); - } - - public function getItemByIndex(int $index): ?AbstractInputType - { - return $this->items[$index] ?? null; + public function getItemByIndex(string $type, int $index): ?AbstractInputType { + return $this->items[$type][$index] ?? null; } public static function merge(self ...$collections): self { + $items = []; - $iterator = new \AppendIterator; foreach ($collections as $c) { - $iterator->append($c->getItems()); + foreach($c->items() as $type => $items) { + foreach($items as $item) { + $items[] = $item; + } + } } $mergedCollection = new self(); - foreach ($iterator as $input) { - $mergedCollection->add($input, true); + + foreach ($items as $input) { + try { + $mergedCollection->append($input, true); + } catch (\Exception $ex) { + // Already exists, so skip it. + } } + return $mergedCollection; } } diff --git a/src/Input/InputHandlerFactory.php b/src/Input/InputHandlerFactory.php index 2ae425c..a899812 100644 --- a/src/Input/InputHandlerFactory.php +++ b/src/Input/InputHandlerFactory.php @@ -9,37 +9,32 @@ use pointybeard\Helpers\Foundation\Factory; final class InputHandlerFactory extends Factory\AbstractFactory { - public function getTemplateNamespace(): string + const FLAG_SKIP_VALIDATION = 0x0001; + + public static function getTemplateNamespace(): string { return __NAMESPACE__.'\\Handlers\\%s'; } - public function getExpectedClassType(): ?string + public static function getExpectedClassType(): ?string { return __NAMESPACE__.'\\Interfaces\\InputHandlerInterface'; } - public static function build(string $name, ...$arguments): object + public static function build(string $name, InputCollection $collection = null, int $flags = null): Interfaces\InputHandlerInterface { - - // Since passing flags is optional, we can use array_pad - // to ensure there are always at least 2 elements in $arguments - [$collection, $flags] = array_pad($arguments, 2, null); - - $factory = new self; - try { - $handler = $factory->instanciate( - $factory->generateTargetClassName($name) + $handler = self::instanciate( + self::generateTargetClassName($name) ); } catch (\Exception $ex) { throw new Exceptions\UnableToLoadInputHandlerException($name, 0, $ex); } - if (null !== $collection) { + if ($collection instanceof InputCollection) { $handler->bind( $collection, - $flags + Flags\is_flag_set($flags, self::FLAG_SKIP_VALIDATION) ); } diff --git a/src/Input/InputTypeFactory.php b/src/Input/InputTypeFactory.php index 82b1115..6d942dc 100644 --- a/src/Input/InputTypeFactory.php +++ b/src/Input/InputTypeFactory.php @@ -4,16 +4,19 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input; +use pointybeard\Helpers\Functions\Flags; use pointybeard\Helpers\Foundation\Factory; final class InputTypeFactory extends Factory\AbstractFactory { - public function getTemplateNamespace(): string + use Factory\Traits\hasSimpleFactoryBuildMethodTrait; + + public static function getTemplateNamespace(): string { return __NAMESPACE__.'\\Types\\%s'; } - public function getExpectedClassType(): ?string + public static function getExpectedClassType(): ?string { return __NAMESPACE__.'\\Interfaces\\InputTypeInterface'; } diff --git a/src/Input/InputTypeFilterIterator.php b/src/Input/InputTypeFilterIterator.php deleted file mode 100644 index e46e231..0000000 --- a/src/Input/InputTypeFilterIterator.php +++ /dev/null @@ -1,39 +0,0 @@ -types = array_map('strtolower', $types); - $this->mode = $mode; - - } - public function accept() - { - $input = $this->getInnerIterator()->current(); - - switch($this->mode) { - case self::FILTER_EXCLUDE: - return !in_array($input->getType(), $this->types); - break; - - case self::FILTER_INCLUDE: - default: - return in_array($input->getType(), $this->types); - break; - - } - } -} diff --git a/src/Input/Interfaces/InputHandlerInterface.php b/src/Input/Interfaces/InputHandlerInterface.php index 8345b30..87a33ac 100644 --- a/src/Input/Interfaces/InputHandlerInterface.php +++ b/src/Input/Interfaces/InputHandlerInterface.php @@ -8,9 +8,9 @@ use pointybeard\Helpers\Cli\Input; interface InputHandlerInterface { - public function bind(Input\InputCollection $inputCollection, ?int $flags = null): bool; + public function bind(Input\InputCollection $inputCollection, bool $skipValidation = false): bool; - public function validate(?int $flags = null): void; + public function validate(): void; public function find(string $name); diff --git a/src/Input/Interfaces/InputTypeInterface.php b/src/Input/Interfaces/InputTypeInterface.php index 3a6bc13..e61d304 100644 --- a/src/Input/Interfaces/InputTypeInterface.php +++ b/src/Input/Interfaces/InputTypeInterface.php @@ -16,10 +16,5 @@ interface InputTypeInterface const FLAG_TYPE_INCREMENTING = 0x0400; public function getType(): string; - public function respondsTo(string $name): bool; - - public function __toString(): string; - - public function getDisplayName(): string; } diff --git a/src/Input/Types/Argument.php b/src/Input/Types/Argument.php index 4550781..3ada5b9 100644 --- a/src/Input/Types/Argument.php +++ b/src/Input/Types/Argument.php @@ -10,9 +10,10 @@ use pointybeard\Helpers\Functions\Cli; class Argument extends Input\AbstractInputType { - public function __construct(string $name = null, int $flags = null, string $description = null, object $validator = null, $default = null) + + public function __construct(string $name = null, int $flags = null, string $description = null, object $validator = null, $default=null) { - if (null === $validator) { + if(null === $validator) { $validator = function (Input\AbstractInputType $input, Input\AbstractInputHandler $context) { // This dummy validator is necessary otherwise the argument // value is ALWAYS set to default (most often NULL) regardless @@ -24,12 +25,7 @@ class Argument extends Input\AbstractInputType parent::__construct($name, $flags, $description, $validator, $default); } - public function getDisplayName(): string - { - return strtoupper($this->name()); - } - - public function __toString(): string + public function __toString() { // MAGIC VALUES!!! OH MY..... $padCharacter = ' '; @@ -57,7 +53,7 @@ class Argument extends Input\AbstractInputType ); $first = Strings\mb_str_pad( - $this->getDisplayName().str_repeat($padCharacter, $argumentNameMinimumPaddingWidth), + strtoupper($this->name()).str_repeat($padCharacter, $argumentNameMinimumPaddingWidth), $argumentNamePaddedWidth, $padCharacter ); @@ -72,6 +68,6 @@ class Argument extends Input\AbstractInputType $second[$ii] = $secondaryLineLeadPadding.$second[$ii]; } - return $first.implode(PHP_EOL, $second); + return $first.implode($second, PHP_EOL); } } diff --git a/src/Input/Types/Flag.php b/src/Input/Types/Flag.php index a3f2329..fba5f55 100644 --- a/src/Input/Types/Flag.php +++ b/src/Input/Types/Flag.php @@ -5,13 +5,16 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input\Types; use pointybeard\Helpers\Functions\Flags; +use pointybeard\Helpers\Functions\Strings; +use pointybeard\Helpers\Functions\Cli; +use pointybeard\Helpers\Cli\Input; class Flag extends Option { public function __construct(string $name = null, int $flags = null, string $description = null, object $validator = null, $default = false) { - if (Flags\is_flag_set($flags, self::FLAG_VALUE_REQUIRED) || Flags\is_flag_set($flags, self::FLAG_VALUE_OPTIONAL)) { - throw new \Exception('The flags FLAG_VALUE_REQUIRED and FLAG_VALUE_OPTIONAL cannot be used on an input of type Flag'); + if(Flags\is_flag_set($flags, self::FLAG_VALUE_REQUIRED) || Flags\is_flag_set($flags, self::FLAG_VALUE_OPTIONAL)) { + throw new \Exception("The flags FLAG_VALUE_REQUIRED and FLAG_VALUE_OPTIONAL cannot be used on an input of type Flag"); } parent::__construct($name, null, $flags, $description, $validator, $default); } diff --git a/src/Input/Types/IncrementingFlag.php b/src/Input/Types/IncrementingFlag.php index 8d850a1..33df708 100644 --- a/src/Input/Types/IncrementingFlag.php +++ b/src/Input/Types/IncrementingFlag.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input\Types; use pointybeard\Helpers\Functions\Flags; +use pointybeard\Helpers\Functions\Strings; +use pointybeard\Helpers\Functions\Cli; +use pointybeard\Helpers\Cli\Input; class IncrementingFlag extends Flag { public function __construct(string $name = null, int $flags = null, string $description = null, object $validator = null, $default = 0) { - if (Flags\is_flag_set($flags, self::FLAG_TYPE_INCREMENTING)) { + if(Flags\is_flag_set($flags, self::FLAG_TYPE_INCREMENTING)) { $flags = $flags | self::FLAG_TYPE_INCREMENTING; } parent::__construct($name, null, $flags, $description, $validator, $default); diff --git a/src/Input/Types/LongOption.php b/src/Input/Types/LongOption.php index 17d73de..8aae031 100644 --- a/src/Input/Types/LongOption.php +++ b/src/Input/Types/LongOption.php @@ -21,21 +21,10 @@ class LongOption extends Input\AbstractInputType public function respondsTo(string $name): bool { - return $name == $this->name || $name == $this->short; + return ($name == $this->name || $name == $this->short); } - public function getDisplayName(): string - { - $short = - null !== $this->short() - ? '-'.$this->short().', ' - : null - ; - - return sprintf('%s--%s', $short, $this->name()); - } - - public function __toString(): string + public function __toString() { // MAGIC VALUES!!! OH MY..... $padCharacter = ' '; @@ -62,7 +51,10 @@ class LongOption extends Input\AbstractInputType STR_PAD_LEFT ); - $long = $this->getDisplayName(); + $short = null !== $this->short() ? '-'.$this->short() : null; + $long = null; + + $long = '--'.$this->name(); if (Flags\is_flag_set($this->flags(), self::FLAG_VALUE_REQUIRED)) { $long .= '=VALUE'; } elseif (Flags\is_flag_set($this->flags(), self::FLAG_VALUE_OPTIONAL)) { @@ -70,7 +62,7 @@ class LongOption extends Input\AbstractInputType } $first = Strings\mb_str_pad( - $long, // -O, --LONG, + (null !== $short ? "{$short}, " : '').$long, // -O, --LONG, $optionNamePaddedWidth, $padCharacter ); @@ -84,6 +76,6 @@ class LongOption extends Input\AbstractInputType $second[$ii] = $secondaryLineLeadPadding.$second[$ii]; } - return $first.implode(PHP_EOL, $second); + return $first.implode($second, PHP_EOL); } } diff --git a/src/Input/Types/Option.php b/src/Input/Types/Option.php index ab081ab..951caf4 100644 --- a/src/Input/Types/Option.php +++ b/src/Input/Types/Option.php @@ -4,18 +4,14 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input\Types; +use pointybeard\Helpers\Functions\Flags; use pointybeard\Helpers\Functions\Strings; use pointybeard\Helpers\Functions\Cli; use pointybeard\Helpers\Cli\Input; class Option extends Input\AbstractInputType { - public function getDisplayName(): string - { - return '-'.$this->name(); - } - - public function __toString(): string + public function __toString() { // MAGIC VALUES!!! OH MY..... $padCharacter = ' '; @@ -42,8 +38,10 @@ class Option extends Input\AbstractInputType STR_PAD_LEFT ); + $short = '-'.$this->name(); + $first = Strings\mb_str_pad( - $this->getDisplayName(), + $short, $optionNamePaddedWidth, $padCharacter );