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

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

This commit is contained in:
Alannah Kearney 2019-05-24 17:16:49 +10:00
commit 9e27b52991
8 changed files with 144 additions and 143 deletions

View file

@ -8,8 +8,7 @@ use pointybeard\Helpers\Functions\Flags;
abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
{ {
protected $options = []; protected $input = [];
protected $arguments = [];
protected $collection = null; protected $collection = null;
abstract protected function parse(): bool; abstract protected function parse(): bool;
@ -17,8 +16,7 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool
{ {
// Do the binding stuff here // Do the binding stuff here
$this->options = []; $this->input = [];
$this->arguments = [];
$this->collection = $inputCollection; $this->collection = $inputCollection;
$this->parse(); $this->parse();
@ -43,71 +41,56 @@ abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
public function validate(): void public function validate(): void
{ {
// Do basic missing option and value checking here foreach ($this->collection->getItems() as $type => $items) {
foreach ($this->collection->getOptions() as $input) { foreach($items as $input) {
self::checkRequiredAndRequiredValue($input, $this->options); 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.");
} }
// Option validation. $result = $validator->validate($input, $this);
foreach ($this->collection->getoptions() as $o) {
$result = false;
if (!array_key_exists($o->name(), $this->options)) {
$result = $o->default();
} else { } else {
if (null === $o->validator()) { $result = $this->find($input->name());
$result = $o->default();
continue;
} elseif ($o->validator() instanceof \Closure) {
$validator = new Validator($o->validator());
} elseif ($o->validator() instanceof Validator) {
$validator = $o->validator();
} else {
throw new \Exception("Validator for option {$o->name()} must be NULL or an instance of either Closure or Input\Validator.");
} }
$result = $validator->validate($o, $this); $this->input[$input->name()] = $result;
}
$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);
} }
} }
} }
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];
} }
public function getOption(string $name) // Check the collection to see if anything responds to $name
{ foreach($this->collection->getItems() as $type => $items) {
return $this->options[$name] ?? null; foreach($items as $ii) {
if($ii->respondsTo($name) && isset($this->input[$ii->name()])) {
return $this->input[$ii->name()];
}
}
} }
public function getArguments(): array throw new Exceptions\InputNotFoundException($name);
{
return $this->arguments;
} }
public function getOptions(): array public function getInput(): array
{ {
return $this->options; return $this->input;
} }
public function getCollection(): ?InputCollection public function getCollection(): ?InputCollection

View file

@ -12,22 +12,40 @@ abstract class AbstractInputType implements Interfaces\InputTypeInterface
protected $flags; protected $flags;
protected $description; protected $description;
protected $validator; protected $validator;
protected $default;
protected $value; 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->name = $name;
$this->flags = $flags; $this->flags = $flags;
$this->description = $description; $this->description = $description;
$this->validator = $validator; $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; return $this->$name;
} }
public function respondsTo(string $name): bool
{
return ($name == $this->name);
}
public function getType(): string public function getType(): string
{ {
return strtolower((new \ReflectionClass(static::class))->getShortName()); return strtolower((new \ReflectionClass(static::class))->getShortName());

View file

@ -4,10 +4,10 @@ declare(strict_types=1);
namespace pointybeard\Helpers\Cli\Input\Exceptions; 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);
} }
} }

View file

