merged branch lyrixx/feat-auto-suggest (PR #3325)
Commits -------e5edf5a
[Console] Fixed CS8abf506
[Console] Added abbreviation into search for bad command / namespacec6203bc
[Console] Added namespace suggest on bad namespace name117359a
[Console] fixed CS according to PR commentdd0d97e
[Console] Added suggest on bad command name Discussion ---------- [Console] Added suggest on bad command name Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass: yes Fixes the following tickets: - Todo: namespace ? Added something like in `git` : if user type a wrong command and if a close alternative exists, Command compenent will display a list of similar command(s). Note : It does not work with namespace. If this PR will be merged, I could work on namespace. see : https://github.com/fabpot/Twig/blob/master/lib/Twig/Environment.php#L1003 --------------------------------------------------------------------------- by fabpot at 2012-02-11T18:54:49Z I think we need it to also work on namespace before merging. Is it possible? --------------------------------------------------------------------------- by henrikbjorn at 2012-02-11T19:01:06Z could maybe use similar_text ? --------------------------------------------------------------------------- by lyrixx at 2012-02-11T19:01:55Z Yes. I will work on it asap --------------------------------------------------------------------------- by lyrixx at 2012-02-11T20:06:43Z I added code for namespace @henrikbjorn I did the same logic as in twig. --------------------------------------------------------------------------- by lyrixx at 2012-02-11T20:27:48Z Note : Travis tests failed : http://travis-ci.org/#!/lyrixx/symfony/builds/663216 ```before_script: Execution of 'php vendors.php' took longer than 600 seconds and was terminated. Consider rewriting your stuff in AssemblyScript, we've heard it handles Web Scale™``` But tests are OK on my laptop --------------------------------------------------------------------------- by stof at 2012-02-11T20:41:15Z Well, it may be due to github issues during the setup of the vendors. There is some issues regularly because of the DDoS attack. --------------------------------------------------------------------------- by lyrixx at 2012-02-11T20:58:07Z Yes, i guessed it :-) that's why i notice it work on my laptop --------------------------------------------------------------------------- by fabpot at 2012-02-11T23:11:08Z This code won't work if you use abbreviations instead of the full namespace or command name. --------------------------------------------------------------------------- by lyrixx at 2012-02-12T23:30:04Z I added code to manage abbreviations. But I'm not sure what you are expecting. Can you try it and give me some feedback ? P.S. : Travis failed again, but tests pass on my laptop.
This commit is contained in:
commit
9f05d4a103
@ -489,7 +489,18 @@ class Application
|
||||
$abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $allNamespaces)))));
|
||||
|
||||
if (!isset($abbrevs[$part])) {
|
||||
throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
|
||||
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
|
||||
|
||||
if (1 <= $i) {
|
||||
$part = implode(':', $found).':'.$part;
|
||||
}
|
||||
|
||||
if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) {
|
||||
$message .= "\n\nDid you mean one of these?\n ";
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
if (count($abbrevs[$part]) > 1) {
|
||||
@ -555,16 +566,23 @@ class Application
|
||||
}
|
||||
}
|
||||
|
||||
$abbrevs = static::getAbbreviations(array_unique($aliases));
|
||||
if (!isset($abbrevs[$searchName])) {
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $name));
|
||||
$aliases = static::getAbbreviations(array_unique($aliases));
|
||||
if (!isset($aliases[$searchName])) {
|
||||
$message = sprintf('Command "%s" is not defined.', $name);
|
||||
|
||||
if ($alternatives = $this->findAlternativeCommands($searchName, $abbrevs)) {
|
||||
$message .= "\n\nDid you mean one of these?\n ";
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
if (count($abbrevs[$searchName]) > 1) {
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($abbrevs[$searchName])));
|
||||
if (count($aliases[$searchName]) > 1) {
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($aliases[$searchName])));
|
||||
}
|
||||
|
||||
return $this->get($abbrevs[$searchName][0]);
|
||||
return $this->get($aliases[$searchName][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -915,4 +933,75 @@ class Application
|
||||
|
||||
return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds alternative commands of $name
|
||||
*
|
||||
* @param string $name The full name of the command
|
||||
* @param array $abbrevs The abbreviations
|
||||
*
|
||||
* @return array A sorted array of similar commands
|
||||
*/
|
||||
private function findAlternativeCommands($name, $abbrevs)
|
||||
{
|
||||
$callback = function($item) {
|
||||
return $item->getName();
|
||||
};
|
||||
|
||||
return $this->findAlternatives($name, $this->commands, $abbrevs, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds alternative namespace of $name
|
||||
*
|
||||
* @param string $name The full name of the namespace
|
||||
* @param array $abbrevs The abbreviations
|
||||
*
|
||||
* @return array A sorted array of similar namespace
|
||||
*/
|
||||
private function findAlternativeNamespace($name, $abbrevs)
|
||||
{
|
||||
return $this->findAlternatives($name, $this->getNamespaces(), $abbrevs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds alternative of $name among $collection,
|
||||
* if nothing is found in $collection, try in $abbrevs
|
||||
*
|
||||
* @param string $name The string
|
||||
* @param array|Traversable $collection The collecion
|
||||
* @param array $abbrevs The abbreviations
|
||||
* @param Closure|string|array $callback The callable to transform collection item before comparison
|
||||
*
|
||||
* @return array A sorted array of similar string
|
||||
*/
|
||||
private function findAlternatives($name, $collection, $abbrevs, $callback = null) {
|
||||
$alternatives = array();
|
||||
|
||||
foreach ($collection as $item) {
|
||||
if (null !== $callback) {
|
||||
$item = call_user_func($callback, $item);
|
||||
}
|
||||
|
||||
$lev = levenshtein($name, $item);
|
||||
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
|
||||
$alternatives[$item] = $lev;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$alternatives) {
|
||||
foreach ($abbrevs as $key => $values) {
|
||||
$lev = levenshtein($name, $key);
|
||||
if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) {
|
||||
foreach ($values as $value) {
|
||||
$alternatives[$value] = $lev;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asort($alternatives);
|
||||
|
||||
return array_keys($alternatives);
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
|
||||
$this->fail('->find() throws an \InvalidArgumentException if the abbreviation is ambiguous for a namespace');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if the abbreviation is ambiguous for a namespace');
|
||||
$this->assertEquals('Command "f" is not defined.', $e->getMessage(), '->find() throws an \InvalidArgumentException if the abbreviation is ambiguous for a namespace');
|
||||
$this->assertRegExp('/Command "f" is not defined./', $e->getMessage(), '->find() throws an \InvalidArgumentException if the abbreviation is ambiguous for a namespace');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -203,6 +203,74 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testFindAlternativeCommands()
|
||||
{
|
||||
$application = new Application();
|
||||
|
||||
$application->add(new \FooCommand());
|
||||
$application->add(new \Foo1Command());
|
||||
$application->add(new \Foo2Command());
|
||||
|
||||
try {
|
||||
$application->find($commandName = 'Unknow command');
|
||||
$this->fail('->find() throws an \InvalidArgumentException if command does not exist');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
|
||||
$this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without alternatives');
|
||||
}
|
||||
|
||||
try {
|
||||
$application->find($commandName = 'foo');
|
||||
$this->fail('->find() throws an \InvalidArgumentException if command does not exist');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
|
||||
$this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
|
||||
$this->assertRegExp('/foo:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar"');
|
||||
$this->assertRegExp('/foo1:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo1:bar"');
|
||||
$this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"');
|
||||
}
|
||||
|
||||
// Test if "foo1" command throw an "\InvalidArgumentException" and does not contain
|
||||
// "foo:bar" as alternative because "foo1" is too far from "foo:bar"
|
||||
try {
|
||||
$application->find($commandName = 'foo1');
|
||||
$this->fail('->find() throws an \InvalidArgumentException if command does not exist');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
|
||||
$this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
|
||||
$this->assertFalse(strpos($e->getMessage(), 'foo:bar'), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative');
|
||||
}
|
||||
}
|
||||
|
||||
public function testFindAlternativeNamespace()
|
||||
{
|
||||
$application = new Application();
|
||||
|
||||
$application->add(new \FooCommand());
|
||||
$application->add(new \Foo1Command());
|
||||
$application->add(new \Foo2Command());
|
||||
$application->add(new \foo3Command());
|
||||
|
||||
try {
|
||||
$application->find('Unknow-namespace:Unknow-command');
|
||||
$this->fail('->find() throws an \InvalidArgumentException if namespace does not exist');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist');
|
||||
$this->assertEquals('There are no commands defined in the "Unknow-namespace" namespace.', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, without alternatives');
|
||||
}
|
||||
|
||||
try {
|
||||
$application->find('foo2:command');
|
||||
$this->fail('->find() throws an \InvalidArgumentException if namespace does not exist');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist');
|
||||
$this->assertRegExp('/There are no commands defined in the "foo2" namespace./', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative');
|
||||
$this->assertRegExp('/foo/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo"');
|
||||
$this->assertRegExp('/foo1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo1"');
|
||||
$this->assertRegExp('/foo3/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo3"');
|
||||
}
|
||||
}
|
||||
|
||||
public function testSetCatchExceptions()
|
||||
{
|
||||
$application = new Application();
|
||||
|
Reference in New Issue
Block a user