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.
This commit is contained in:
Fabien Potencier 2011-06-07 17:51:43 +02:00
parent 89f544afb6
commit facff73049
10 changed files with 96 additions and 97 deletions

View File

@ -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:

View File

@ -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() ? '<comment> ('.implode(', ', $command->getAliases()).')</comment>' : '';
$messages[] = sprintf(" <info>%-${width}s</info> %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases);
$messages[] = sprintf(" <info>%-${width}s</info> %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 '';
}
}

View File

@ -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));
}
}
}

View File

@ -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');
}
}

View File

@ -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('/<command id="list" namespace="_global" name="list">/', $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('/<command id="list" name="list">/', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed');
}
}

View File

@ -12,7 +12,7 @@
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<comment>Available commands:</comment>
<info>help </info> Displays help for a command<comment> (?)</comment>
<info>list </info> Lists commands
<info>help </info> Displays help for a command<comment> (?)</comment>
<info>list </info> Lists commands
<comment>foo</comment>
<info>:bar </info> The foo:bar command<comment> (afoobar)</comment>
<info>foo:bar </info> The foo:bar command<comment> (afoobar)</comment>

View File

@ -12,4 +12,4 @@
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<comment>Available commands for the "foo" namespace:</comment>
<info>:bar </info> The foo:bar command<comment> (afoobar)</comment>
<info>foo:bar </info> The foo:bar command<comment> (afoobar)</comment>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
<commands>
<command id="help" namespace="_global" name="help">
<command id="help" name="help">
<usage>help [--xml] [command_name]</usage>
<description>Displays help for a command</description>
<help>The &lt;info&gt;help&lt;/info&gt; command displays help for a given command:
@ -28,7 +28,7 @@
</option>
</options>
</command>
<command id="list" namespace="_global" name="list">
<command id="list" name="list">
<usage>list [--xml] [namespace]</usage>
<description>Lists commands</description>
<help>The &lt;info&gt;list&lt;/info&gt; command lists all commands:
@ -55,7 +55,7 @@
</option>
</options>
</command>
<command id="foo:bar" namespace="foo" name="bar">
<command id="foo:bar" name="foo:bar">
<usage>foo:bar</usage>
<description>The foo:bar command</description>
<help/>
@ -72,7 +72,7 @@
<command>list</command>
</namespace>
<namespace id="foo">
<command>bar</command>
<command>foo:bar</command>
</namespace>
</namespaces>
</symfony>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
<commands namespace="foo">
<command id="foo:bar" namespace="foo" name="bar">
<command id="foo:bar" name="foo:bar">
<usage>foo:bar</usage>
<description>The foo:bar command</description>
<help/>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<command id="namespace:name" namespace="namespace" name="name">
<command id="namespace:name" name="namespace:name">
<usage>namespace:name</usage>
<description>description</description>
<help>help</help>