@ -67,6 +67,7 @@ class Argv extends Input\AbstractInputHandler
$it = new \ArrayIterator($this->argv); $it = new \ArrayIterator($this->argv);
$position = 0; $position = 0;
$argumentCount = 0;
while ($it->valid()) { while ($it->valid()) {
$token = $it->current(); $token = $it->current();
@ -82,9 +83,9 @@ class Argv extends Input\AbstractInputHandler
$value = true; $value = true;
} }
$o = $this->collection->findOption($name); $o = $this->collection->find($name);
$this->options[ $this->input[
$o instanceof Input\AbstractInputType $o instanceof Input\AbstractInputType
? $o->name() ? $o->name()
: $name : $name
@ -98,14 +99,14 @@ 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->findOption($name); $o = $this->collection->find($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
// would be -v => 3 // would be -v => 3
if ($o instanceof Input\AbstractInputType && Flags\is_flag_set($o->flags(), Input\AbstractInputType::FLAG_TYPE_INCREMENTING)) { if ($o instanceof Input\AbstractInputType && Flags\is_flag_set($o->flags(), Input\AbstractInputType::FLAG_TYPE_INCREMENTING)) {
$value = isset($this->options[$name]) $value = isset($this->input[$name])
? $this->options[$name] + 1 ? $this->input[$name] + 1
: 1 : 1
; ;
@ -128,7 +129,7 @@ class Argv extends Input\AbstractInputHandler
} }
} }
$this->options[ $this->input[
$o instanceof Input\AbstractInputType $o instanceof Input\AbstractInputType
? $o->name() ? $o->name()
: $name : $name
@ -141,12 +142,13 @@ 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->getArgumentsByIndex(count($this->arguments)); $a = $this->collection->getItemByIndex('Argument', $argumentCount);
$this->arguments[ $this->input[
$a instanceof Input\AbstractInputType $a instanceof Input\AbstractInputType
? $a->name() ? $a->name()
: count($this->arguments) : $argumentCount
] = $token; ] = $token;
$argumentCount++;
break; break;
} }
$it->next(); $it->next();

View file

@ -6,8 +6,7 @@ namespace pointybeard\Helpers\Cli\Input;
class InputCollection class InputCollection
{ {
private $arguments = []; private $items = [];
private $options = [];
// Prevents the class from being instanciated // Prevents the class from being instanciated
public function __construct() public function __construct()
@ -17,96 +16,77 @@ class InputCollection
public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self
{ {
$class = new \ReflectionClass($input); $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; 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) { foreach($this->items as $type => $items) {
if ($a->name() == $name) {
return $a; // 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; $index = null;
return null; return null;
} }
public function findOption(string $name, ?int &$index = null): ?AbstractInputType public function getTypes(): array {
{ return array_keys($this->items);
$type = 1 == strlen($name) ? 'name' : 'long';
foreach ($this->options as $index => $o) {
if ($o->$type() == $name) {
return $o;
}
} }
$index = null; public function getItems(): array {
return $this->items;
return null;
} }
private function appendArgument(Interfaces\InputTypeInterface $argument, bool $replace = false): void public function getItemsByType(string $type): array {
{ return $this->items[$type] ?? [];
if (null !== $this->findArgument($argument->name(), $index) && !$replace) {
throw new \Exception("Argument {$argument->name()} already exists in collection");
} }
if (true == $replace && null !== $index) { public function getItemByIndex(string $type, int $index): ?AbstractInputType {
$this->arguments[$index] = $argument; return $this->items[$type][$index] ?? null;
} else {
$this->arguments[] = $argument;
}
}
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 getArgumentsByIndex(int $index): ?AbstractInputType
{
return $this->arguments[$index] ?? null;
}
public function getArguments(): array
{
return $this->arguments;
}
public function getOptions(): array
{
return $this->options;
} }
public static function merge(self ...$collections): self public static function merge(self ...$collections): self
{ {
$arguments = []; $items = [];
$options = [];
foreach ($collections as $c) { foreach ($collections as $c) {
$arguments = array_merge($arguments, $c->getArguments()); foreach($c->items() as $type => $items) {
$options = array_merge($options, $c->getOptions()); foreach($items as $item) {
$items[] = $item;
}
}
} }
$mergedCollection = new self(); $mergedCollection = new self();
$it = new \AppendIterator(); foreach ($items as $input) {
$it->append(new \ArrayIterator($arguments));
$it->append(new \ArrayIterator($options));
foreach ($it as $input) {
try { try {
$mergedCollection->append($input, true); $mergedCollection->append($input, true);
} catch (\Exception $ex) { } catch (\Exception $ex) {

View file

@ -0,0 +1,23 @@
<?php
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
{
use Factory\Traits\hasSimpleFactoryBuildMethodTrait;
public static function getTemplateNamespace(): string
{
return __NAMESPACE__.'\\Types\\%s';
}
public static function getExpectedClassType(): ?string
{
return __NAMESPACE__.'\\Interfaces\\InputTypeInterface';
}
}

View file

@ -12,15 +12,9 @@ interface InputHandlerInterface
public function validate(): void; public function validate(): void;
public function getArgument(string $name): ?string; public function find(string $name);
// note that the return value of getOption() isn't always going to be public function getInput(): array;
// a string like getArgument()
public function getOption(string $name);
public function getArguments(): array;
public function getOptions(): array;
public function getCollection(): ?Input\InputCollection; public function getCollection(): ?Input\InputCollection;
} }

View file

@ -16,4 +16,5 @@ interface InputTypeInterface
const FLAG_TYPE_INCREMENTING = 0x0400; const FLAG_TYPE_INCREMENTING = 0x0400;
public function getType(): string; public function getType(): string;
public function respondsTo(string $name): bool;
} }