From 9e27b52991d66da6b3f10e823fdc6d19ef549098 Mon Sep 17 00:00:00 2001 From: Alannah Kearney Date: Fri, 24 May 2019 17:16:49 +1000 Subject: [PATCH] Updated to work with more than just Argument and Option input types. Makes use of InputTypeFactory to allow addition of new types as needed. Added InputTypeFactory to help with loading input type classes --- src/Input/AbstractInputHandler.php | 95 +++++++-------- src/Input/AbstractInputType.php | 22 +++- .../Exceptions/InputNotFoundException.php | 6 +- src/Input/Handlers/Argv.php | 20 ++-- src/Input/InputCollection.php | 110 +++++++----------- src/Input/InputTypeFactory.php | 23 ++++ .../Interfaces/InputHandlerInterface.php | 10 +- src/Input/Interfaces/InputTypeInterface.php | 1 + 8 files changed, 144 insertions(+), 143 deletions(-) create mode 100644 src/Input/InputTypeFactory.php diff --git a/src/Input/AbstractInputHandler.php b/src/Input/AbstractInputHandler.php index d35eb47..89d4c00 100644 --- a/src/Input/AbstractInputHandler.php +++ b/src/Input/AbstractInputHandler.php @@ -8,8 +8,7 @@ use pointybeard\Helpers\Functions\Flags; abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface { - protected $options = []; - protected $arguments = []; + protected $input = []; protected $collection = null; abstract protected function parse(): bool; @@ -17,8 +16,7 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool { // Do the binding stuff here - $this->options = []; - $this->arguments = []; + $this->input = []; $this->collection = $inputCollection; $this->parse(); @@ -43,71 +41,56 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface public function validate(): void { - // Do basic missing option and value checking here - foreach ($this->collection->getOptions() as $input) { - self::checkRequiredAndRequiredValue($input, $this->options); - } + foreach ($this->collection->getItems() as $type => $items) { + foreach($items as $input) { + self::checkRequiredAndRequiredValue($input, $this->input); - // Option validation. - foreach ($this->collection->getoptions() as $o) { - $result = false; + if( + null !== $input->default() && + null === $this->find($input->name()) && + null === $input->validator() + ) { + $result = $input->default(); + } elseif(null !== $input->validator()) { + $validator = $input->validator(); - if (!array_key_exists($o->name(), $this->options)) { - $result = $o->default(); - } else { - if (null === $o->validator()) { - $result = $o->default(); - continue; - } elseif ($o->validator() instanceof \Closure) { - $validator = new Validator($o->validator()); - } elseif ($o->validator() instanceof Validator) { - $validator = $o->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 { - throw new \Exception("Validator for option {$o->name()} must be NULL or an instance of either Closure or Input\Validator."); + $result = $this->find($input->name()); } - $result = $validator->validate($o, $this); - } - - $this->options[$o->name()] = $result; - } - - // Argument validation. - foreach ($this->collection->getArguments() as $a) { - self::checkRequiredAndRequiredValue($a, $this->arguments); - - if (isset($this->arguments[$a->name()]) && null !== $a->validator()) { - if ($a->validator() instanceof \Closure) { - $validator = new Validator($a->validator()); - } elseif ($a->validator() instanceof Validator) { - $validator = $a->validator(); - } else { - throw new \Exception("Validator for argument {$a->name()} must be NULL or an instance of either Closure or Input\Validator."); - } - - $validator->validate($a, $this); + $this->input[$input->name()] = $result; } } } - public function getArgument(string $name): ?string + public function find(string $name) { - return $this->arguments[$name] ?? null; + if(isset($this->input[$name])) { + return $this->input[$name]; + } + + // Check the collection to see if anything responds to $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()]; + } + } + } + + throw new Exceptions\InputNotFoundException($name); } - public function getOption(string $name) + public function getInput(): array { - return $this->options[$name] ?? null; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function getOptions(): array - { - return $this->options; + return $this->input; } public function getCollection(): ?InputCollection diff --git a/src/Input/AbstractInputType.php b/src/Input/AbstractInputType.php index 9fadc5e..be3999c 100644 --- a/src/Input/AbstractInputType.php +++ b/src/Input/AbstractInputType.php @@ -12,22 +12,40 @@ abstract class AbstractInputType implements Interfaces\InputTypeInterface protected $flags; protected $description; protected $validator; + protected $default; protected $value; - public function __construct($name, int $flags = null, string $description = null, object $validator = null) + public function __construct(string $name = null, int $flags = null, string $description = null, object $validator = null, $default = null) { $this->name = $name; $this->flags = $flags; $this->description = $description; $this->validator = $validator; + $this->default = $default; } - public function __call($name, array $args = []) + public function __call($name, array $args=[]) + { + if (empty($args)) { + return $this->$name; + } + + $this->$name = $args[0]; + + return $this; + } + + public function __get($name) { return $this->$name; } + public function respondsTo(string $name): bool + { + return ($name == $this->name); + } + public function getType(): string { return strtolower((new \ReflectionClass(static::class))->getShortName()); diff --git a/src/Input/Exceptions/InputNotFoundException.php b/src/Input/Exceptions/InputNotFoundException.php index 475cc66..f262aaa 100644 --- a/src/Input/Exceptions/InputNotFoundException.php +++ b/src/Input/Exceptions/InputNotFoundException.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace pointybeard\Helpers\Cli\Input\Exceptions; -class InputHandlerNotFoundException extends \Exception +class InputNotFoundException extends \Exception { - public function __construct(string $handler, string $command, $code = 0, \Exception $previous = null) + public function __construct(string $name, $code = 0, \Exception $previous = null) { - return parent::__construct(sprintf('The input handler %s could not be located.', $handler), $code, $previous); + return parent::__construct(sprintf('Input %s could not be found.', $name), $code, $previous); } } diff --git a/src/Input/Handlers/Argv.php b/src/Input/Handlers/Argv.php index 75f30dd..536284c 100644 --- a/src/Input/Handlers/Argv.php +++ b/src/Input/Handlers/Argv.php @@ -67,6 +67,7 @@ class Argv extends Input\AbstractInputHandler $it = new \ArrayIterator($this->argv); $position = 0; + $argumentCount = 0; while ($it->valid()) { $token = $it->current(); @@ -82,9 +83,9 @@ class Argv extends Input\AbstractInputHandler $value = true; } - $o = $this->collection->findOption($name); + $o = $this->collection->find($name); - $this->options[ + $this->input[ $o instanceof Input\AbstractInputType ? $o->name() : $name @@ -98,14 +99,14 @@ 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->collection->findOption($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 // would be -v => 3 if ($o instanceof Input\AbstractInputType && Flags\is_flag_set($o->flags(), Input\AbstractInputType::FLAG_TYPE_INCREMENTING)) { - $value = isset($this->options[$name]) - ? $this->options[$name] + 1 + $value = isset($this->input[$name]) + ? $this->input[$name] + 1 : 1 ; @@ -128,7 +129,7 @@ class Argv extends Input\AbstractInputHandler } } - $this->options[ + $this->input[ $o instanceof Input\AbstractInputType ? $o->name() : $name @@ -141,12 +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->collection->getArgumentsByIndex(count($this->arguments)); - $this->arguments[ + $a = $this->collection->getItemByIndex('Argument', $argumentCount); + $this->input[ $a instanceof Input\AbstractInputType ? $a->name() - : count($this->arguments) + : $argumentCount ] = $token; + $argumentCount++; break; } $it->next(); diff --git a/src/Input/InputCollection.php b/src/Input/InputCollection.php index ddf99ba..d307217 100644 --- a/src/Input/InputCollection.php +++ b/src/Input/InputCollection.php @@ -6,8 +6,7 @@ namespace pointybeard\Helpers\Cli\Input; class InputCollection { - private $arguments = []; - private $options = []; + private $items = []; // Prevents the class from being instanciated public function __construct() @@ -17,96 +16,77 @@ class InputCollection public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self { $class = new \ReflectionClass($input); - $this->{'append'.$class->getShortName()}($input, $replace); + + $index = null; + $type = null; + + 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[$class->getShortName()][$index] = $argument; + } else { + $this->items[$class->getShortName()][] = $input; + } return $this; } - public function findArgument(string $name, ?int &$index = null): ?AbstractInputType + public function find(string $name, array $restrictToType=null, array $excludeType=null, &$type = null, &$index = null): ?AbstractInputType { - foreach ($this->arguments as $index => $a) { - if ($a->name() == $name) { - return $a; + foreach($this->items as $type => $items) { + + // Check if we're restricting to or excluding specific types + if(null !== $restrictToType && !in_array($type, $restrictToType)) { + continue; + + } elseif(null !== $excludeType && in_array($type, $excludeType)) { + continue; + } + + foreach($items as $index => $item) { + if($item->respondsTo($name)) { + return $item; + } } } - + $type = null; $index = null; - return null; } - public function findOption(string $name, ?int &$index = null): ?AbstractInputType - { - $type = 1 == strlen($name) ? 'name' : 'long'; - - foreach ($this->options as $index => $o) { - if ($o->$type() == $name) { - return $o; - } - } - - $index = null; - - return null; + public function getTypes(): array { + return array_keys($this->items); } - private function appendArgument(Interfaces\InputTypeInterface $argument, bool $replace = false): void - { - if (null !== $this->findArgument($argument->name(), $index) && !$replace) { - throw new \Exception("Argument {$argument->name()} already exists in collection"); - } - - if (true == $replace && null !== $index) { - $this->arguments[$index] = $argument; - } else { - $this->arguments[] = $argument; - } + public function getItems(): array { + return $this->items; } - private function appendOption(Interfaces\InputTypeInterface $option, bool $replace = false): void - { - if (null !== $this->findOption($option->name(), $index) && !$replace) { - throw new \Exception("Option -{$option->name()} already exists in collection"); - } - if (true == $replace && null !== $index) { - $this->options[$index] = $option; - } else { - $this->options[] = $option; - } + public function getItemsByType(string $type): array { + return $this->items[$type] ?? []; } - public function getArgumentsByIndex(int $index): ?AbstractInputType - { - return $this->arguments[$index] ?? null; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function getOptions(): array - { - return $this->options; + public function getItemByIndex(string $type, int $index): ?AbstractInputType { + return $this->items[$type][$index] ?? null; } public static function merge(self ...$collections): self { - $arguments = []; - $options = []; + $items = []; foreach ($collections as $c) { - $arguments = array_merge($arguments, $c->getArguments()); - $options = array_merge($options, $c->getOptions()); + foreach($c->items() as $type => $items) { + foreach($items as $item) { + $items[] = $item; + } + } } $mergedCollection = new self(); - $it = new \AppendIterator(); - $it->append(new \ArrayIterator($arguments)); - $it->append(new \ArrayIterator($options)); - - foreach ($it as $input) { + foreach ($items as $input) { try { $mergedCollection->append($input, true); } catch (\Exception $ex) { diff --git a/src/Input/InputTypeFactory.php b/src/Input/InputTypeFactory.php new file mode 100644 index 0000000..6d942dc --- /dev/null +++ b/src/Input/InputTypeFactory.php @@ -0,0 +1,23 @@ +