[Console] Add option to automatically run suggested command if there is only 1 alternative
This commit is contained in:
parent
8cf8d1619f
commit
83d52f02f9
@ -13,6 +13,7 @@ namespace Symfony\Component\Console;
|
|||||||
|
|
||||||
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
|
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
|
||||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Symfony\Component\Console\Helper\DebugFormatterHelper;
|
use Symfony\Component\Console\Helper\DebugFormatterHelper;
|
||||||
use Symfony\Component\Console\Helper\Helper;
|
use Symfony\Component\Console\Helper\Helper;
|
||||||
@ -39,6 +40,7 @@ use Symfony\Component\Console\Event\ConsoleErrorEvent;
|
|||||||
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\Debug\ErrorHandler;
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
@ -223,6 +225,7 @@ class Application
|
|||||||
// the command name MUST be the first element of the input
|
// the command name MUST be the first element of the input
|
||||||
$command = $this->find($name);
|
$command = $this->find($name);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
|
||||||
if (null !== $this->dispatcher) {
|
if (null !== $this->dispatcher) {
|
||||||
$event = new ConsoleErrorEvent($input, $output, $e);
|
$event = new ConsoleErrorEvent($input, $output, $e);
|
||||||
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
|
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
|
||||||
@ -237,6 +240,24 @@ class Application
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$alternative = $alternatives[0];
|
||||||
|
|
||||||
|
$style = new SymfonyStyle($input, $output);
|
||||||
|
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
|
||||||
|
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
|
||||||
|
if (null !== $this->dispatcher) {
|
||||||
|
$event = new ConsoleErrorEvent($input, $output, $e);
|
||||||
|
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
|
||||||
|
|
||||||
|
return $event->getExitCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $this->find($alternative);
|
||||||
|
}
|
||||||
|
|
||||||
$this->runningCommand = $command;
|
$this->runningCommand = $command;
|
||||||
$exitCode = $this->doRunCommand($command, $input, $output);
|
$exitCode = $this->doRunCommand($command, $input, $output);
|
||||||
$this->runningCommand = null;
|
$this->runningCommand = null;
|
||||||
@ -533,7 +554,7 @@ class Application
|
|||||||
*
|
*
|
||||||
* @return string A registered namespace
|
* @return string A registered namespace
|
||||||
*
|
*
|
||||||
* @throws CommandNotFoundException When namespace is incorrect or ambiguous
|
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
|
||||||
*/
|
*/
|
||||||
public function findNamespace($namespace)
|
public function findNamespace($namespace)
|
||||||
{
|
{
|
||||||
@ -554,12 +575,12 @@ class Application
|
|||||||
$message .= implode("\n ", $alternatives);
|
$message .= implode("\n ", $alternatives);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CommandNotFoundException($message, $alternatives);
|
throw new NamespaceNotFoundException($message, $alternatives);
|
||||||
}
|
}
|
||||||
|
|
||||||
$exact = in_array($namespace, $namespaces, true);
|
$exact = in_array($namespace, $namespaces, true);
|
||||||
if (count($namespaces) > 1 && !$exact) {
|
if (count($namespaces) > 1 && !$exact) {
|
||||||
throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
|
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $exact ? $namespace : reset($namespaces);
|
return $exact ? $namespace : reset($namespaces);
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
4.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added option to run suggested command if command is not found and only 1 alternative is available
|
||||||
|
|
||||||
4.0.0
|
4.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Console\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an incorrect namespace typed in the console.
|
||||||
|
*
|
||||||
|
* @author Pierre du Plessis <pdples@gmail.com>
|
||||||
|
*/
|
||||||
|
class NamespaceNotFoundException extends CommandNotFoundException
|
||||||
|
{
|
||||||
|
}
|
@ -16,6 +16,7 @@ use Symfony\Component\Console\Application;
|
|||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
|
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
|
||||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||||
|
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
|
||||||
use Symfony\Component\Console\Helper\HelperSet;
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
@ -56,6 +57,7 @@ class ApplicationTest extends TestCase
|
|||||||
require_once self::$fixturesPath.'/BarBucCommand.php';
|
require_once self::$fixturesPath.'/BarBucCommand.php';
|
||||||
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';
|
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';
|
||||||
require_once self::$fixturesPath.'/FooSubnamespaced2Command.php';
|
require_once self::$fixturesPath.'/FooSubnamespaced2Command.php';
|
||||||
|
require_once self::$fixturesPath.'/FooWithoutAliasCommand.php';
|
||||||
require_once self::$fixturesPath.'/TestTiti.php';
|
require_once self::$fixturesPath.'/TestTiti.php';
|
||||||
require_once self::$fixturesPath.'/TestToto.php';
|
require_once self::$fixturesPath.'/TestToto.php';
|
||||||
}
|
}
|
||||||
@ -275,10 +277,10 @@ class ApplicationTest extends TestCase
|
|||||||
$expectedMsg = "The namespace \"f\" is ambiguous.\nDid you mean one of these?\n foo\n foo1";
|
$expectedMsg = "The namespace \"f\" is ambiguous.\nDid you mean one of these?\n foo\n foo1";
|
||||||
|
|
||||||
if (method_exists($this, 'expectException')) {
|
if (method_exists($this, 'expectException')) {
|
||||||
$this->expectException(CommandNotFoundException::class);
|
$this->expectException(NamespaceNotFoundException::class);
|
||||||
$this->expectExceptionMessage($expectedMsg);
|
$this->expectExceptionMessage($expectedMsg);
|
||||||
} else {
|
} else {
|
||||||
$this->setExpectedException(CommandNotFoundException::class, $expectedMsg);
|
$this->setExpectedException(NamespaceNotFoundException::class, $expectedMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
$application->findNamespace('f');
|
$application->findNamespace('f');
|
||||||
@ -293,7 +295,7 @@ class ApplicationTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
|
* @expectedException \Symfony\Component\Console\Exception\NamespaceNotFoundException
|
||||||
* @expectedExceptionMessage There are no commands defined in the "bar" namespace.
|
* @expectedExceptionMessage There are no commands defined in the "bar" namespace.
|
||||||
*/
|
*/
|
||||||
public function testFindInvalidNamespace()
|
public function testFindInvalidNamespace()
|
||||||
@ -457,6 +459,68 @@ class ApplicationTest extends TestCase
|
|||||||
$application->find($name);
|
$application->find($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDontRunAlternativeNamespaceName()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
$application->add(new \Foo1Command());
|
||||||
|
$application->setAutoExit(false);
|
||||||
|
$tester = new ApplicationTester($application);
|
||||||
|
$tester->run(array('command' => 'foos:bar1'), array('decorated' => false));
|
||||||
|
$this->assertSame('
|
||||||
|
|
||||||
|
There are no commands defined in the "foos" namespace.
|
||||||
|
|
||||||
|
Did you mean this?
|
||||||
|
foo
|
||||||
|
|
||||||
|
|
||||||
|
', $tester->getDisplay(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanRunAlternativeCommandName()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
$application->add(new \FooWithoutAliasCommand());
|
||||||
|
$application->setAutoExit(false);
|
||||||
|
$tester = new ApplicationTester($application);
|
||||||
|
$tester->setInputs(array('y'));
|
||||||
|
$tester->run(array('command' => 'foos'), array('decorated' => false));
|
||||||
|
$this->assertSame(<<<OUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
Command "foos" is not defined.
|
||||||
|
|
||||||
|
|
||||||
|
Do you want to run "foo" instead? (yes/no) [no]:
|
||||||
|
>
|
||||||
|
called
|
||||||
|
|
||||||
|
OUTPUT
|
||||||
|
, $tester->getDisplay(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDontRunAlternativeCommandName()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
$application->add(new \FooWithoutAliasCommand());
|
||||||
|
$application->setAutoExit(false);
|
||||||
|
$tester = new ApplicationTester($application);
|
||||||
|
$tester->setInputs(array('n'));
|
||||||
|
$exitCode = $tester->run(array('command' => 'foos'), array('decorated' => false));
|
||||||
|
$this->assertSame(1, $exitCode);
|
||||||
|
$this->assertSame(<<<OUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
Command "foos" is not defined.
|
||||||
|
|
||||||
|
|
||||||
|
Do you want to run "foo" instead? (yes/no) [no]:
|
||||||
|
>
|
||||||
|
|
||||||
|
OUTPUT
|
||||||
|
, $tester->getDisplay(true));
|
||||||
|
}
|
||||||
|
|
||||||
public function provideInvalidCommandNamesSingle()
|
public function provideInvalidCommandNamesSingle()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
@ -574,7 +638,8 @@ class ApplicationTest extends TestCase
|
|||||||
$application->find('foo2:command');
|
$application->find('foo2:command');
|
||||||
$this->fail('->find() throws a CommandNotFoundException if namespace does not exist');
|
$this->fail('->find() throws a CommandNotFoundException if namespace does not exist');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if namespace does not exist');
|
$this->assertInstanceOf('Symfony\Component\Console\Exception\NamespaceNotFoundException', $e, '->find() throws a NamespaceNotFoundException if namespace does not exist');
|
||||||
|
$this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, 'NamespaceNotFoundException extends from CommandNotFoundException');
|
||||||
$this->assertCount(3, $e->getAlternatives());
|
$this->assertCount(3, $e->getAlternatives());
|
||||||
$this->assertContains('foo', $e->getAlternatives());
|
$this->assertContains('foo', $e->getAlternatives());
|
||||||
$this->assertContains('foo1', $e->getAlternatives());
|
$this->assertContains('foo1', $e->getAlternatives());
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class FooWithoutAliasCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('foo')
|
||||||
|
->setDescription('The foo command')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$output->writeln('called');
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user