Compare commits
No commits in common. "9070bf3862ad51df7861644b5a7cfdad6c0f054e" and "6d771ed88f788c8b0302534185aa99e4ae5155be" have entirely different histories.
9070bf3862
...
6d771ed88f
@ -55,10 +55,8 @@ class CliMiddleware implements MiddlewareInterface
|
||||
|
||||
$request = $request->withAttribute('cli.command', $command)
|
||||
->withAttribute('cli.request', $cliRequest);
|
||||
|
||||
$cliRequest->parseOptions($command);
|
||||
|
||||
return $this->container->make($class)->{$method}($request, $this->commands->arguments, $command->options, $cliRequest->rest);
|
||||
|
||||
return $this->container->make($class)->{$method}($request, []);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -81,7 +79,6 @@ class CliMiddleware implements MiddlewareInterface
|
||||
|
||||
private function printCommand(Command $command) : ResponseInterface
|
||||
{
|
||||
# @TODO !
|
||||
$text = [
|
||||
$command->description, "",
|
||||
"#[color: rgb(145;130;195), format: bold]Usage:[#]",
|
||||
@ -96,7 +93,6 @@ class CliMiddleware implements MiddlewareInterface
|
||||
$text = ( new AsciiFormatter() )->parse($text);
|
||||
}
|
||||
|
||||
# @TODO => plug httpfactory here !
|
||||
return new TextResponse( implode(PHP_EOL, $text) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,9 +10,9 @@ class CliRequest
|
||||
|
||||
public array $options = [];
|
||||
|
||||
public mixed $rest = "";
|
||||
protected string $shortOptions;
|
||||
|
||||
protected array $unparsedOptions;
|
||||
protected array $longOptions;
|
||||
|
||||
public function __construct(
|
||||
public ServerRequestInterface $request
|
||||
@ -20,6 +20,22 @@ class CliRequest
|
||||
$this->parseCommandLine();
|
||||
}
|
||||
|
||||
public function matchOptions(string ... $options) : bool
|
||||
{
|
||||
foreach($options as $opt) {
|
||||
if ( $this->matchOption($opt) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function matchOption(string $option) : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function parseCommandLine() : void
|
||||
{
|
||||
$executionString = $this->argv();
|
||||
@ -27,67 +43,11 @@ class CliRequest
|
||||
# Removing execution's path
|
||||
array_shift($executionString);
|
||||
|
||||
while ( $cmd = array_shift($executionString) ) {
|
||||
if (str_starts_with($cmd, '-')) {
|
||||
array_unshift($executionString, $cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($executionString as $cmd) {
|
||||
$this->command[] = $cmd;
|
||||
}
|
||||
|
||||
$this->unparsedOptions = $executionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the limited getopt() method which do not allow for parameters to be passed after multiple command arguments
|
||||
*
|
||||
* @param Command $command Matched command found using parseCommandLine
|
||||
* @return void
|
||||
*/
|
||||
public function parseOptions(Command $command) : void
|
||||
{
|
||||
$this->getopt($command->options);
|
||||
|
||||
/*
|
||||
if ( false !== $options ) {
|
||||
if ($restIndex !== null) {
|
||||
$this->rest = $this->argv()[$restIndex];
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
}*/
|
||||
}
|
||||
|
||||
protected function getopt(OptionStack $options) :void
|
||||
{
|
||||
$currentOption = false;
|
||||
|
||||
while ( $argument = array_shift($this->unparsedOptions) ) {
|
||||
#if ($currentOption !== false) {
|
||||
$currentOption = $options->matchOption($argument);
|
||||
|
||||
if ($currentOption === false) {
|
||||
$this->rest = implode(' ', $this->unparsedOptions);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*elseif (is_array($currentOption)) {
|
||||
foreach($currentOption as $matchingOption) {
|
||||
$matchingOption->handle();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
}*/
|
||||
# }
|
||||
#else {
|
||||
|
||||
# }
|
||||
}
|
||||
|
||||
# dump($options);
|
||||
$this->command = $executionString;
|
||||
}
|
||||
|
||||
protected function argv() : array
|
||||
|
||||
@ -4,33 +4,17 @@ namespace Mcnd\CLI;
|
||||
|
||||
class Command
|
||||
{
|
||||
public array $arguments;
|
||||
|
||||
public readonly OptionStack $options;
|
||||
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public string $description,
|
||||
array $options = [],
|
||||
public array $options = [],
|
||||
public null|Command $parent = null,
|
||||
public \Closure|string|null $callback = null,
|
||||
public bool $isRoot = false,
|
||||
) {
|
||||
$this->options = new OptionStack($options);
|
||||
}
|
||||
) {}
|
||||
|
||||
public function pushOption(Option|array $option) : void
|
||||
{
|
||||
if (is_array($option)) {
|
||||
$this->options->merge($option);
|
||||
}
|
||||
else {
|
||||
$this->options->append($option);
|
||||
}
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments) : void
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
$this->options[] = array_merge($this->options, $option);
|
||||
}
|
||||
}
|
||||
@ -4,10 +4,10 @@ namespace Mcnd\CLI;
|
||||
|
||||
class CommandStack
|
||||
{
|
||||
public function __construct(
|
||||
public array $commands = [],
|
||||
public array $arguments = []
|
||||
) {}
|
||||
public array $commands = [];
|
||||
|
||||
public function __construct()
|
||||
{}
|
||||
|
||||
public function merge(CommandStack $stack) : void
|
||||
{
|
||||
@ -28,20 +28,16 @@ class CommandStack
|
||||
*/
|
||||
public function matchCommand(array $commands) : null|Command
|
||||
{
|
||||
$arguments = [];
|
||||
|
||||
while ($commands) {
|
||||
$cmd = implode(' ', $commands);
|
||||
|
||||
foreach ($this->commands as $command) {
|
||||
if ($command->name === $cmd) {
|
||||
$this->arguments = array_reverse($arguments);
|
||||
|
||||
return $command;
|
||||
}
|
||||
}
|
||||
|
||||
$arguments[] = array_pop($commands);
|
||||
array_pop($commands);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
109
src/Option.php
109
src/Option.php
@ -2,113 +2,20 @@
|
||||
|
||||
namespace Mcnd\CLI;
|
||||
|
||||
use Mcnd\CLI\Option\{CountOption, ParsedOption, ToggleOption, ValueOption, KeyValueOption};
|
||||
|
||||
class Option
|
||||
{
|
||||
protected ParsedOption $parsed;
|
||||
public const ARGUMENT_NONE = 0;
|
||||
|
||||
public const ARGUMENT_OPTIONAL = 1;
|
||||
|
||||
public const ARGUMENT_REQUIRED = 2;
|
||||
|
||||
public function __construct(
|
||||
public array|string $switch,
|
||||
public array|string $toggle,
|
||||
public string $description = "",
|
||||
public int $argument = self::ARGUMENT_NONE,
|
||||
public mixed $default = null,
|
||||
public mixed $awaiting = null,
|
||||
public OptionValueTypeEnum $value = OptionValueTypeEnum::NoValue,
|
||||
) {
|
||||
$this->setParsedOption();
|
||||
}
|
||||
) {}
|
||||
|
||||
public function getParsedOption() : ParsedOption
|
||||
{
|
||||
return $this->parsed;
|
||||
}
|
||||
|
||||
public function setParsedOption() : void
|
||||
{
|
||||
if ($this->value === OptionValueTypeEnum::NoValue) {
|
||||
$this->parsed = new ToggleOption($this);
|
||||
}
|
||||
elseif ($this->value === OptionValueTypeEnum::OptionalValue || $this->value === OptionValueTypeEnum::RequiredValue ) {
|
||||
$this->parsed = new ValueOption($this);
|
||||
}
|
||||
elseif ($this->value === OptionValueTypeEnum::KeyValue) {
|
||||
$this->parsed = new KeyValueOption($this);
|
||||
}
|
||||
elseif ($this->value === OptionValueTypeEnum::CountValue) {
|
||||
$this->parsed = new CountOption($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function awaitingValue() : bool
|
||||
{
|
||||
return match($this->value) {
|
||||
OptionValueTypeEnum::NoValue => false,
|
||||
OptionValueTypeEnum::CountValue => false,
|
||||
OptionValueTypeEnum::RequiredValue => $this->parsed->getValue() === null,
|
||||
OptionValueTypeEnum::OptionalValue => $this->parsed->getValue() === null,
|
||||
OptionValueTypeEnum::MultipleValue => true,
|
||||
OptionValueTypeEnum::KeyValue => true,
|
||||
};
|
||||
}
|
||||
|
||||
public function matchShortSwitch(string $switch) : bool
|
||||
{
|
||||
$switch = strtolower($switch);
|
||||
|
||||
foreach((array) $this->switch as $item) {
|
||||
if ( ! str_starts_with($item, '--') && substr($item, 0, 1) === '-' ) {
|
||||
$singleItem = substr($item, 1, 1);
|
||||
|
||||
if (substr($item, 1, 1) === $switch) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function matchLongSwitch(string $switch) : bool
|
||||
{
|
||||
list($key, $value) = array_pad(explode('=', $switch, 2), 2, null);
|
||||
|
||||
foreach((array) $this->switch as $item) {
|
||||
if ( str_starts_with($item, '--') ) {
|
||||
if (strtolower(substr($item, 2)) === strtolower($key)) {
|
||||
if ($value !== null) {
|
||||
$this->value($value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function toggle() : void
|
||||
{
|
||||
match ($this->value) {
|
||||
OptionValueTypeEnum::NoValue => $this->parsed->toggle(),
|
||||
OptionValueTypeEnum::CountValue => $this->parsed->toggle(),
|
||||
OptionValueTypeEnum::OptionalValue => $this->parsed->setValue(true),
|
||||
};
|
||||
}
|
||||
|
||||
public function value(mixed $value = null) : mixed
|
||||
{
|
||||
if ($value === null) {
|
||||
return $this->parsed->getValue();
|
||||
}
|
||||
|
||||
return match ($this->value) {
|
||||
OptionValueTypeEnum::MultipleValue => $this->parsed->addValue($value),
|
||||
OptionValueTypeEnum::RequiredValue => $this->parsed->setValue($value),
|
||||
OptionValueTypeEnum::OptionalValue => $this->parsed->setValue($value),
|
||||
OptionValueTypeEnum::KeyValue => $this->parsed->setRawValue($value),
|
||||
OptionValueTypeEnum::NoValue => throw new \RuntimeException(sprintf("A value was given to an option (%s) which was not awaiting any", implode(', ', (array) $this->switch))),
|
||||
OptionValueTypeEnum::CountValue => is_numeric($value) ? $this->parsed->setValue($value) : throw new \RuntimeException(sprintf("A value was given to an option (%s) which is countable", implode(', ', (array) $this->switch))),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
class CountOption extends ParsedOption
|
||||
{
|
||||
protected int $count = 0;
|
||||
|
||||
public function toggle() : void
|
||||
{
|
||||
$this->count++;
|
||||
}
|
||||
|
||||
public function getValue() : null|int
|
||||
{
|
||||
return $this->count ?: null;
|
||||
}
|
||||
|
||||
public function setValue(int $value) : void
|
||||
{
|
||||
$this->count = $value;
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
class KeyValueOption extends ParsedOption
|
||||
{
|
||||
protected array $values = [];
|
||||
|
||||
public function setValue(string $key, string $value) : void
|
||||
{
|
||||
$this->values[$key] = $value;
|
||||
}
|
||||
|
||||
public function setRawValue(string $value) : void
|
||||
{
|
||||
if (! str_contains($value, '=')) {
|
||||
throw new \RuntimeException(sprintf("A key=value notation was awaited, received %s instead", $value));
|
||||
}
|
||||
|
||||
list($key, $value) = explode('=', trim($value), 2);
|
||||
|
||||
$this->setValue($key, $value);
|
||||
}
|
||||
|
||||
public function getValue() : null|array
|
||||
{
|
||||
return $this->values ?: null;
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
class MultipleValueOption extends ParsedOption
|
||||
{
|
||||
protected array $value;
|
||||
|
||||
public function addValue(string $value) : void
|
||||
{
|
||||
$this->value[] = $value;
|
||||
}
|
||||
|
||||
public function setValue(array $value) : void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue() : null|array
|
||||
{
|
||||
return $this->value ?? null;
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
use Mcnd\CLI\Option;
|
||||
|
||||
abstract class ParsedOption
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Option $attribute,
|
||||
) {}
|
||||
|
||||
public abstract function getValue() : mixed;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
class ToggleOption extends ParsedOption
|
||||
{
|
||||
protected bool $value = false;
|
||||
|
||||
public function toggle() : void
|
||||
{
|
||||
$this->value = true;
|
||||
}
|
||||
|
||||
public function getValue() : null|true
|
||||
{
|
||||
return $this->value ?: null;
|
||||
}
|
||||
|
||||
public function toggled() : bool
|
||||
{
|
||||
return (bool) $this->getValue();
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI\Option;
|
||||
|
||||
class ValueOption extends ParsedOption
|
||||
{
|
||||
protected string $value;
|
||||
|
||||
public function setValue(string $value) : void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue() : null|string
|
||||
{
|
||||
return $this->value ?? null;
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI;
|
||||
|
||||
class OptionStack
|
||||
{
|
||||
public function __construct(
|
||||
public array $options = []
|
||||
) {}
|
||||
|
||||
public function merge(OptionStack|array $stack) : void
|
||||
{
|
||||
$this->options = array_merge($this->options, is_array($stack) ? $stack : $stack->options);
|
||||
}
|
||||
|
||||
public function append(Option $option) : void
|
||||
{
|
||||
$this->options[] = $option;
|
||||
}
|
||||
|
||||
public function get(string $switch) : Option|false
|
||||
{
|
||||
foreach($this->options as $option) {
|
||||
if ($option->matchShortSwitch($switch) || $option->matchLongSwitch($switch)) {
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches the exact option (short or long switch) found from the stack
|
||||
*
|
||||
* @param string $switch
|
||||
* @return Option|null
|
||||
*/
|
||||
public function matchOption(string $switch) : bool|Option
|
||||
{
|
||||
$matched = false;
|
||||
|
||||
# Long switch
|
||||
if (str_starts_with($switch, '--')) {
|
||||
$switch = substr($switch, 2);
|
||||
|
||||
foreach($this->options as $option) {
|
||||
if ( $option->matchLongSwitch($switch) ) {
|
||||
$matched = true;
|
||||
|
||||
if ( $option->awaitingValue() ) {
|
||||
return $option;
|
||||
}
|
||||
# matching --switch
|
||||
else {
|
||||
$option->toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
} # Short switch
|
||||
elseif (substr($switch, 0, 1) === '-') {
|
||||
$switch = substr($switch, 1);
|
||||
$split = str_split($switch);
|
||||
|
||||
foreach($split as $index => $singleSwitch) {
|
||||
foreach ($this->options as $option) {
|
||||
if ( $option->matchShortSwitch($singleSwitch) ) {
|
||||
$matched = true;
|
||||
|
||||
if ( $option->awaitingValue() ) {
|
||||
# matching -abcValueOfC
|
||||
if ( $index < count($split) - 1 ) {
|
||||
$option->value(substr($switch, $index + 1));
|
||||
|
||||
return false;
|
||||
}
|
||||
# matching -abc
|
||||
else {
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$option->toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matched;
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Mcnd\CLI;
|
||||
|
||||
enum OptionValueTypeEnum
|
||||
{
|
||||
case NoValue; # -s
|
||||
case RequiredValue; # -s value / -svalue
|
||||
case OptionalValue; # -s value / -svalue
|
||||
case MultipleValue; # -s value1 -s value2 -svalue3 -sothervalue4 / --var value1 --var value2
|
||||
case KeyValue; # -s key=value / --var key=value
|
||||
case CountValue; # -ssss / -s 4 / --var 4
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user