11
0
Fork 0
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:
Alannah Kearney 2019-05-20 15:08:41 +10:00
commit 0620d00f08
24 changed files with 1054 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
vendor/
composer.lock
.DS_Store
.php_cs.cache

12
CHANGELOG.md Normal file
View file

@ -0,0 +1,12 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
**View all [Unreleased][] changes here**
## 1.0.0
#### Added
- Initial release
[Unreleased]: https://github.com/pointybeard/helpers-cli-input/compare/1.0.0...integration

19
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,19 @@
# Contributing to this project
We encourage contribution to this project and only ask you follow some simple rules to make everyone's job a little easier.
## Found a bug?
Please lodge an issue at the GitHub issue tracker for this project -- [https://github.com/pointybeard/helpers-cli-input/issues](https://github.com/pointybeard/helpers-cli-input/issues)
Include details on the behaviour you are seeing, and steps needed to reproduce the problem.
## Want to contribute code?
* Fork the project
* Make your feature addition or bug fix
* Ensure your code is nicely formatted
* Commit just the modifications, do not alter CHANGELOG.md. If relevant, link to GitHub issue (see [https://help.github.com/articles/closing-issues-via-commit-messages/](https://help.github.com/articles/closing-issues-via-commit-messages/))
* Send the pull request
We will review the code and either merge it in, or leave some feedback.

26
LICENCE Normal file
View file

@ -0,0 +1,26 @@
All source code included in the "PHP Helpers: Command-line Input and Input Type Handlers" archive is,
unless otherwise specified, released under the MIT licence as follows:
----- begin license block -----
Copyright 2019 Alannah Kearney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
----- end license block -----

46
README.md Normal file
View file

@ -0,0 +1,46 @@
# PHP Helpers: Command-line Input and Input Type Handlers
- Version: v1.0.0
- Date: May 20 2019
- [Release notes](https://github.com/pointybeard/helpers-cli-input/blob/master/CHANGELOG.md)
- [GitHub repository](https://github.com/pointybeard/helpers-cli-input)
Collection of classes for handling argv (and other) input when calling command-line scripts. Helps with parsing, collecting and validating arguments, options, and flags.
## 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.0"` to your `composer.json` file.
And run composer to update your dependencies:
$ curl -s http://getcomposer.org/installer | php
$ php composer.phar update
### Requirements
This library makes use of the [PHP Helpers: Flag Functions](https://github.com/pointybeard/helpers-functions-flags) (`pointybeard/helpers-functions-flags`) and [PHP Helpers: Factory Foundation Classes](https://github.com/pointybeard/helpers-foundation-factory) packages. They are installed automatically via composer.
To include all the [PHP Helpers](https://github.com/pointybeard/helpers) packages on your project, use `composer require pointybeard/helpers` or add `"pointybeard/helpers": "~1.1"` to your composer file.
## Usage
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
## Support
If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/pointybeard/helpers-cli-input/issues),
or better yet, fork the library and submit a pull request.
## Contributing
We encourage you to contribute to this project. Please check out the [Contributing documentation](https://github.com/pointybeard/helpers-cli-input/blob/master/CONTRIBUTING.md) for guidelines about how to get involved.
## License
"PHP Helpers: Command-line Input and Input Type Handlers" is released under the [MIT License](http://www.opensource.org/licenses/MIT).
## Credits
* Some inspiration taken from the [Symfony Console Component](https://github.com/symfony/console) (although no code has been used).

35
composer.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "pointybeard/helpers-cli-input",
"version": "1.0.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",
"authors": [
{
"name": "Alannah Kearney",
"email": "hi@alannahkearney.com",
"homepage": "http://alannahkearney.com",
"role": "Developer"
}
],
"require": {
"php": ">=7.2",
"pointybeard/helpers-foundation-factory": "~1.0",
"pointybeard/helpers-functions-flags": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^8",
"pointybeard/helpers-functions-strings": "~1.0",
"pointybeard/helpers-cli-colour": "~1.0"
},
"support": {
"issues": "https://github.com/pointybeard/helpers-cli-input/issues",
"wiki": "https://github.com/pointybeard/helpers-cli-input/wiki"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"pointybeard\\Helpers\\Cli\\": "src/"
}
}
}

1
example/example.json Normal file
View file

@ -0,0 +1 @@
{"fruit": ["apple", "banana"]}

155
example/example.php Normal file
View file

@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
include __DIR__.'/../vendor/autoload.php';
use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Functions\Flags;
use pointybeard\Helpers\Functions\Strings;
use pointybeard\Helpers\Cli\Colour\Colour;
// Define what we are expecting to get from the command line
$collection = (new Input\InputCollection())
->append(new Input\Types\Argument(
'action',
Input\AbstractInputType::FLAG_REQUIRED,
'The name of the action to perform'
))
->append(new Input\Types\Option(
'v',
null,
Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING,
'verbosity level. -v (errors only), -vv (warnings and errors), -vvv (everything).',
null,
0
))
->append(new Input\Types\Option(
'd',
'data',
Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED,
'Path to the input JSON data',
function (Input\AbstractInputType $input, Input\AbstractInputHandler $context) {
// Make sure -d (--data) is a valid file that can be read
$file = $context->getOption('d');
if (!is_readable($file)) {
throw new \Exception('The file specified via option -d (--data) does not exist or is not readable.');
}
// Now make sure it is valid JSON
try {
$json = json_decode(file_get_contents($file), false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $ex) {
throw new \Exception(sprintf('The file specified via option -d (--data) does not appear to be a valid JSON ddocument. Returned: %s: %s', $ex->getCode(), $ex->getMessage()));
}
return $json;
}
))
;
// Get the supplied input. Passing the collection will make the handler bind values
// and validate the input according to our collection
$argv = Input\InputHandlerFactory::build('Argv', $collection);
// Example of using an input collection to generate a usage string
function usage(Input\InputCollection $collection): string {
$arguments = [];
foreach ($collection->getArguments() as $a) {
$arguments[] = strtoupper(
// Wrap with square brackets if it's not required
Flags\is_flag_set(Input\AbstractInputType::FLAG_OPTIONAL, $a->flags()) ||
!Flags\is_flag_set(Input\AbstractInputType::FLAG_REQUIRED, $a->flags())
? "[{$a->name()}]"
: $a->name()
);
}
$arguments = trim(implode($arguments, ' '));
return sprintf(
"Usage: php -f example.php -- [OPTIONS]... %s%s",
$arguments,
strlen($arguments) > 0 ? '...' : ''
);
}
// Example of using an input collection to generate a manual page
function manpage(Input\InputCollection $collection) : string {
$arguments = $options = [];
foreach ($collection->getArguments() as $a) {
$arguments[] = (string) $a;
}
foreach ($collection->getOptions() as $o) {
$options[] = (string) $o;
}
$arguments = implode($arguments, PHP_EOL.' ');
$options = implode($options, PHP_EOL.' ');
return sprintf('%s 1.0.0, %s
%s
Mandatory values for long options are mandatory for short options too.
Arguments:
%s
Options:
%s
Examples:
php -f example/example.php -- -vvv -d example/example.json import
',
basename(__FILE__),
Strings\utf8_wordwrap(
"An example script for the PHP Helpers: Command-line Input and Input Type Handlers composer library (pointybeard/helpers-cli-input)."
),
usage($collection),
$arguments,
$options
);
}
// Display the manual in green text
echo Colour::colourise(manpage($collection), Colour::FG_GREEN) . PHP_EOL . PHP_EOL;
/*
example.php 1.0.0, An example script for the PHP Helpers: Command-line Input and Input Type Handlers
composer library (pointybeard/helpers-cli-input).
Usage: php -f example.php -- [OPTIONS]... ACTION...
Mandatory values for long options are mandatory for short options too.
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 -- -vvv -d example/example.json import
*/
var_dump($argv->getArgument('action'));
// string(6) "import"
var_dump($argv->getOption('v'));
//int(3)
var_dump($argv->getOption('s'));
//bool(true)
var_dump($argv->getOption('d'));
// class stdClass#11 (1) {
// public $fruit =>
// array(2) {
// [0] =>
// string(5) "apple"
// [1] =>
// string(6) "banana"
// }
// }

View 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;
}
}

View 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());
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}

View 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;
}

View 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);
}

View 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);
}
}

View 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
View 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);
}
}