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
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
.DS_Store
|
||||||
|
.php_cs.cache
|
||||||
12
CHANGELOG.md
Normal file
12
CHANGELOG.md
Normal 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
19
CONTRIBUTING.md
Normal 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
26
LICENCE
Normal 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
46
README.md
Normal 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
35
composer.json
Normal 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
1
example/example.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"fruit": ["apple", "banana"]}
|
||||||
155
example/example.php
Normal file
155
example/example.php
Normal 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"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
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