11
0
Fork 0
mirror of https://github.com/n3w/helpers-cli-input.git synced 2025-12-19 12:43:23 +00:00

Compare commits

..

16 commits

Author SHA1 Message Date
Norbert Wagner
c6fe64321e Passing the separator after the array is no longer supported 2023-03-23 08:47:59 +01:00
Alannah Kearney
9622c98258 Removed dev packages from composer.json to avoid circular package dependency version issues and removed example for manpage() method. 2019-11-28 03:33:52 +00:00
Alannah Kearney
2d87f04dff Removed version information from composer.json 2019-08-06 09:23:59 +10:00
Alannah Kearney
1e903ea6e1 Updated README and CHANGELOG for 1.2.2 release 2019-08-06 09:03:28 +10:00
Alannah Kearney
326a979302 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. 2019-08-06 09:02:03 +10:00
Alannah Kearney
d501a680ff Updated README, CHANGELOG, and composer.json for 1.2.1 release 2019-06-04 23:23:35 +10:00
Alannah Kearney
e542f6fd94 Updated InputHandlerFactory and InputTypeFactory to work with changes in pointybeard/helpers-foundation-factory 1.0.2 2019-06-04 23:22:15 +10:00
Alannah Kearney
46b87d3d62 Updated README, CHANGELOG, and composer.json for 1.2.0 release 2019-06-01 22:57:42 +10:00
Alannah Kearney
27c04d1d43 Updated example 2019-06-01 22:49:10 +10:00
Alannah Kearney
f7a9b66590 Added Argv::findOptionInCollection() and Argv::findArgumentInCollection() wrapper method, which are used in Argv::parse() 2019-06-01 22:44:09 +10:00
Alannah Kearney
05d283b6d2 InputCollection now implements Iterator and Countable (implementing required methods). Removed use of $type. Added getItemsExcludeByType(). getItemsByType() and getItems() now returns an Iterator. Renamed append() to add() and added $position flag. Added POSITION_APPEND and POSITION_PREPEND flags. 2019-06-01 22:43:19 +10:00
Alannah Kearney
90db5c2047 Added InputTypeFilterIterator class 2019-06-01 22:34:32 +10:00
Alannah Kearney
9f5c5c7d4d Made getCollection(), getInput(), find(), and validate() in AbstractInputHandler final. Removed categorising input by type. Abstracted most of validate() into it's own protected method called validateInput(). Removed $skipValidation argument from bind() and relaced with $flags. Added FLAG_BIND_SKIP_VALIDATION, FLAG_VALIDATION_SKIP_REQUIRED, FLAG_VALIDATION_SKIP_CUSTOM, and FLAG_VALIDATION_SKIP_UNRECOGNISED flags. Added check in validate() to look for unrecognised options and arguments. 2019-06-01 22:31:59 +10:00
Alannah Kearney
1100d5d959 Removed InputHandlerFactory::FLAG_SKIP_VALIDATION. Passing flags in call to bind() 2019-06-01 22:28:20 +10:00
Alannah Kearney
0cbbbdff66 Updated InputHandlerInterface bind() and validate() methods to support flags 2019-06-01 22:27:18 +10:00
Alannah Kearney
7069019301 Added UnrecognisedInputException exception 2019-06-01 15:09:02 +10:00
14 changed files with 321 additions and 161 deletions

View file

