mirror of
https://github.com/n3w/helpers-cli-input.git
synced 2025-12-19 12:43:23 +00:00
Initial commit
This commit is contained in:
commit
0620d00f08
24 changed files with 1054 additions and 0 deletions
117
src/Input/AbstractInputHandler.php
Normal file
117
src/Input/AbstractInputHandler.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input;
|
||||
|
||||
use pointybeard\Helpers\Functions\Flags;
|
||||
|
||||
abstract class AbstractInputHandler implements Interfaces\InputHandlerInterface
|
||||
{
|
||||
protected $options = [];
|
||||
protected $arguments = [];
|
||||
protected $collection = null;
|
||||
|
||||
abstract protected function parse(): bool;
|
||||
|
||||
public function bind(InputCollection $inputCollection, bool $skipValidation = false): bool
|
||||
{
|
||||
// Do the binding stuff here
|
||||
$this->options = [];
|
||||
$this->arguments = [];
|
||||
$this->collection = $inputCollection;
|
||||
|
||||
$this->parse();
|
||||
|
||||
if (true !== $skipValidation) {
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function checkRequiredAndRequiredValue(AbstractInputType $input, array $context): void
|
||||
{
|
||||
if (!isset($context[$input->name()])) {
|
||||
if (Flags\is_flag_set($input->flags(), AbstractInputType::FLAG_REQUIRED)) {
|
||||
throw new Exceptions\RequiredInputMissingException($input);
|
||||
}
|
||||
} elseif (Flags\is_flag_set($input->flags(), AbstractInputType::FLAG_VALUE_REQUIRED) && (null == $context[$input->name()] || true === $context[$input->name()])) {
|
||||
throw new Exceptions\RequiredInputMissingValueException($input);
|
||||
}
|
||||
}
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
// Do basic missing option and value checking here
|
||||
foreach ($this->collection->getOptions() as $input) {
|
||||
self::checkRequiredAndRequiredValue($input, $this->options);
|
||||
}
|
||||
|
||||
// Option validation.
|
||||
foreach ($this->collection->getoptions() as $o) {
|
||||
$result = false;
|
||||
|
||||
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();
|
||||
} 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->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
|
||||
{
|
||||
return $this->arguments[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getOption(string $name)
|
||||
{
|
||||
return $this->options[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getCollection(): ?InputCollection
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
}
|
||||
35
src/Input/AbstractInputType.php
Normal file
35
src/Input/AbstractInputType.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input;
|
||||
|
||||
abstract class AbstractInputType implements Interfaces\InputTypeInterface
|
||||
{
|
||||
protected static $type;
|
||||
|
||||
protected $name;
|
||||
protected $flags;
|
||||
protected $description;
|
||||
protected $validator;
|
||||
|
||||
protected $value;
|
||||
|
||||
public function __construct($name, int $flags = null, string $description = null, object $validator = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->flags = $flags;
|
||||
$this->description = $description;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
public function __call($name, array $args = [])
|
||||
{
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return strtolower((new \ReflectionClass(static::class))->getShortName());
|
||||
}
|
||||
}
|
||||
13
src/Input/Exceptions/InputNotFoundException.php
Normal file
13
src/Input/Exceptions/InputNotFoundException.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Exceptions;
|
||||
|
||||
class InputHandlerNotFoundException extends \Exception
|
||||
{
|
||||
public function __construct(string $handler, string $command, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
return parent::__construct(sprintf('The input handler %s could not be located.', $handler), $code, $previous);
|
||||
}
|
||||
}
|
||||
22
src/Input/Exceptions/RequiredArgumentMissingException.php
Normal file
22
src/Input/Exceptions/RequiredArgumentMissingException.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Exceptions;
|
||||
|
||||
class RequiredArgumentMissingException extends \Exception
|
||||
{
|
||||
private $argument;
|
||||
|
||||
public function __construct(string $argument, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
$this->argument = strtoupper($argument);
|
||||
|
||||
return parent::__construct("missing argument {$this->argument}.", $code, $previous);
|
||||
}
|
||||
|
||||
public function getArgumentName(): string
|
||||
{
|
||||
return $this->argument;
|
||||
}
|
||||
}
|
||||
29
src/Input/Exceptions/RequiredInputMissingException.php
Normal file
29
src/Input/Exceptions/RequiredInputMissingException.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Exceptions;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
|
||||
class RequiredInputMissingException extends \Exception
|
||||
{
|
||||
private $input;
|
||||
|
||||
public function __construct(Input\AbstractInputType $input, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
return parent::__construct(sprintf(
|
||||
'missing %s %s%s',
|
||||
$input->getType(),
|
||||
'option' == $input->getType() ? '-' : '',
|
||||
'option' == $input->getType() ? $input->name() : strtoupper($input->name())
|
||||
), $code, $previous);
|
||||
}
|
||||
|
||||
public function getInput(): Input\AbstractInputType
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
}
|
||||
29
src/Input/Exceptions/RequiredInputMissingValueException.php
Normal file
29
src/Input/Exceptions/RequiredInputMissingValueException.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Exceptions;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
|
||||
class RequiredInputMissingValueException extends \Exception
|
||||
{
|
||||
private $input;
|
||||
|
||||
public function __construct(Input\AbstractInputType $input, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
return parent::__construct(sprintf(
|
||||
'%s %s%s is missing a value',
|
||||
$input->getType(),
|
||||
'option' == $input->getType() ? '-' : '',
|
||||
'option' == $input->getType() ? $input->name() : strtoupper($input->name())
|
||||
), $code, $previous);
|
||||
}
|
||||
|
||||
public function getInput(): Input\AbstractInputType
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
}
|
||||
13
src/Input/Exceptions/UnableToLoadInputHandlerException.php
Normal file
13
src/Input/Exceptions/UnableToLoadInputHandlerException.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Exceptions;
|
||||
|
||||
class UnableToLoadInputHandlerException extends \Exception
|
||||
{
|
||||
public function __construct(string $name, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
return parent::__construct(sprintf('The input handler %s could not be loaded. Returned: %s', $name, $previous->getMessage()), $code, $previous);
|
||||
}
|
||||
}
|
||||
174
src/Input/Handlers/Argv.php
Normal file
174
src/Input/Handlers/Argv.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Handlers;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
use pointybeard\Helpers\Functions\Flags;
|
||||
|
||||
class Argv extends Input\AbstractInputHandler
|
||||
{
|
||||
private $argv = null;
|
||||
|
||||
const OPTION_LONG = 'long';
|
||||
const OPTION_SHORT = 'short';
|
||||
const ARGUMENT = 'argument';
|
||||
|
||||
public function __construct(array $argv = null)
|
||||
{
|
||||
if (null === $argv) {
|
||||
$argv = $_SERVER['argv'];
|
||||
}
|
||||
|
||||
// Remove the script name
|
||||
array_shift($argv);
|
||||
|
||||
$this->argv = self::expandOptions($argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will look for combined options, e.g -vlt and expand them to -v -l -t.
|
||||
*/
|
||||
protected static function expandOptions(array $args): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($args as $a) {
|
||||
switch (self::findType($a)) {
|
||||
case self::OPTION_SHORT:
|
||||
|
||||
// If the name is longer than a 2 characters
|
||||
// it will mean it's a combination of flags. e.g.
|
||||
// -vlt 12345 is the same as -v -l -t 12345
|
||||
if (strlen($a) > 2) {
|
||||
// Strip the leading hyphen (-)
|
||||
$a = substr($a, 1);
|
||||
|
||||
for ($ii = 0; $ii < strlen($a); ++$ii) {
|
||||
$result[] = "-{$a[$ii]}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// no break
|
||||
default:
|
||||
$result[] = $a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function parse(): bool
|
||||
{
|
||||
// So some parsing here.
|
||||
$it = new \ArrayIterator($this->argv);
|
||||
|
||||
$position = 0;
|
||||
|
||||
while ($it->valid()) {
|
||||
$token = $it->current();
|
||||
|
||||
switch (self::findType($token)) {
|
||||
case self::OPTION_LONG:
|
||||
$opt = substr($token, 2);
|
||||
|
||||
if (false !== strstr($opt, '=')) {
|
||||
list($name, $value) = explode('=', $opt, 2);
|
||||
} else {
|
||||
$name = $opt;
|
||||
$value = true;
|
||||
}
|
||||
|
||||
$o = $this->collection->findOption($name);
|
||||
|
||||
$this->options[
|
||||
$o instanceof Input\AbstractInputType
|
||||
? $o->name()
|
||||
: $name
|
||||
] = $value;
|
||||
|
||||
break;
|
||||
|
||||
case self::OPTION_SHORT:
|
||||
$name = substr($token, 1);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
: 1
|
||||
;
|
||||
|
||||
// Not incrementing, so resume default behaviour
|
||||
} else {
|
||||
// We'll need to look ahead and see what the next value is.
|
||||
// Ignore it if the next item is another option
|
||||
// Advance the pointer to grab the next value
|
||||
$it->next();
|
||||
$value = $it->current();
|
||||
|
||||
// See if the next item is another option and of it is,
|
||||
// rewind the iterator and set value to 'true'. Also,
|
||||
// if this option doesn't expect a value (no FLAG_VALUE_REQUIRED or FLAG_VALUE_OPTIONAL flag set), don't capture the next value.
|
||||
if (null === $value || self::isOption($value) || !($o instanceof Input\AbstractInputType) || (
|
||||
!Flags\is_flag_set($o->flags(), Input\AbstractInputType::FLAG_VALUE_REQUIRED) && !Flags\is_flag_set($o->flags(), Input\AbstractInputType::FLAG_VALUE_OPTIONAL)
|
||||
)) {
|
||||
$value = true;
|
||||
$it->seek($position);
|
||||
}
|
||||
}
|
||||
|
||||
$this->options[
|
||||
$o instanceof Input\AbstractInputType
|
||||
? $o->name()
|
||||
: $name
|
||||
] = $value;
|
||||
|
||||
break;
|
||||
|
||||
case self::ARGUMENT:
|
||||
default:
|
||||
// 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 instanceof Input\AbstractInputType
|
||||
? $a->name()
|
||||
: count($this->arguments)
|
||||
] = $token;
|
||||
break;
|
||||
}
|
||||
$it->next();
|
||||
++$position;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function isOption(string $value): bool
|
||||
{
|
||||
return '-' == $value[0];
|
||||
}
|
||||
|
||||
private static function findType(string $value): string
|
||||
{
|
||||
if (0 === strpos($value, '--')) {
|
||||
return self::OPTION_LONG;
|
||||
} elseif (self::isOption($value)) {
|
||||
return self::OPTION_SHORT;
|
||||
} else {
|
||||
return self::ARGUMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
src/Input/InputCollection.php
Normal file
119
src/Input/InputCollection.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input;
|
||||
|
||||
class InputCollection
|
||||
{
|
||||
private $arguments = [];
|
||||
private $options = [];
|
||||
|
||||
// Prevents the class from being instanciated
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function append(Interfaces\InputTypeInterface $input, bool $replace = false): self
|
||||
{
|
||||
$class = new \ReflectionClass($input);
|
||||
$this->{'append'.$class->getShortName()}($input, $replace);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function findArgument(string $name, ?int &$index = null): ?AbstractInputType
|
||||
{
|
||||
foreach ($this->arguments as $index => $a) {
|
||||
if ($a->name() == $name) {
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public static function merge(self ...$collections): self
|
||||
{
|
||||
$arguments = [];
|
||||
$options = [];
|
||||
|
||||
foreach ($collections as $c) {
|
||||
$arguments = array_merge($arguments, $c->getArguments());
|
||||
$options = array_merge($options, $c->getOptions());
|
||||
}
|
||||
|
||||
$mergedCollection = new self();
|
||||
|
||||
$it = new \AppendIterator();
|
||||
$it->append(new \ArrayIterator($arguments));
|
||||
$it->append(new \ArrayIterator($options));
|
||||
|
||||
foreach ($it as $input) {
|
||||
try {
|
||||
$mergedCollection->append($input, true);
|
||||
} catch (\Exception $ex) {
|
||||
// Already exists, so skip it.
|
||||
}
|
||||
}
|
||||
|
||||
return $mergedCollection;
|
||||
}
|
||||
}
|
||||
43
src/Input/InputHandlerFactory.php
Normal file
43
src/Input/InputHandlerFactory.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input;
|
||||
|
||||
use pointybeard\Helpers\Functions\Flags;
|
||||
use pointybeard\Helpers\Foundation\Factory;
|
||||
|
||||
final class InputHandlerFactory extends Factory\AbstractFactory
|
||||
{
|
||||
const FLAG_SKIP_VALIDATION = 0x0001;
|
||||
|
||||
public static function getTemplateNamespace(): string
|
||||
{
|
||||
return __NAMESPACE__.'\\Handlers\\%s';
|
||||
}
|
||||
|
||||
public static function getExpectedClassType(): ?string
|
||||
{
|
||||
return __NAMESPACE__.'\\Interfaces\\InputHandlerInterface';
|
||||
}
|
||||
|
||||
public static function build(string $name, InputCollection $collection = null, int $flags = null): Interfaces\InputHandlerInterface
|
||||
{
|
||||
try {
|
||||
$handler = self::instanciate(
|
||||
self::generateTargetClassName($name)
|
||||
);
|
||||
} catch (\Exception $ex) {
|
||||
throw new Exceptions\UnableToLoadInputHandlerException($name, 0, $ex);
|
||||
}
|
||||
|
||||
if ($collection instanceof InputCollection) {
|
||||
$handler->bind(
|
||||
$collection,
|
||||
Flags\is_flag_set($flags, self::FLAG_SKIP_VALIDATION)
|
||||
);
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
26
src/Input/Interfaces/InputHandlerInterface.php
Normal file
26
src/Input/Interfaces/InputHandlerInterface.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Interfaces;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
|
||||
interface InputHandlerInterface
|
||||
{
|
||||
public function bind(Input\InputCollection $inputCollection, bool $skipValidation = false): bool;
|
||||
|
||||
public function validate(): void;
|
||||
|
||||
public function getArgument(string $name): ?string;
|
||||
|
||||
// note that the return value of getOption() isn't always going to be
|
||||
// a string like getArgument()
|
||||
public function getOption(string $name);
|
||||
|
||||
public function getArguments(): array;
|
||||
|
||||
public function getOptions(): array;
|
||||
|
||||
public function getCollection(): ?Input\InputCollection;
|
||||
}
|
||||
19
src/Input/Interfaces/InputTypeInterface.php
Normal file
19
src/Input/Interfaces/InputTypeInterface.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Interfaces;
|
||||
|
||||
interface InputTypeInterface
|
||||
{
|
||||
const FLAG_REQUIRED = 0x0001;
|
||||
const FLAG_OPTIONAL = 0x0002;
|
||||
const FLAG_VALUE_REQUIRED = 0x0004;
|
||||
const FLAG_VALUE_OPTIONAL = 0x0008;
|
||||
|
||||
const FLAG_TYPE_STRING = 0x0100;
|
||||
const FLAG_TYPE_INT = 0x0200;
|
||||
const FLAG_TYPE_INCREMENTING = 0x0400;
|
||||
|
||||
public function getType(): string;
|
||||
}
|
||||
12
src/Input/Interfaces/InputValidatorInterface.php
Normal file
12
src/Input/Interfaces/InputValidatorInterface.php
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Interfaces;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
|
||||
interface InputValidatorInterface
|
||||
{
|
||||
public function validate(Input\AbstractInputType $input, Input\AbstractInputHandler $context);
|
||||
}
|
||||
25
src/Input/Types/Argument.php
Normal file
25
src/Input/Types/Argument.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Types;
|
||||
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
use pointybeard\Helpers\Functions\Strings;
|
||||
|
||||
class Argument extends Input\AbstractInputType
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
$name = strtoupper($this->name());
|
||||
|
||||
$first = str_pad(sprintf('%s ', $name), 20, ' ');
|
||||
|
||||
$second = Strings\utf8_wordwrap_array($this->description(), 40);
|
||||
for ($ii = 1; $ii < count($second); ++$ii) {
|
||||
$second[$ii] = str_pad('', 22, ' ', \STR_PAD_LEFT).$second[$ii];
|
||||
}
|
||||
|
||||
return $first.implode($second, PHP_EOL);
|
||||
}
|
||||
}
|
||||
42
src/Input/Types/Option.php
Normal file
42
src/Input/Types/Option.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input\Types;
|
||||
|
||||
use pointybeard\Helpers\Functions\Flags;
|
||||
use pointybeard\Helpers\Functions\Strings;
|
||||
use pointybeard\Helpers\Cli\Input;
|
||||
|
||||
class Option extends Input\AbstractInputType
|
||||
{
|
||||
protected $long;
|
||||
protected $default;
|
||||
|
||||
public function __construct(string $name, string $long = null, int $flags = null, string $description = null, object $validator = null, $default = false)
|
||||
{
|
||||
$this->default = $default;
|
||||
$this->long = $long;
|
||||
parent::__construct($name, $flags, $description, $validator);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$long = null !== $this->long() ? ', --'.$this->long() : null;
|
||||
if (null != $long) {
|
||||
if (Flags\is_flag_set($this->flags(), self::FLAG_VALUE_REQUIRED)) {
|
||||
$long .= '=VALUE';
|
||||
} elseif (Flags\is_flag_set($this->flags(), self::FLAG_VALUE_OPTIONAL)) {
|
||||
$long .= '[=VALUE]';
|
||||
}
|
||||
}
|
||||
$first = str_pad(sprintf('-%s%s ', $this->name(), $long), 36, ' ');
|
||||
|
||||
$second = Strings\utf8_wordwrap_array($this->description(), 40);
|
||||
for ($ii = 1; $ii < count($second); ++$ii) {
|
||||
$second[$ii] = str_pad('', 38, ' ', \STR_PAD_LEFT).$second[$ii];
|
||||
}
|
||||
|
||||
return $first.implode($second, PHP_EOL);
|
||||
}
|
||||
}
|
||||
38
src/Input/Validator.php
Normal file
38
src/Input/Validator.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pointybeard\Helpers\Cli\Input;
|
||||
|
||||
class Validator implements Interfaces\InputValidatorInterface
|
||||
{
|
||||
private $func;
|
||||
|
||||
public function __construct(\Closure $func)
|
||||
{
|
||||
// Check the closure used for validation meets requirements
|
||||
$params = (new \ReflectionFunction($func))->getParameters();
|
||||
|
||||
// Must have exactly 2 params
|
||||
if (2 != count($params)) {
|
||||
throw new \Exception('Closure passed to Validator::__construct() is invalid: Must have exactly 2 parameters.');
|
||||
}
|
||||
|
||||
// First must be 'input' and be of type pointybeard\Helpers\Cli\Input\AbstractInputType
|
||||
if ('input' != $params[0]->getName() || __NAMESPACE__.'\AbstractInputType' != (string) $params[0]->getType()) {
|
||||
throw new \Exception('Closure passed to Validator::__construct() is invalid: First parameter must match '.__NAMESPACE__."\AbstractInputType \$input. Provided with ".(string) $params[0]->getType()." \${$params[0]->getName()}");
|
||||
}
|
||||
|
||||
// Second must be 'context' and be of type pointybeard\Helpers\Cli\Input\AbstractInputHandler
|
||||
if ('context' != $params[1]->getName() || __NAMESPACE__.'\AbstractInputHandler' != (string) $params[1]->getType()) {
|
||||
throw new \Exception('Closure passed to Validator::__construct() is invalid: Second parameter must match '.__NAMESPACE__."\AbstractInputHandler \$context. Provided with ".(string) $params[1]->getType()." \${$params[1]->getName()}");
|
||||
}
|
||||
|
||||
$this->func = $func;
|
||||
}
|
||||
|
||||
public function validate(AbstractInputType $input, AbstractInputHandler $context)
|
||||
{
|
||||
return ($this->func)($input, $context);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue