Skip to content

Commit

Permalink
[Console] Added suggest on bad command name
Browse files Browse the repository at this point in the history
  • Loading branch information
lyrixx committed Feb 11, 2012
1 parent 2ae1f90 commit dd0d97e
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 2 deletions.
32 changes: 31 additions & 1 deletion src/Symfony/Component/Console/Application.php
Expand Up @@ -557,7 +557,14 @@ public function find($name)

$abbrevs = static::getAbbreviations(array_unique($aliases));
if (!isset($abbrevs[$searchName])) {
throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $name));
$message = sprintf('Command "%s" is not defined.', $name);

if ($alternatives = $this->findAlternativeCommands($searchName)) {
$message .= PHP_EOL.'Did you mean one of these?'.PHP_EOL.' ';
$message .= implode(PHP_EOL.' ', $alternatives);
}

throw new \InvalidArgumentException($message);
}

if (count($abbrevs[$searchName]) > 1) {
Expand Down Expand Up @@ -915,4 +922,27 @@ private function extractNamespace($name, $limit = null)

return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
}

/**
* Finds alternative commands of $name
*
* @param string $name The full name of the command
* @return array A sorted array of similar commands
*/
private function findAlternativeCommands($name)
{
$alternatives = array();

foreach ($this->commands as $command) {
$commandName = $command->getName();
$lev = levenshtein($name, $commandName);
if ($lev <= strlen($name) / 3 || false !== strpos($commandName, $name)) {
$alternatives[$commandName] = $lev;
}
}

asort($alternatives);

return array_keys($alternatives);
}
}
41 changes: 40 additions & 1 deletion tests/Symfony/Tests/Component/Console/ApplicationTest.php
Expand Up @@ -183,7 +183,7 @@ public function testFind()
$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 {
Expand All @@ -203,6 +203,45 @@ public function testFind()
}
}

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 testSetCatchExceptions()
{
$application = new Application();
Expand Down

0 comments on commit dd0d97e

Please sign in to comment.