Upstream thephpleague/climate state of the touched files before c418b12a45
This commit is contained in:
commit
e8a9b69c46
2 files changed
+557
No files matched your search
@@ -0,0 +1,424 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace League\CLImate\Argument;
|
||||||
|
|
||||||
|
use League\CLImate\Exceptions\InvalidArgumentException;
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Filter class to find various types of arguments
|
||||||
|
*
|
||||||
|
* @var \League\CLImate\Argument\Filter $filter
|
||||||
|
*/
|
||||||
|
protected $filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary builder class
|
||||||
|
*
|
||||||
|
* @var \League\CLImate\Argument\Summary $summary
|
||||||
|
*/
|
||||||
|
protected $summary;
|
||||||
|
|
||||||
|
protected $trailing;
|
||||||
|
|
||||||
|
protected $trailingArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of unknown arguments and best argument suggestion.
|
||||||
|
*
|
||||||
|
* The key corresponds to the unknown argument and the value to the
|
||||||
|
* argument suggestion, if any.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $unknowPrefixedArguments = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum similarity percentage to detect similar arguments.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
protected $minimumSimilarityPercentage = 0.6;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->summary = new Summary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Filter $filter
|
||||||
|
* @param Argument[] $arguments
|
||||||
|
*
|
||||||
|
* @return \League\CLImate\Argument\Parser
|
||||||
|
*/
|
||||||
|
public function setFilter($filter, $arguments)
|
||||||
|
{
|
||||||
|
$this->filter = $filter;
|
||||||
|
$this->filter->setArguments($arguments);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse command line arguments into CLImate arguments.
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws InvalidArgumentException if required arguments aren't defined.
|
||||||
|
*/
|
||||||
|
public function parse(?array $argv = null)
|
||||||
|
{
|
||||||
|
$cliArguments = $this->arguments($argv);
|
||||||
|
|
||||||
|
if (in_array('--', $cliArguments)) {
|
||||||
|
$cliArguments = $this->removeTrailingArguments($cliArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
$unParsedArguments = $this->prefixedArguments($cliArguments);
|
||||||
|
|
||||||
|
// Searches for unknown prefixed arguments and finds a suggestion
|
||||||
|
// within the list of valid arguments.
|
||||||
|
$this->unknowPrefixedArguments($unParsedArguments);
|
||||||
|
|
||||||
|
$this->nonPrefixedArguments($unParsedArguments);
|
||||||
|
|
||||||
|
// After parsing find out which arguments were required but not
|
||||||
|
// defined on the command line.
|
||||||
|
$missingArguments = $this->filter->missing();
|
||||||
|
|
||||||
|
if (count($missingArguments) > 0) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'The following arguments are required: '
|
||||||
|
. $this->summary->short($missingArguments) . '.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the command name.
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function command(?array $argv = null)
|
||||||
|
{
|
||||||
|
return $this->getCommandAndArguments($argv)['command'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the passed arguments.
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function arguments(?array $argv = null)
|
||||||
|
{
|
||||||
|
return $this->getCommandAndArguments($argv)['arguments'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the trailing arguments
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function trailing()
|
||||||
|
{
|
||||||
|
return $this->trailing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the trailing arguments as an array
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function trailingArray()
|
||||||
|
{
|
||||||
|
return $this->trailingArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the trailing arguments from the parser and set them aside
|
||||||
|
*
|
||||||
|
* @param array $arguments
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function removeTrailingArguments(array $arguments)
|
||||||
|
{
|
||||||
|
$trailing = array_splice($arguments, array_search('--', $arguments));
|
||||||
|
array_shift($trailing);
|
||||||
|
$this->trailingArray = $trailing;
|
||||||
|
$this->trailing = implode(' ', $trailing);
|
||||||
|
|
||||||
|
return $arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse command line options into prefixed CLImate arguments.
|
||||||
|
*
|
||||||
|
* Prefixed arguments are arguments with a prefix (-) or a long prefix (--)
|
||||||
|
* on the command line.
|
||||||
|
*
|
||||||
|
* Return the arguments passed on the command line that didn't match up with
|
||||||
|
* prefixed arguments so they can be assigned to non-prefixed arguments.
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prefixedArguments(array $argv = [])
|
||||||
|
{
|
||||||
|
foreach ($argv as $key => $passed_argument) {
|
||||||
|
$argv = $this->trySettingArgumentValue($argv, $key, $passed_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send un-parsed arguments back upstream.
|
||||||
|
return array_values($argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse unset command line options into non-prefixed CLImate arguments.
|
||||||
|
*
|
||||||
|
* Non-prefixed arguments are parsed after the prefixed arguments on the
|
||||||
|
* command line, in the order that they're defined in the script.
|
||||||
|
*
|
||||||
|
* @param array $unParsedArguments
|
||||||
|
*/
|
||||||
|
protected function nonPrefixedArguments(array $unParsedArguments = [])
|
||||||
|
{
|
||||||
|
foreach ($this->filter->withoutPrefix() as $key => $argument) {
|
||||||
|
if (isset($unParsedArguments[$key])) {
|
||||||
|
$argument->setValue($unParsedArguments[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the name and value of the argument passed in
|
||||||
|
*
|
||||||
|
* @param string $cliArgument
|
||||||
|
* @return string[] [$name, $value]
|
||||||
|
*/
|
||||||
|
protected function getNameAndValue($cliArgument)
|
||||||
|
{
|
||||||
|
// Look for arguments defined in the "key=value" format.
|
||||||
|
if (strpos($cliArgument, '=') !== false) {
|
||||||
|
return explode('=', $cliArgument, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the argument isn't in "key=value" format then assume it's in
|
||||||
|
// "key value" format and define the value after we've found the
|
||||||
|
// matching CLImate argument.
|
||||||
|
return [$cliArgument, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to set the an argument's value and remove applicable
|
||||||
|
* arguments from array
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
* @param int $key
|
||||||
|
* @param string $passed_argument
|
||||||
|
*
|
||||||
|
* @return array The new $argv
|
||||||
|
*/
|
||||||
|
protected function trySettingArgumentValue($argv, $key, $passed_argument)
|
||||||
|
{
|
||||||
|
list($name, $value) = $this->getNameAndValue($passed_argument);
|
||||||
|
|
||||||
|
// Look for the argument in our defined $arguments
|
||||||
|
// and assign their value.
|
||||||
|
if (!($argument = $this->findPrefixedArgument($name))) {
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found an argument key, so take it out of the array.
|
||||||
|
unset($argv[$key]);
|
||||||
|
|
||||||
|
return $this->setArgumentValue($argv, $argument, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the argument's value
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
* @param Argument $argument
|
||||||
|
* @param int $key
|
||||||
|
* @param string|null $value
|
||||||
|
*
|
||||||
|
* @return array The new $argv
|
||||||
|
*/
|
||||||
|
protected function setArgumentValue($argv, $argument, $key, $value)
|
||||||
|
{
|
||||||
|
// Arguments are given the value true if they only need to
|
||||||
|
// be defined on the command line to be set.
|
||||||
|
if ($argument->noValue()) {
|
||||||
|
$argument->setValue(true);
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($value)) {
|
||||||
|
if (count($argv) === 0) {
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value wasn't previously defined in "key=value"
|
||||||
|
// format then define it from the next command argument.
|
||||||
|
$nextArgvValue = $argv[$key + 1];
|
||||||
|
if ($this->isValidArgumentValue($nextArgvValue)) {
|
||||||
|
$argument->setValue($nextArgvValue);
|
||||||
|
unset($argv[$key + 1]);
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$argument->setValue($value);
|
||||||
|
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the value is considered a valid input value.
|
||||||
|
*
|
||||||
|
* @param $argumentValue
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isValidArgumentValue($argumentValue)
|
||||||
|
{
|
||||||
|
return empty($this->findPrefixedArgument($argumentValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for argument in defined prefix arguments
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return Argument|false
|
||||||
|
*/
|
||||||
|
protected function findPrefixedArgument($name)
|
||||||
|
{
|
||||||
|
foreach ($this->filter->withPrefix() as $argument) {
|
||||||
|
if (in_array($name, ["-{$argument->prefix()}", "--{$argument->longPrefix()}"])) {
|
||||||
|
return $argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull a command name and arguments from $argv.
|
||||||
|
*
|
||||||
|
* @param array $argv
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getCommandAndArguments(?array $argv = null)
|
||||||
|
{
|
||||||
|
// If no $argv is provided then use the global PHP defined $argv.
|
||||||
|
if (is_null($argv)) {
|
||||||
|
global $argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments = $argv;
|
||||||
|
$command = array_shift($arguments);
|
||||||
|
|
||||||
|
return compact('arguments', 'command');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes unknown prefixed arguments and sets suggestions if no matching
|
||||||
|
* prefix is found.
|
||||||
|
*
|
||||||
|
* @param array $unParsedArguments The array of unparsed arguments to
|
||||||
|
* process.
|
||||||
|
*/
|
||||||
|
protected function unknowPrefixedArguments(array $unParsedArguments)
|
||||||
|
{
|
||||||
|
foreach ($unParsedArguments as $arg) {
|
||||||
|
$unknowArgumentName = $this->getUnknowArgumentName($arg);
|
||||||
|
if (!$this->findPrefixedArgument($unknowArgumentName)) {
|
||||||
|
if (is_null($unknowArgumentName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$suggestion = $this->findSuggestionsForUnknowPrefixedArguments(
|
||||||
|
$unknowArgumentName,
|
||||||
|
$this->filter->withPrefix()
|
||||||
|
);
|
||||||
|
$this->setSuggestion($unknowArgumentName, $suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the suggestion for an unknown argument name.
|
||||||
|
*
|
||||||
|
* @param string $unknowArgName The name of the unknown argument.
|
||||||
|
* @param string $suggestion The suggestion for the unknown argument.
|
||||||
|
*/
|
||||||
|
protected function setSuggestion(string $unknowArgName, string $suggestion)
|
||||||
|
{
|
||||||
|
$this->unknowPrefixedArguments[$unknowArgName] = $suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the unknown argument name from a given argument string.
|
||||||
|
*
|
||||||
|
* @param string $arg The argument string to process.
|
||||||
|
* @return string|null The extracted unknown argument name or null if not
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
protected function getUnknowArgumentName(string $arg)
|
||||||
|
{
|
||||||
|
if (preg_match('/^[-]{1,2}([^-]+?)(?:=|$)/', $arg, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the most similar known argument for an unknown prefixed argument.
|
||||||
|
*
|
||||||
|
* @param string $argName The name of the unknown argument to find
|
||||||
|
* suggestions for.
|
||||||
|
* @param array $argList The list of known arguments to compare against.
|
||||||
|
* @return string The most similar known argument name.
|
||||||
|
*/
|
||||||
|
protected function findSuggestionsForUnknowPrefixedArguments(
|
||||||
|
string $argName,
|
||||||
|
array $argList
|
||||||
|
) {
|
||||||
|
$mostSimilar = '';
|
||||||
|
$greatestSimilarity = $this->minimumSimilarityPercentage * 100;
|
||||||
|
foreach ($argList as $arg) {
|
||||||
|
similar_text($argName, $arg->name(), $percent);
|
||||||
|
if ($percent > $greatestSimilarity) {
|
||||||
|
$greatestSimilarity = $percent;
|
||||||
|
$mostSimilar = $arg->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $mostSimilar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of unknown prefixed arguments and their suggestions.
|
||||||
|
*
|
||||||
|
* @return array The list of unknown prefixed arguments and their suggestions.
|
||||||
|
*/
|
||||||
|
public function getUnknowPrefixedArgumentsAndSuggestions()
|
||||||
|
{
|
||||||
|
return $this->unknowPrefixedArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum similarity percentage for finding suggestions.
|
||||||
|
*
|
||||||
|
* @param float $percentage The minimum similarity percentage to set.
|
||||||
|
*/
|
||||||
|
public function setMinimumSimilarityPercentage(float $percentage)
|
||||||
|
{
|
||||||
|
$this->minimumSimilarityPercentage = $percentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace League\CLImate\Tests\Argument;
|
||||||
|
|
||||||
|
use League\CLImate\Argument\Argument;
|
||||||
|
use League\CLImate\Argument\Manager;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ManagerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $manager;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->manager = new Manager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testDefined1()
|
||||||
|
{
|
||||||
|
$argument = Argument::createFromArray("test", [
|
||||||
|
"prefix" => "t",
|
||||||
|
"longPrefix" => "test",
|
||||||
|
]);
|
||||||
|
$this->manager->add($argument);
|
||||||
|
|
||||||
|
$result = $this->manager->defined("test", ["command", "--test"]);
|
||||||
|
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
public function testDefined2()
|
||||||
|
{
|
||||||
|
$result = $this->manager->defined("test");
|
||||||
|
|
||||||
|
$this->assertFalse($result);
|
||||||
|
}
|
||||||
|
public function testDefined3()
|
||||||
|
{
|
||||||
|
$argument = Argument::createFromArray("lorem", [
|
||||||
|
"prefix" => "l",
|
||||||
|
"longPrefix" => "Лорем",
|
||||||
|
]);
|
||||||
|
$this->manager->add($argument);
|
||||||
|
|
||||||
|
$result = $this->manager->defined("lorem", ["command", "--Лорем"]);
|
||||||
|
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProvidedReturnsTrueWhenValueGivenOnCommandLine()
|
||||||
|
{
|
||||||
|
$this->manager->add([
|
||||||
|
'foo' => ['prefix' => 'f', 'defaultValue' => 'bar'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->manager->parse(['command', '-f', 'baz']);
|
||||||
|
|
||||||
|
$this->assertTrue($this->manager->provided('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProvidedReturnsFalseWhenFallingBackToDefault()
|
||||||
|
{
|
||||||
|
$this->manager->add([
|
||||||
|
'foo' => ['prefix' => 'f', 'defaultValue' => 'bar'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->manager->parse(['command']);
|
||||||
|
|
||||||
|
$this->assertFalse($this->manager->provided('foo'));
|
||||||
|
$this->assertEquals('bar', $this->manager->get('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProvidedReturnsFalseForUnknownArgument()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->manager->provided('missing'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItParsesAnOptionalArgument()
|
||||||
|
{
|
||||||
|
$this->manager->add([
|
||||||
|
'foo' => ['prefix' => 'f'],
|
||||||
|
'bar' => ['prefix' => 'b']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->manager->parse(['command', '-f', '-b', 'abc']);
|
||||||
|
|
||||||
|
$this->assertEquals('', $this->manager->get('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItStoresTrailingInArray()
|
||||||
|
{
|
||||||
|
$this->manager->add([
|
||||||
|
'foo' => ['prefix' => 'f']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->manager->parse(['command', '-f', '--', 'test', 'trailing with spaces']);
|
||||||
|
|
||||||
|
$this->assertEquals('test trailing with spaces', $this->manager->trailing());
|
||||||
|
$this->assertEquals(['test', 'trailing with spaces'], $this->manager->trailingArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItSuggestAlternativesToUnknowArguments()
|
||||||
|
{
|
||||||
|
$this->manager->add([
|
||||||
|
'user' => [
|
||||||
|
'longPrefix' => 'user',
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'longPrefix' => 'password',
|
||||||
|
],
|
||||||
|
'flag' => [
|
||||||
|
'longPrefix' => 'flag',
|
||||||
|
'noValue' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$argv = [
|
||||||
|
'test-script',
|
||||||
|
'--user=baz',
|
||||||
|
'--pass=123',
|
||||||
|
'--fag',
|
||||||
|
'--xyz',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->manager->parse($argv);
|
||||||
|
$processed = $this->manager->getUnknowPrefixedArgumentsAndSuggestions();
|
||||||
|
|
||||||
|
$this->assertCount(3, $processed);
|
||||||
|
$this->assertEquals('password', $processed['pass']);
|
||||||
|
$this->assertEquals('flag', $processed['fag']);
|
||||||
|
$this->assertEquals('', $processed['xyz']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user