From dd0d97e643d722c0a4d7df42116ec0076a47d6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Sat, 11 Feb 2012 02:58:39 +0100 Subject: [PATCH] [Console] Added suggest on bad command name --- src/Symfony/Component/Console/Application.php | 32 ++++++++++++++- .../Component/Console/ApplicationTest.php | 41 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 219111737e..ce6e2ce8db 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -557,7 +557,14 @@ class Application $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) { @@ -915,4 +922,27 @@ 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 + * @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); + } } diff --git a/tests/Symfony/Tests/Component/Console/ApplicationTest.php b/tests/Symfony/Tests/Component/Console/ApplicationTest.php index b24add6502..5a8186f57c 100644 --- a/tests/Symfony/Tests/Component/Console/ApplicationTest.php +++ b/tests/Symfony/Tests/Component/Console/ApplicationTest.php @@ -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,45 @@ 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 testSetCatchExceptions() { $application = new Application();