@ -3,7 +3,35 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
**View all [Unreleased][] changes here** ## [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][] ## [1.1.4][]
#### Fixed #### Fixed
@ -11,7 +39,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [1.1.3][] ## [1.1.3][]
#### Fixed #### 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. - 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][] ## [1.1.2][]
#### Added #### Added
@ -19,22 +47,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `InputTypeInterface::getDisplayName()` method to standardise how the name of an `InputTypeInterface` class wants to display it's name - Added `InputTypeInterface::getDisplayName()` method to standardise how the name of an `InputTypeInterface` class wants to display it's name
#### Changed #### Changed
- Updated validation logic for inputs that have a validator, no default, and are not set. - Updated validation logic for inputs that have a validator, no default, and are not set
- Throwing `InputValidationFailedException` exception when validation fails - Throwing `InputValidationFailedException` exception when validation fails
- Updated `RequiredInputMissingException` and `RequiredInputMissingValueException` exceptions to use `InputTypeInterface::getDisplayName()` when producing their message - Updated `RequiredInputMissingException` and `RequiredInputMissingValueException` exceptions to use `InputTypeInterface::getDisplayName()` when producing their message
- Removed unused `RequiredArgumentMissingException` exception - Removed unused `RequiredArgumentMissingException` exception
## [1.1.1][] ## [1.1.1][]
#### Changed #### 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. - `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
## [1.1.0][] ## [1.1.0][]
#### Added #### 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 - Added `InputTypeFactory` to help with loading input type classes
#### Changed #### 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][] ## [1.0.2][]
#### Changed #### Changed
@ -53,7 +81,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
#### Added #### Added
- Initial release - Initial release
[Unreleased]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.4...integration [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.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.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.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.1...1.1.2

View file

@ -1,7 +1,7 @@
# PHP Helpers: Command-line Input and Input Type Handlers # PHP Helpers: Command-line Input and Input Type Handlers
- Version: v1.1.4 - Version: v1.2.2
- Date: May 27 2019 - Date: Aug 06 2019
- [Release notes](https://github.com/pointybeard/helpers-cli-input/blob/master/CHANGELOG.md) - [Release notes](https://github.com/pointybeard/helpers-cli-input/blob/master/CHANGELOG.md)
- [GitHub repository](https://github.com/pointybeard/helpers-cli-input) - [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 ## 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.1"` 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.2.0"` to your `composer.json` file.
And run composer to update your dependencies: 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: 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 -- -vvvs -d example/example.json import php -f example/example.php -- -vvv -d example/example.json import
## Support ## Support

View file

@ -1,6 +1,5 @@
{ {
"name": "pointybeard/helpers-cli-input", "name": "pointybeard/helpers-cli-input",
"version": "1.1.4",
"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.", "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", "homepage": "https://github.com/pointybeard/helpers-cli-input",
"license": "MIT", "license": "MIT",
@ -14,14 +13,8 @@
], ],
"require": { "require": {
"php": ">=7.2", "php": ">=7.2",
"pointybeard/helpers-foundation-factory": "~1", "pointybeard/helpers-foundation-factory": "~1.0",
"pointybeard/helpers-functions-flags": "~1" "pointybeard/helpers-functions-flags": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^8",
"pointybeard/helpers-functions-strings": "^1",
"pointybeard/helpers-cli-colour": "^1",
"pointybeard/helpers-functions-cli": "^1"
}, },
"support": { "support": {
"issues": "https://github.com/pointybeard/helpers-cli-input/issues", "issues": "https://github.com/pointybeard/helpers-cli-input/issues",

View file

@ -4,18 +4,17 @@ declare(strict_types=1);
include __DIR__.'/../vendor/autoload.php'; include __DIR__.'/../vendor/autoload.php';
use pointybeard\Helpers\Cli\Input; use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Cli\Colour\Colour;
use pointybeard\Helpers\Functions\Cli; use pointybeard\Helpers\Functions\Cli;
// Define what we are expecting to get from the command line // Define what we are expecting to get from the command line
$collection = (new Input\InputCollection()) $collection = (new Input\InputCollection())
->append( ->add(
Input\InputTypeFactory::build('Argument') Input\InputTypeFactory::build('Argument')
->name('action') ->name('action')
->flags(Input\AbstractInputType::FLAG_REQUIRED) ->flags(Input\AbstractInputType::FLAG_REQUIRED)
->description('The name of the action to perform') ->description('The name of the action to perform')
) )
->append( ->add(
Input\InputTypeFactory::build('IncrementingFlag') Input\InputTypeFactory::build('IncrementingFlag')
->name('v') ->name('v')
->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING) ->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING)
@ -27,7 +26,7 @@ $collection = (new Input\InputCollection())
} }
)) ))
) )
->append( ->add(
Input\InputTypeFactory::build('LongOption') Input\InputTypeFactory::build('LongOption')
->name('data') ->name('data')
->short('d') ->short('d')
@ -64,43 +63,12 @@ try {
exit; exit;
} }
// 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;
// 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('action')); var_dump($argv->find('action'));
// string(6) "import" // string(6) "import"
var_dump($argv->find('v')); var_dump($argv->find('v'));
//int(3) //int(3)
var_dump($argv->find('s'));
//bool(true)
var_dump($argv->find('data')); var_dump($argv->find('data'));
// class stdClass#11 (1) { // class stdClass#11 (1) {
// public $fruit => // public $fruit =>

View file

@ -5,15 +5,40 @@ declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input; namespace pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Functions\Flags; use pointybeard\Helpers\Functions\Flags;
use pointybeard\Helpers\Functions\Debug;
abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface 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 $input = [];
protected $collection = null; protected $collection = null;
abstract protected function parse(): bool; abstract protected function parse(): bool;
public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool final public function bind(InputCollection $inputCollection, ?int $flags = null): bool
{ {
// Do the binding stuff here // Do the binding stuff here
$this->input = []; $this->input = [];
@ -21,8 +46,8 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
$this->parse(); $this->parse();
if (true !== $skipValidation) { if (!Flags\is_flag_set($flags, self::FLAG_BIND_SKIP_VALIDATION)) {
$this->validate(); $this->validate($flags);
} }
return true; return true;
@ -39,71 +64,86 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
} }
} }
public function validate(): void protected function validateInput(AbstractInputType $input, ?int $flags) {
{ if(!Flags\is_flag_set($flags, self::FLAG_VALIDATION_SKIP_REQUIRED)) {
foreach ($this->collection->getItems() as $type => $items) { self::checkRequiredAndRequiredValue($input, $this->input);
foreach ($items as $input) { }
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();
// There is a default value, input has not been set, and there // Input has been set AND it has a validator. Run the validator over the
// is no validator // input. Note, this will be skipped if FLAG_VALIDATION_SKIP_CUSTOM is
if ( // set
null !== $input->default() && } elseif (null !== $this->find($input->name()) && null !== $input->validator() && !Flags\is_flag_set($flags, self::FLAG_VALIDATION_SKIP_CUSTOM)) {
null === $this->find($input->name()) && $validator = $input->validator();
null === $input->validator()
) {
$result = $input->default();
// Input has been set and it has a validator if ($validator instanceof \Closure) {
} elseif (null !== $this->find($input->name()) && null !== $input->validator()) { $validator = new Validator($validator);
$validator = $input->validator(); } elseif (!($validator instanceof Validator)) {
throw new \Exception("Validator for '{$input->name()}' must be NULL or an instance of either Closure or 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, no validator, but may or may not have been set
} else {
$result = $this->find($input->name());
}
$this->input[$input->name()] = $result;
} }
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
{
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 $input) {
$this->input[$input->name()] = static::validateInput($input, $flags);
} }
} }
public function find(string $name) final public function find(string $name)
{ {
if (isset($this->input[$name])) { if (isset($this->input[$name])) {
return $this->input[$name]; return $this->input[$name];
} }
// Check the collection to see if anything responds to $name // Check the collection to see if anything responds to $name
foreach ($this->collection->getItems() as $type => $items) { foreach ($this->collection->getItems() as $item) {
foreach ($items as $ii) { if ($item->respondsTo($name) && isset($this->input[$item->name()])) {
if ($ii->respondsTo($name) && isset($this->input[$ii->name()])) { return $this->input[$item->name()];
return $this->input[$ii->name()];
}
} }
} }
return null; return null;
} }
public function getInput(): array final public function getInput(): array
{ {
return $this->input; return $this->input;
} }
public function getCollection(): ?InputCollection final public function getCollection(): ?InputCollection
{ {
return $this->collection; return $this->collection;
} }

