diff --git a/src/Argument/Manager.php b/src/Argument/Manager.php index a18410b..324c174 100644 --- a/src/Argument/Manager.php +++ b/src/Argument/Manager.php @@ -258,4 +258,24 @@ public function trailingArray() { return $this->parser->trailingArray(); } + + /** + * 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->parser->getUnknowPrefixedArgumentsAndSuggestions(); + } + + /** + * Sets the minimum similarity percentage for finding suggestions. + * + * @param float $percentage The minimum similarity percentage to set. + */ + public function setMinimumSimilarityPercentage(float $percentage) + { + $this->parser->setMinimumSimilarityPercentage($percentage); + } } diff --git a/src/Argument/Parser.php b/src/Argument/Parser.php index 1badc63..242aeb9 100644 --- a/src/Argument/Parser.php +++ b/src/Argument/Parser.php @@ -24,6 +24,23 @@ class Parser 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(); @@ -61,6 +78,10 @@ public function parse(array $argv = null) $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 @@ -306,4 +327,98 @@ protected function getCommandAndArguments(array $argv = null) 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; + } } diff --git a/tests/Argument/ManagerTest.php b/tests/Argument/ManagerTest.php index dcb46d3..ae19d52 100644 --- a/tests/Argument/ManagerTest.php +++ b/tests/Argument/ManagerTest.php @@ -70,4 +70,36 @@ public function testItStoresTrailingInArray() $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']); + } }