Skip to content

Commit

Permalink
Adds argument suggestion support for unknown arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
everton3x authored and duncan3dc committed Oct 30, 2024
1 parent 540f61e commit 9e8a8b1
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/Argument/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
115 changes: 115 additions & 0 deletions src/Argument/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
32 changes: 32 additions & 0 deletions tests/Argument/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
}

0 comments on commit 9e8a8b1

Please sign in to comment.