From c6203bcffaa8ecd1b34d1c881dcdc62c14d7198c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Sat, 11 Feb 2012 20:38:08 +0100 Subject: [PATCH] [Console] Added namespace suggest on bad namespace name --- src/Symfony/Component/Console/Application.php | 54 ++++++++++++++++--- .../Component/Console/ApplicationTest.php | 29 ++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 9f21b934dc..03c7d18a6c 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -489,7 +489,14 @@ 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 ($alternatives = $this->findAlternativeNamespace($namespace)) { + $message .= "\n\nDid you mean one of these?\n "; + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); } if (count($abbrevs[$part]) > 1) { @@ -560,7 +567,7 @@ class Application $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternativeCommands($searchName)) { - $message .= "\nDid you mean one of these?\n "; + $message .= "\n\nDid you mean one of these?\n "; $message .= implode("\n ", $alternatives); } @@ -927,17 +934,50 @@ class Application * 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) { + $getNameCallback = function($command) { + return $command->getName(); + }; + + return $this->findAlternatives($name, $this->commands, $getNameCallback); + } + + /** + * Finds alternative namespace of $name + * + * @param string $name The full name of the namespace + * + * @return array A sorted array of similar namespace + */ + private function findAlternativeNamespace($name) + { + return $this->findAlternatives($name, $this->getNamespaces()); + } + + /** + * Finds alternative of $name among $collection + * + * @param string $name The string + * @param array|Traversable $collection The collection + * @param Closure|string|array $callback The callable to transform item before comparison + * + * @return array A sorted array of similar string + */ + private function findAlternatives($name, $collection, $callback = null) { $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; + 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; } } diff --git a/tests/Symfony/Tests/Component/Console/ApplicationTest.php b/tests/Symfony/Tests/Component/Console/ApplicationTest.php index 5a8186f57c..4e4e2fa59f 100644 --- a/tests/Symfony/Tests/Component/Console/ApplicationTest.php +++ b/tests/Symfony/Tests/Component/Console/ApplicationTest.php @@ -242,6 +242,35 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase } } + 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();