View file

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input\Exceptions;
class UnrecognisedInputException extends \Exception
{
}

View file

@ -3,9 +3,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input\Handlers; namespace pointybeard\Helpers\Cli\Input\Handlers;
use pointybeard\Helpers\Cli\Input\Exceptions;
use pointybeard\Helpers\Cli\Input; use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Functions\Flags; use pointybeard\Helpers\Functions\Flags;
use pointybeard\Helpers\Functions\Debug;
class Argv extends Input\AbstractInputHandler class Argv extends Input\AbstractInputHandler
{ {
@ -61,9 +62,31 @@ class Argv extends Input\AbstractInputHandler
return $result; return $result;
} }
protected function findOptionInCollection(string $name): ?Input\AbstractInputType
{
$option = $this->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 protected function parse(): bool
{ {
// So some parsing here.
$it = new \ArrayIterator($this->argv); $it = new \ArrayIterator($this->argv);
$position = 0; $position = 0;
@ -83,7 +106,7 @@ class Argv extends Input\AbstractInputHandler
$value = true; $value = true;
} }
$o = $this->collection->find($name); $o = $this->findOptionInCollection($name);
$this->input[ $this->input[
$o instanceof Input\AbstractInputType $o instanceof Input\AbstractInputType
@ -99,7 +122,7 @@ class Argv extends Input\AbstractInputHandler
// Determine if we're expecting a value. // Determine if we're expecting a value.
// It also might have a long option equivalent, so we need // It also might have a long option equivalent, so we need
// to look for that too. // to look for that too.
$o = $this->collection->find($name); $o = $this->findOptionInCollection($name);
// This could also be an incrementing value // This could also be an incrementing value
// and needs to be added up. E.g. e.g. -vvv or -v -v -v // and needs to be added up. E.g. e.g. -vvv or -v -v -v
@ -142,7 +165,8 @@ class Argv extends Input\AbstractInputHandler
// Arguments are positional, so we need to keep a track // Arguments are positional, so we need to keep a track
// of the index and look at the collection for an argument // of the index and look at the collection for an argument
// with the same index // with the same index
$a = $this->collection->getItemByIndex('Argument', $argumentCount); $a = $this->findArgumentInCollection($argumentCount, $token);
$this->input[ $this->input[
$a instanceof Input\AbstractInputType $a instanceof Input\AbstractInputType
? $a->name() ? $a->name()

View file

@ -4,96 +4,150 @@ declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input; namespace pointybeard\Helpers\Cli\Input;
class InputCollection class InputCollection implements \Iterator, \Countable
{ {
private $items = []; private $items = [];
private $position = 0;
public const POSITION_APPEND = 0x0001;
public const POSITION_PREPEND = 0x0002;
// Prevents the class from being instanciated // Prevents the class from being instanciated
public function __construct() public function __construct()
{ {
$this->position = 0;
} }
public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self public function current(): mixed
{ {
$class = new \ReflectionClass($input); return $this->items[$this->position];
}
if (null !== $this->find($input->name(), null, null, $type, $index) && !$replace) { public function key(): scalar
throw new \Exception("{$class->getShortName()} '{$input->name()}' already exists in this collection"); {
return $this->position;
}
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 (true == $replace && null !== $index) { if (true == $replace && null !== $index) {
$this->items[$class->getShortName()][$index] = $input; $this->items[$index] = $input;
} else { } else {
$this->items[$class->getShortName()][] = $input; if($position == self::POSITION_PREPEND) {
array_unshift($this->items, $input);
} else {
array_push($this->items, $input);
}
} }
return $this; return $this;
} }
public function find(string $name, array $restrictToType = null, array $excludeType = null, &$type = null, &$index = null): ?AbstractInputType public function find(string $name, array $restrictToType = null, array $excludeType = null, &$index = null): ?AbstractInputType
{ {
foreach ($this->items as $type => $items) { foreach ($this->items as $index => $input) {
// Check if we're restricting to or excluding specific types // Check if we're restricting to or excluding specific types
if (null !== $restrictToType && !in_array($type, $restrictToType)) { if (null !== $restrictToType && !in_array($input->getType(), $restrictToType)) {
continue; continue;
} elseif (null !== $excludeType && in_array($type, $excludeType)) { } elseif (null !== $excludeType && in_array($input->getType(), $excludeType)) {
continue; continue;
} }
foreach ($items as $index => $item) { if ($input->respondsTo($name)) {
if ($item->respondsTo($name)) { return $input;
return $item;
}
} }
} }
$type = null;
$index = null; $index = null;
return null; return null;
} }
public function getTypes(): array public function getTypes(): array
{ {
return array_keys($this->items); $types = [];
foreach($this->items as $input) {
$types[] = $input->getType();
}
return array_unique($types);
} }
public function getItems(): array public function getItems(): \Iterator
{ {
return $this->items; return (new \ArrayObject($this->items))->getIterator();
} }
public function getItemsByType(string $type): array public function getItemsByType(string $type): \Iterator
{ {
return $this->items[$type] ?? []; return new InputTypeFilterIterator(
$this->getItems(),
[$type],
InputTypeFilterIterator::FILTER_INCLUDE
);
} }
public function getItemByIndex(string $type, int $index): ?AbstractInputType public function getItemsExcludeByType(string $type): \Iterator
{ {
return $this->items[$type][$index] ?? null; return new InputTypeFilterIterator(
$this->getItems(),
[$type],
InputTypeFilterIterator::FILTER_EXCLUDE
);
}
public function getItemByIndex(int $index): ?AbstractInputType
{
return $this->items[$index] ?? null;
} }
public static function merge(self ...$collections): self public static function merge(self ...$collections): self
{ {
$inputs = [];
$iterator = new \AppendIterator;
foreach ($collections as $c) { foreach ($collections as $c) {
foreach ($c->getItems() as $type => $items) { $iterator->append($c->getItems());
foreach ($items as $item) {
$inputs[] = $item;
}
}
} }
$mergedCollection = new self(); $mergedCollection = new self();
foreach ($iterator as $input) {
foreach ($inputs as $input) { $mergedCollection->add($input, true);
try {
$mergedCollection->append($input, true);
} catch (\Exception $ex) {
// Already exists, so skip it.
}
} }
return $mergedCollection; return $mergedCollection;
} }
} }

View file

@ -9,32 +9,37 @@ use pointybeard\Helpers\Foundation\Factory;
final class InputHandlerFactory extends Factory\AbstractFactory final class InputHandlerFactory extends Factory\AbstractFactory
{ {
const FLAG_SKIP_VALIDATION = 0x0001; public function getTemplateNamespace(): string
public static function getTemplateNamespace(): string
{ {
return __NAMESPACE__.'\\Handlers\\%s'; return __NAMESPACE__.'\\Handlers\\%s';
} }
public static function getExpectedClassType(): ?string public function getExpectedClassType(): ?string
{ {
return __NAMESPACE__.'\\Interfaces\\InputHandlerInterface'; return __NAMESPACE__.'\\Interfaces\\InputHandlerInterface';
} }
public static function build(string $name, InputCollection $collection = null, int $flags = null): Interfaces\InputHandlerInterface public static function build(string $name, ...$arguments): object
{ {
// 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 { try {
$handler = self::instanciate( $handler = $factory->instanciate(
self::generateTargetClassName($name) $factory->generateTargetClassName($name)
); );
} catch (\Exception $ex) { } catch (\Exception $ex) {
throw new Exceptions\UnableToLoadInputHandlerException($name, 0, $ex); throw new Exceptions\UnableToLoadInputHandlerException($name, 0, $ex);
} }
if ($collection instanceof InputCollection) { if (null !== $collection) {
$handler->bind( $handler->bind(
$collection, $collection,
Flags\is_flag_set($flags, self::FLAG_SKIP_VALIDATION) $flags
); );
} }

View file

@ -8,14 +8,12 @@ use pointybeard\Helpers\Foundation\Factory;
final class InputTypeFactory extends Factory\AbstractFactory final class InputTypeFactory extends Factory\AbstractFactory
{ {
use Factory\Traits\hasSimpleFactoryBuildMethodTrait; public function getTemplateNamespace(): string
public static function getTemplateNamespace(): string
{ {
return __NAMESPACE__.'\\Types\\%s'; return __NAMESPACE__.'\\Types\\%s';
} }
public static function getExpectedClassType(): ?string public function getExpectedClassType(): ?string
{ {
return __NAMESPACE__.'\\Interfaces\\InputTypeInterface'; return __NAMESPACE__.'\\Interfaces\\InputTypeInterface';
} }

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input;
class InputTypeFilterIterator extends \FilterIterator {
private $types;
private $mode;
public const FILTER_INCLUDE = 0x0001;
public const FILTER_EXCLUDE = 0x0002;
public function __construct(\Iterator $iterator, array $types=[], int $mode=self::FILTER_INCLUDE)
{
parent::__construct($iterator);
$this->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;
}
}
}

View file

@ -8,9 +8,9 @@ use pointybeard\Helpers\Cli\Input;
interface InputHandlerInterface interface InputHandlerInterface
{ {
public function bind(Input\InputCollection $inputCollection, bool $skipValidation = false): bool; public function bind(Input\InputCollection $inputCollection, ?int $flags = null): bool;
public function validate(): void; public function validate(?int $flags = null): void;
public function find(string $name); public function find(string $name);

View file

@ -72,6 +72,6 @@ class Argument extends Input\AbstractInputType
$second[$ii] = $secondaryLineLeadPadding.$second[$ii]; $second[$ii] = $secondaryLineLeadPadding.$second[$ii];
} }
return $first.implode($second, PHP_EOL); return $first.implode(PHP_EOL, $second);
} }
} }

View file

@ -84,6 +84,6 @@ class LongOption extends Input\AbstractInputType
$second[$ii] = $secondaryLineLeadPadding.$second[$ii]; $second[$ii] = $secondaryLineLeadPadding.$second[$ii];
} }
return $first.implode($second, PHP_EOL); return $first.implode(PHP_EOL, $second);
} }
} }