[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\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Helper\DebugFormatterHelper;
|
||||
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\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@ -223,6 +225,7 @@ class Application
|
||||
// the command name MUST be the first element of the input
|
||||
$command = $this->find($name);
|
||||
} catch (\Throwable $e) {
|
||||
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
|
||||
if (null !== $this->dispatcher) {
|
||||
$event = new ConsoleErrorEvent($input, $output, $e);
|
||||
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
|
||||
@ -237,6 +240,24 @@ class Application
|
||||
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;
|
||||
$exitCode = $this->doRunCommand($command, $input, $output);
|
||||
$this->runningCommand = null;
|
||||
@ -533,7 +554,7 @@ class Application
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
@ -554,12 +575,12 @@ class Application
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
throw new CommandNotFoundException($message, $alternatives);
|
||||
throw new NamespaceNotFoundException($message, $alternatives);
|
||||
}
|
||||
|
||||
$exact = in_array($namespace, $namespaces, true);
|
||||
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);
|
||||
|
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* added option to run suggested command if command is not found and only 1 alternative is available
|
||||
|
||||
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\CommandLoader\FactoryCommandLoader;
|
||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
@ -56,6 +57,7 @@ class ApplicationTest extends TestCase
|
||||
require_once self::$fixturesPath.'/BarBucCommand.php';
|
||||
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';
|
||||
require_once self::$fixturesPath.'/FooSubnamespaced2Command.php';
|
||||
require_once self::$fixturesPath.'/FooWithoutAliasCommand.php';
|
||||
require_once self::$fixturesPath.'/TestTiti.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";
|
||||
|
||||
if (method_exists($this, 'expectException')) {
|
||||
$this->expectException(CommandNotFoundException::class);
|
||||
$this->expectException(NamespaceNotFoundException::class);
|
||||
$this->expectExceptionMessage($expectedMsg);
|
||||
} else {
|
||||
$this->setExpectedException(CommandNotFoundException::class, $expectedMsg);
|
||||
$this->setExpectedException(NamespaceNotFoundException::class, $expectedMsg);
|
||||
}
|
||||
|
||||
$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.
|
||||
*/
|
||||
public function testFindInvalidNamespace()
|
||||
@ -457,6 +459,68 @@ class ApplicationTest extends TestCase
|
||||
$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()
|
||||
{
|
||||
return array(
|
||||
@ -574,7 +638,8 @@ class ApplicationTest extends TestCase
|
||||
$application->find('foo2:command');
|
||||
$this->fail('->find() throws a CommandNotFoundException if namespace does not exist');
|
||||
} 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->assertContains('foo', $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