Skip to content

Commit 9e8a8b1

Browse files
everton3xduncan3dc
authored andcommitted
Adds argument suggestion support for unknown arguments
1 parent 540f61e commit 9e8a8b1

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

src/Argument/Manager.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,24 @@ public function trailingArray()
258258
{
259259
return $this->parser->trailingArray();
260260
}
261+
262+
/**
263+
* Returns the list of unknown prefixed arguments and their suggestions.
264+
*
265+
* @return array The list of unknown prefixed arguments and their suggestions.
266+
*/
267+
public function getUnknowPrefixedArgumentsAndSuggestions()
268+
{
269+
return $this->parser->getUnknowPrefixedArgumentsAndSuggestions();
270+
}
271+
272+
/**
273+
* Sets the minimum similarity percentage for finding suggestions.
274+
*
275+
* @param float $percentage The minimum similarity percentage to set.
276+
*/
277+
public function setMinimumSimilarityPercentage(float $percentage)
278+
{
279+
$this->parser->setMinimumSimilarityPercentage($percentage);
280+
}
261281
}

src/Argument/Parser.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ class Parser
2424

2525
protected $trailingArray;
2626

27+
/**
28+
* List of unknown arguments and best argument suggestion.
29+
*
30+
* The key corresponds to the unknown argument and the value to the
31+
* argument suggestion, if any.
32+
*
33+
* @var array
34+
*/
35+
protected $unknowPrefixedArguments = [];
36+
37+
/**
38+
* Minimum similarity percentage to detect similar arguments.
39+
*
40+
* @var float
41+
*/
42+
protected $minimumSimilarityPercentage = 0.6;
43+
2744
public function __construct()
2845
{
2946
$this->summary = new Summary();
@@ -61,6 +78,10 @@ public function parse(array $argv = null)
6178

6279
$unParsedArguments = $this->prefixedArguments($cliArguments);
6380

81+
// Searches for unknown prefixed arguments and finds a suggestion
82+
// within the list of valid arguments.
83+
$this->unknowPrefixedArguments($unParsedArguments);
84+
6485
$this->nonPrefixedArguments($unParsedArguments);
6586

6687
// After parsing find out which arguments were required but not
@@ -306,4 +327,98 @@ protected function getCommandAndArguments(array $argv = null)
306327

307328
return compact('arguments', 'command');
308329
}
330+
331+
/**
332+
* Processes unknown prefixed arguments and sets suggestions if no matching
333+
* prefix is found.
334+
*
335+
* @param array $unParsedArguments The array of unparsed arguments to
336+
* process.
337+
*/
338+
protected function unknowPrefixedArguments(array $unParsedArguments)
339+
{
340+
foreach ($unParsedArguments as $arg) {
341+
$unknowArgumentName = $this->getUnknowArgumentName($arg);
342+
if (!$this->findPrefixedArgument($unknowArgumentName)) {
343+
if (is_null($unknowArgumentName)) {
344+
continue;
345+
}
346+
$suggestion = $this->findSuggestionsForUnknowPrefixedArguments(
347+
$unknowArgumentName,
348+
$this->filter->withPrefix()
349+
);
350+
$this->setSuggestion($unknowArgumentName, $suggestion);
351+
}
352+
}
353+
}
354+
355+
/**
356+
* Sets the suggestion for an unknown argument name.
357+
*
358+
* @param string $unknowArgName The name of the unknown argument.
359+
* @param string $suggestion The suggestion for the unknown argument.
360+
*/
361+
protected function setSuggestion(string $unknowArgName, string $suggestion)
362+
{
363+
$this->unknowPrefixedArguments[$unknowArgName] = $suggestion;
364+
}
365+
366+
/**
367+
* Extracts the unknown argument name from a given argument string.
368+
*
369+
* @param string $arg The argument string to process.
370+
* @return string|null The extracted unknown argument name or null if not
371+
* found.
372+
*/
373+
protected function getUnknowArgumentName(string $arg)
374+
{
375+
if (preg_match('/^[-]{1,2}([^-]+?)(?:=|$)/', $arg, $matches)) {
376+
return $matches[1];
377+
}
378+
return null;
379+
}
380+
381+
/**
382+
* Finds the most similar known argument for an unknown prefixed argument.
383+
*
384+
* @param string $argName The name of the unknown argument to find
385+
* suggestions for.
386+
* @param array $argList The list of known arguments to compare against.
387+
* @return string The most similar known argument name.
388+
*/
389+
protected function findSuggestionsForUnknowPrefixedArguments(
390+
string $argName,
391+
array $argList
392+
) {
393+
$mostSimilar = '';
394+
$greatestSimilarity = $this->minimumSimilarityPercentage * 100;
395+
foreach ($argList as $arg) {
396+
similar_text($argName, $arg->name(), $percent);
397+
if ($percent > $greatestSimilarity) {
398+
$greatestSimilarity = $percent;
399+
$mostSimilar = $arg->name();
400+
}
401+
}
402+
return $mostSimilar;
403+
}
404+
405+
/**
406+
* Returns the list of unknown prefixed arguments and their suggestions.
407+
*
408+
* @return array The list of unknown prefixed arguments and their suggestions.
409+
*/
410+
public function getUnknowPrefixedArgumentsAndSuggestions()
411+
{
412+
return $this->unknowPrefixedArguments;
413+
}
414+
415+
/**
416+
* Sets the minimum similarity percentage for finding suggestions.
417+
*
418+
* @param float $percentage The minimum similarity percentage to set.
419+
*/
420+
public function setMinimumSimilarityPercentage(float $percentage)
421+
{
422+
$this->minimumSimilarityPercentage = $percentage;
423+
}
309424
}

tests/Argument/ManagerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,36 @@ public function testItStoresTrailingInArray()
7070
$this->assertEquals('test trailing with spaces', $this->manager->trailing());
7171
$this->assertEquals(['test', 'trailing with spaces'], $this->manager->trailingArray());
7272
}
73+
74+
public function testItSuggestAlternativesToUnknowArguments()
75+
{
76+
$this->manager->add([
77+
'user' => [
78+
'longPrefix' => 'user',
79+
],
80+
'password' => [
81+
'longPrefix' => 'password',
82+
],
83+
'flag' => [
84+
'longPrefix' => 'flag',
85+
'noValue' => true,
86+
],
87+
]);
88+
89+
$argv = [
90+
'test-script',
91+
'--user=baz',
92+
'--pass=123',
93+
'--fag',
94+
'--xyz',
95+
];
96+
97+
$this->manager->parse($argv);
98+
$processed = $this->manager->getUnknowPrefixedArgumentsAndSuggestions();
99+
100+
$this->assertCount(3, $processed);
101+
$this->assertEquals('password', $processed['pass']);
102+
$this->assertEquals('flag', $processed['fag']);
103+
$this->assertEquals('', $processed['xyz']);
104+
}
73105
}

0 commit comments

Comments
 (0)