diff --git a/src/Argument/Manager.php b/src/Argument/Manager.php index a18410b2..4f34a765 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 1badc638..a4f62d98 100644 --- a/src/Argument/Parser.php +++ b/src/Argument/Parser.php @@ -1,11 +1,11 @@ summary = new Summary(); @@ -61,6 +76,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 @@ -69,9 +88,9 @@ public function parse(array $argv = null) if (count($missingArguments) > 0) { throw new InvalidArgumentException( - 'The following arguments are required: ' - . $this->summary->short($missingArguments) . '.' - ); + 'The following arguments are required: ' + . $this->summary->short($missingArguments) . '.' + ); } } @@ -302,8 +321,102 @@ protected function getCommandAndArguments(array $argv = null) } $arguments = $argv; - $command = array_shift($arguments); + $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; + } } diff --git a/src/TerminalObject/Dynamic/Progress.php b/src/TerminalObject/Dynamic/Progress.php index e673eda3..3d55ea50 100644 --- a/src/TerminalObject/Dynamic/Progress.php +++ b/src/TerminalObject/Dynamic/Progress.php @@ -1,11 +1,11 @@ drawProgressBar($current, $label); $this->current = $current; - $this->label = $label; + $this->label = $label; } /** @@ -138,7 +138,6 @@ public function forceRedraw($force = true) return $this; } - /** * Update a progress bar using an iterable. * @@ -168,7 +167,6 @@ public function each($items, callable $callback = null) } } - /** * Draw the progress bar, if necessary * @@ -207,7 +205,7 @@ protected function getProgressBar($current, $label) // Move the cursor up and clear it to the end $line_count = $this->has_label_line ? 2 : 1; - $progress_bar = $this->util->cursor->up($line_count); + $progress_bar = $this->util->cursor->up($line_count); $progress_bar .= $this->util->cursor->startOfCurrentLine(); $progress_bar .= $this->util->cursor->deleteCurrentLine(); $progress_bar .= $this->getProgressBarStr($current, $label); @@ -234,13 +232,13 @@ protected function getProgressBarStr($current, $label) $percentage = $current / $this->total; $bar_length = round($this->getBarStrLen() * $percentage); - $bar = $this->getBar($bar_length); - $number = $this->percentageFormatted($percentage); + $bar = $this->getBar($bar_length); + $number = $this->percentageFormatted($percentage); if ($label) { $label = $this->labelFormatted($label); - // If this line doesn't have a label, but we've had one before, - // then ensure the label line is cleared + // If this line doesn't have a label, but we've had one before, + // then ensure the label line is cleared } elseif ($this->has_label_line) { $label = $this->labelFormatted(''); } @@ -257,7 +255,7 @@ protected function getProgressBarStr($current, $label) */ protected function getBar($length) { - $bar = str_repeat('=', $length); + $bar = str_repeat('=', $length); $padding = str_repeat(' ', $this->getBarStrLen() - $length); return "{$bar}>{$padding}"; @@ -312,4 +310,24 @@ protected function shouldRedraw($percentage, $label) { return ($this->force_redraw || $percentage != $this->current_percentage || $label != $this->label); } + + /** + * Gets the current progress value. + * + * @return int The current progress value. + */ + public function getCurrent() + { + return $this->current; + } + + /** + * Gets de total value for the progress. + * + * @return int The total progress value. + */ + public function getTotal() + { + return $this->total; + } } diff --git a/tests/Argument/ManagerTest.php b/tests/Argument/ManagerTest.php index 2a66c186..f5f566c9 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']); + } }