From facff730497c122a50539092abd4581fda8a618e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 7 Jun 2011 17:51:43 +0200 Subject: [PATCH] made the console tool more powerful * The command names have now full support for nested namespaces. It means that abbreviations work for each sub-namespace: ./app/console doctrine:mapping:info # worked before ./app/console doctrine:map:in # works now ./app/console doc:map:in * Aliases are now first class citizen. They can have their own namespace, like the main name. So, now, there is no difference between an alias and a name. * As names and aliases can be namespaced, the Command::getFullName() and Command::getNamespace() method have been removed. --- UPDATE.md | 4 + src/Symfony/Component/Console/Application.php | 83 ++++++++++++------- .../Component/Console/Command/Command.php | 57 ++++--------- .../Component/Console/Command/CommandTest.php | 23 ++--- .../Console/Command/ListCommandTest.php | 6 +- .../Console/Fixtures/application_astext1.txt | 6 +- .../Console/Fixtures/application_astext2.txt | 2 +- .../Console/Fixtures/application_asxml1.txt | 8 +- .../Console/Fixtures/application_asxml2.txt | 2 +- .../Console/Fixtures/command_asxml.txt | 2 +- 10 files changed, 96 insertions(+), 97 deletions(-) diff --git a/UPDATE.md b/UPDATE.md index 5597ec9c80..0609708f6c 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -9,6 +9,10 @@ timeline closely anyway. beta4 to beta5 -------------- +* In the Console component: `Command::getFullname()` and + `Command::getNamespace()` have been removed (`Command::getName()` behavior + is now the same as the old `Command::getFullname()`). + * Default Twig form templates have been moved to the Twig bridge. Here is how you can reference them now from a template or in a configuration setting: diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index d3504cea0b..f9fd8b237c 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -387,7 +387,7 @@ class Application { $command->setApplication($this); - $this->commands[$command->getFullName()] = $command; + $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->aliases[$alias] = $command; @@ -452,12 +452,18 @@ class Application { $namespaces = array(); foreach ($this->commands as $command) { - if ($command->getNamespace()) { - $namespaces[$command->getNamespace()] = true; + if ($namespace = $this->extractNamespace($command->getName())) { + $namespaces[] = $namespace; + } + + foreach ($command->getAliases() as $alias) { + if ($namespace = $this->extractNamespace($alias)) { + $namespaces[] = $namespace; + } } } - return array_keys($namespaces); + return array_unique($namespaces); } /** @@ -471,17 +477,27 @@ class Application */ public function findNamespace($namespace) { - $abbrevs = static::getAbbreviations($this->getNamespaces()); - - if (!isset($abbrevs[$namespace])) { - throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace)); + $allNamespaces = array(); + foreach ($this->getNamespaces() as $n) { + $allNamespaces[$n] = explode(':', $n); } - if (count($abbrevs[$namespace]) > 1) { - throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace]))); + $found = array(); + foreach (explode(':', $namespace) as $i => $part) { + $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)); + } + + if (count($abbrevs[$part]) > 1) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace]))); + } + + $found[] = $abbrevs[$part][0]; } - return $abbrevs[$namespace][0]; + return implode(':', $found); } /** @@ -502,43 +518,42 @@ class Application { // namespace $namespace = ''; + $searchName = $name; if (false !== $pos = strrpos($name, ':')) { $namespace = $this->findNamespace(substr($name, 0, $pos)); - $name = substr($name, $pos + 1); + $searchName = $namespace.substr($name, $pos); } - $fullName = $namespace ? $namespace.':'.$name : $name; - // name $commands = array(); foreach ($this->commands as $command) { - if ($command->getNamespace() == $namespace) { + if ($this->extractNamespace($command->getName()) == $namespace) { $commands[] = $command->getName(); } } $abbrevs = static::getAbbreviations($commands); - if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) { - return $this->get($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]); + if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) { + return $this->get($abbrevs[$searchName][0]); } - if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) { - $suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name])); + if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) { + $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]); - throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions)); + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); } // aliases $abbrevs = static::getAbbreviations(array_keys($this->aliases)); - if (!isset($abbrevs[$fullName])) { - throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName)); + if (!isset($abbrevs[$searchName])) { + throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $name)); } - if (count($abbrevs[$fullName]) > 1) { - throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName]))); + if (count($abbrevs[$searchName]) > 1) { + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($abbrevs[$searchName]))); } - return $this->get($abbrevs[$fullName][0]); + return $this->get($abbrevs[$searchName][0]); } /** @@ -560,7 +575,7 @@ class Application $commands = array(); foreach ($this->commands as $name => $command) { - if ($namespace === $command->getNamespace()) { + if ($namespace === $this->extractNamespace($command->getName())) { $commands[$name] = $command; } } @@ -630,7 +645,7 @@ class Application foreach ($commands as $command) { $aliases = $command->getAliases() ? ' ('.implode(', ', $command->getAliases()).')' : ''; - $messages[] = sprintf(" %-${width}s %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases); + $messages[] = sprintf(" %-${width}s %s%s", $command->getName(), $command->getDescription(), $aliases); } } @@ -778,7 +793,10 @@ class Application { $namespacedCommands = array(); foreach ($commands as $name => $command) { - $key = $command->getNamespace() ? $command->getNamespace() : '_global'; + $key = $this->extractNamespace($command->getName()); + if (!$key) { + $key = '_global'; + } if (!isset($namespacedCommands[$key])) { $namespacedCommands[$key] = array(); @@ -806,4 +824,13 @@ class Application { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } + + private function extractNamespace($name) + { + if (false !== $pos = strrpos($name, ':')) { + return substr($name, 0, $pos); + } + + return ''; + } } diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index cdfe8c5de4..6c4487f325 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -29,7 +29,6 @@ class Command { private $application; private $name; - private $namespace; private $aliases; private $definition; private $help; @@ -319,35 +318,13 @@ class Command */ public function setName($name) { - if (false !== $pos = strrpos($name, ':')) { - $namespace = substr($name, 0, $pos); - $name = substr($name, $pos + 1); - } else { - $namespace = $this->namespace; - } + $this->validateName($name); - if (!$name) { - throw new \InvalidArgumentException('A command name cannot be empty.'); - } - - $this->namespace = $namespace; $this->name = $name; return $this; } - /** - * Returns the command namespace. - * - * @return string The command namespace - * - * @api - */ - public function getNamespace() - { - return $this->namespace; - } - /** * Returns the command name. * @@ -360,18 +337,6 @@ class Command return $this->name; } - /** - * Returns the fully qualified command name. - * - * @return string The fully qualified command name - * - * @api - */ - public function getFullName() - { - return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName(); - } - /** * Sets the description for the command. * @@ -436,7 +401,7 @@ class Command */ public function getProcessedHelp() { - $name = $this->namespace.':'.$this->name; + $name = $this->name; $placeholders = array( '%command.name%', @@ -461,6 +426,10 @@ class Command */ public function setAliases($aliases) { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + $this->aliases = $aliases; return $this; @@ -486,7 +455,7 @@ class Command public function getSynopsis() { if (null === $this->synopsis) { - $this->synopsis = trim(sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis())); + $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis())); } return $this->synopsis; @@ -547,9 +516,8 @@ class Command $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->appendChild($commandXML = $dom->createElement('command')); - $commandXML->setAttribute('id', $this->getFullName()); - $commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global'); - $commandXML->setAttribute('name', $this->getName()); + $commandXML->setAttribute('id', $this->name); + $commandXML->setAttribute('name', $this->name); $commandXML->appendChild($usageXML = $dom->createElement('usage')); $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), ''))); @@ -573,4 +541,11 @@ class Command return $asDom ? $dom : $dom->saveXml(); } + + private function validateName($name) + { + if (!preg_match('/^[^\:]+(\:[^\:]+)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } } diff --git a/tests/Symfony/Tests/Component/Console/Command/CommandTest.php b/tests/Symfony/Tests/Component/Console/Command/CommandTest.php index 8b3fae544e..590892ea96 100644 --- a/tests/Symfony/Tests/Component/Console/Command/CommandTest.php +++ b/tests/Symfony/Tests/Component/Console/Command/CommandTest.php @@ -44,7 +44,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase $this->assertEquals('The command name cannot be empty.', $e->getMessage(), '__construct() throws a \LogicException if the name is null'); } $command = new Command('foo:bar'); - $this->assertEquals('foo:bar', $command->getFullName(), '__construct() takes the command name as its first argument'); + $this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument'); } public function testSetApplication() @@ -83,30 +83,23 @@ class CommandTest extends \PHPUnit_Framework_TestCase $this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command'); } - public function testGetNamespaceGetNameGetFullNameSetName() + public function testGetNamespaceGetNameSetName() { $command = new \TestCommand(); - $this->assertEquals('namespace', $command->getNamespace(), '->getNamespace() returns the command namespace'); - $this->assertEquals('name', $command->getName(), '->getName() returns the command name'); - $this->assertEquals('namespace:name', $command->getFullName(), '->getNamespace() returns the full command name'); + $this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name'); $command->setName('foo'); $this->assertEquals('foo', $command->getName(), '->setName() sets the command name'); - $command->setName(':bar'); - $this->assertEquals('bar', $command->getName(), '->setName() sets the command name'); - $this->assertEquals('', $command->getNamespace(), '->setName() can set the command namespace'); - $ret = $command->setName('foobar:bar'); $this->assertEquals($command, $ret, '->setName() implements a fluent interface'); - $this->assertEquals('bar', $command->getName(), '->setName() sets the command name'); - $this->assertEquals('foobar', $command->getNamespace(), '->setName() can set the command namespace'); + $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name'); try { $command->setName(''); $this->fail('->setName() throws an \InvalidArgumentException if the name is empty'); } catch (\Exception $e) { $this->assertInstanceOf('\InvalidArgumentException', $e, '->setName() throws an \InvalidArgumentException if the name is empty'); - $this->assertEquals('A command name cannot be empty.', $e->getMessage(), '->setName() throws an \InvalidArgumentException if the name is empty'); + $this->assertEquals('Command name "" is invalid.', $e->getMessage(), '->setName() throws an \InvalidArgumentException if the name is empty'); } try { @@ -114,7 +107,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase $this->fail('->setName() throws an \InvalidArgumentException if the name is empty'); } catch (\Exception $e) { $this->assertInstanceOf('\InvalidArgumentException', $e, '->setName() throws an \InvalidArgumentException if the name is empty'); - $this->assertEquals('A command name cannot be empty.', $e->getMessage(), '->setName() throws an \InvalidArgumentException if the name is empty'); + $this->assertEquals('Command name "foo:" is invalid.', $e->getMessage(), '->setName() throws an \InvalidArgumentException if the name is empty'); } } @@ -239,7 +232,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase $command = new \TestCommand(); $command->setApplication(new Application()); $tester = new CommandTester($command); - $tester->execute(array('command' => $command->getFullName())); + $tester->execute(array('command' => $command->getName())); $this->assertStringEqualsFile(self::$fixturesPath.'/command_astext.txt', $command->asText(), '->asText() returns a text representation of the command'); } @@ -248,7 +241,7 @@ class CommandTest extends \PHPUnit_Framework_TestCase $command = new \TestCommand(); $command->setApplication(new Application()); $tester = new CommandTester($command); - $tester->execute(array('command' => $command->getFullName())); + $tester->execute(array('command' => $command->getName())); $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/command_asxml.txt', $command->asXml(), '->asXml() returns an XML representation of the command'); } } diff --git a/tests/Symfony/Tests/Component/Console/Command/ListCommandTest.php b/tests/Symfony/Tests/Component/Console/Command/ListCommandTest.php index 42946dba2b..16fb32e8a0 100644 --- a/tests/Symfony/Tests/Component/Console/Command/ListCommandTest.php +++ b/tests/Symfony/Tests/Component/Console/Command/ListCommandTest.php @@ -21,10 +21,10 @@ class ListCommandTest extends \PHPUnit_Framework_TestCase $application = new Application(); $commandTester = new CommandTester($command = $application->get('list')); - $commandTester->execute(array('command' => $command->getFullName())); + $commandTester->execute(array('command' => $command->getName())); $this->assertRegExp('/help Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands'); - $commandTester->execute(array('command' => $command->getFullName(), '--xml' => true)); - $this->assertRegExp('//', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed'); + $commandTester->execute(array('command' => $command->getName(), '--xml' => true)); + $this->assertRegExp('//', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed'); } } diff --git a/tests/Symfony/Tests/Component/Console/Fixtures/application_astext1.txt b/tests/Symfony/Tests/Component/Console/Fixtures/application_astext1.txt index 6171578cc2..309be271da 100644 --- a/tests/Symfony/Tests/Component/Console/Fixtures/application_astext1.txt +++ b/tests/Symfony/Tests/Component/Console/Fixtures/application_astext1.txt @@ -12,7 +12,7 @@ --no-interaction -n Do not ask any interactive question. Available commands: - help Displays help for a command (?) - list Lists commands + help Displays help for a command (?) + list Lists commands foo - :bar The foo:bar command (afoobar) \ No newline at end of file + foo:bar The foo:bar command (afoobar) \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Console/Fixtures/application_astext2.txt b/tests/Symfony/Tests/Component/Console/Fixtures/application_astext2.txt index 2d68fa642e..01fe6b61cd 100644 --- a/tests/Symfony/Tests/Component/Console/Fixtures/application_astext2.txt +++ b/tests/Symfony/Tests/Component/Console/Fixtures/application_astext2.txt @@ -12,4 +12,4 @@ --no-interaction -n Do not ask any interactive question. Available commands for the "foo" namespace: - :bar The foo:bar command (afoobar) \ No newline at end of file + foo:bar The foo:bar command (afoobar) \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml1.txt b/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml1.txt index e16ebd4ffb..bc022b8b14 100644 --- a/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml1.txt +++ b/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml1.txt @@ -1,7 +1,7 @@ - + help [--xml] [command_name] Displays help for a command The <info>help</info> command displays help for a given command: @@ -28,7 +28,7 @@ - + list [--xml] [namespace] Lists commands The <info>list</info> command lists all commands: @@ -55,7 +55,7 @@ - + foo:bar The foo:bar command @@ -72,7 +72,7 @@ list - bar + foo:bar diff --git a/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml2.txt b/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml2.txt index 0ff8ddf652..9e1f4a1db9 100644 --- a/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml2.txt +++ b/tests/Symfony/Tests/Component/Console/Fixtures/application_asxml2.txt @@ -1,7 +1,7 @@ - + foo:bar The foo:bar command diff --git a/tests/Symfony/Tests/Component/Console/Fixtures/command_asxml.txt b/tests/Symfony/Tests/Component/Console/Fixtures/command_asxml.txt index e54b311252..6e1c6024ca 100644 --- a/tests/Symfony/Tests/Component/Console/Fixtures/command_asxml.txt +++ b/tests/Symfony/Tests/Component/Console/Fixtures/command_asxml.txt @@ -1,5 +1,5 @@ - + namespace:name description help