Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 83d52f0

Browse filesBrowse files
pierredupfabpot
authored andcommitted
[Console] Add option to automatically run suggested command if there is only 1 alternative
1 parent 8cf8d16 commit 83d52f0
Copy full SHA for 83d52f0

File tree

5 files changed

+147
-14
lines changed
Filter options

5 files changed

+147
-14
lines changed

‎src/Symfony/Component/Console/Application.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+31-10Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
1515
use Symfony\Component\Console\Exception\ExceptionInterface;
16+
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
1617
use Symfony\Component\Console\Formatter\OutputFormatter;
1718
use Symfony\Component\Console\Helper\DebugFormatterHelper;
1819
use Symfony\Component\Console\Helper\Helper;
@@ -39,6 +40,7 @@
3940
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
4041
use Symfony\Component\Console\Exception\CommandNotFoundException;
4142
use Symfony\Component\Console\Exception\LogicException;
43+
use Symfony\Component\Console\Style\SymfonyStyle;
4244
use Symfony\Component\Debug\ErrorHandler;
4345
use Symfony\Component\Debug\Exception\FatalThrowableError;
4446
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -223,18 +225,37 @@ public function doRun(InputInterface $input, OutputInterface $output)
223225
// the command name MUST be the first element of the input
224226
$command = $this->find($name);
225227
} catch (\Throwable $e) {
226-
if (null !== $this->dispatcher) {
227-
$event = new ConsoleErrorEvent($input, $output, $e);
228-
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
228+
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
229+
if (null !== $this->dispatcher) {
230+
$event = new ConsoleErrorEvent($input, $output, $e);
231+
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
229232

230-
if (0 === $event->getExitCode()) {
231-
return 0;
233+
if (0 === $event->getExitCode()) {
234+
return 0;
235+
}
236+
237+
$e = $event->getError();
232238
}
233239

234-
$e = $event->getError();
240+
throw $e;
235241
}
236242

237-
throw $e;
243+
$alternative = $alternatives[0];
244+
245+
$style = new SymfonyStyle($input, $output);
246+
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
247+
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
248+
if (null !== $this->dispatcher) {
249+
$event = new ConsoleErrorEvent($input, $output, $e);
250+
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
251+
252+
return $event->getExitCode();
253+
}
254+
255+
return 1;
256+
}
257+
258+
$command = $this->find($alternative);
238259
}
239260

240261
$this->runningCommand = $command;
@@ -533,7 +554,7 @@ public function getNamespaces()
533554
*
534555
* @return string A registered namespace
535556
*
536-
* @throws CommandNotFoundException When namespace is incorrect or ambiguous
557+
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
537558
*/
538559
public function findNamespace($namespace)
539560
{
@@ -554,12 +575,12 @@ public function findNamespace($namespace)
554575
$message .= implode("\n ", $alternatives);
555576
}
556577

557-
throw new CommandNotFoundException($message, $alternatives);
578+
throw new NamespaceNotFoundException($message, $alternatives);
558579
}
559580

560581
$exact = in_array($namespace, $namespaces, true);
561582
if (count($namespaces) > 1 && !$exact) {
562-
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));
583+
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));
563584
}
564585

565586
return $exact ? $namespace : reset($namespaces);

‎src/Symfony/Component/Console/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.1.0
5+
-----
6+
7+
* added option to run suggested command if command is not found and only 1 alternative is available
8+
49
4.0.0
510
-----
611

+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Exception;
13+
14+
/**
15+
* Represents an incorrect namespace typed in the console.
16+
*
17+
* @author Pierre du Plessis <pdples@gmail.com>
18+
*/
19+
class NamespaceNotFoundException extends CommandNotFoundException
20+
{
21+
}

‎src/Symfony/Component/Console/Tests/ApplicationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+69-4Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Console\Command\Command;
1717
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
1818
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
19+
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
1920
use Symfony\Component\Console\Helper\HelperSet;
2021
use Symfony\Component\Console\Helper\FormatterHelper;
2122
use Symfony\Component\Console\Input\ArgvInput;
@@ -56,6 +57,7 @@ public static function setUpBeforeClass()
5657
require_once self::$fixturesPath.'/BarBucCommand.php';
5758
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';
5859
require_once self::$fixturesPath.'/FooSubnamespaced2Command.php';
60+
require_once self::$fixturesPath.'/FooWithoutAliasCommand.php';
5961
require_once self::$fixturesPath.'/TestTiti.php';
6062
require_once self::$fixturesPath.'/TestToto.php';
6163
}
@@ -275,10 +277,10 @@ public function testFindAmbiguousNamespace()
275277
$expectedMsg = "The namespace \"f\" is ambiguous.\nDid you mean one of these?\n foo\n foo1";
276278

277279
if (method_exists($this, 'expectException')) {
278-
$this->expectException(CommandNotFoundException::class);
280+
$this->expectException(NamespaceNotFoundException::class);
279281
$this->expectExceptionMessage($expectedMsg);
280282
} else {
281-
$this->setExpectedException(CommandNotFoundException::class, $expectedMsg);
283+
$this->setExpectedException(NamespaceNotFoundException::class, $expectedMsg);
282284
}
283285

284286
$application->findNamespace('f');
@@ -293,7 +295,7 @@ public function testFindNonAmbiguous()
293295
}
294296

295297
/**
296-
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
298+
* @expectedException \Symfony\Component\Console\Exception\NamespaceNotFoundException
297299
* @expectedExceptionMessage There are no commands defined in the "bar" namespace.
298300
*/
299301
public function testFindInvalidNamespace()
@@ -457,6 +459,68 @@ public function testFindAlternativeExceptionMessageSingle($name)
457459
$application->find($name);
458460
}
459461

462+
public function testDontRunAlternativeNamespaceName()
463+
{
464+
$application = new Application();
465+
$application->add(new \Foo1Command());
466+
$application->setAutoExit(false);
467+
$tester = new ApplicationTester($application);
468+
$tester->run(array('command' => 'foos:bar1'), array('decorated' => false));
469+
$this->assertSame('
470+
471+
There are no commands defined in the "foos" namespace.
472+
473+
Did you mean this?
474+
foo
475+
476+
477+
', $tester->getDisplay(true));
478+
}
479+
480+
public function testCanRunAlternativeCommandName()
481+
{
482+
$application = new Application();
483+
$application->add(new \FooWithoutAliasCommand());
484+
$application->setAutoExit(false);
485+
$tester = new ApplicationTester($application);
486+
$tester->setInputs(array('y'));
487+
$tester->run(array('command' => 'foos'), array('decorated' => false));
488+
$this->assertSame(<<<OUTPUT
489+
490+
491+
Command "foos" is not defined.
492+
493+
494+
Do you want to run "foo" instead? (yes/no) [no]:
495+
>
496+
called
497+
498+
OUTPUT
499+
, $tester->getDisplay(true));
500+
}
501+
502+
public function testDontRunAlternativeCommandName()
503+
{
504+
$application = new Application();
505+
$application->add(new \FooWithoutAliasCommand());
506+
$application->setAutoExit(false);
507+
$tester = new ApplicationTester($application);
508+
$tester->setInputs(array('n'));
509+
$exitCode = $tester->run(array('command' => 'foos'), array('decorated' => false));
510+
$this->assertSame(1, $exitCode);
511+
$this->assertSame(<<<OUTPUT
512+
513+
514+
Command "foos" is not defined.
515+
516+
517+
Do you want to run "foo" instead? (yes/no) [no]:
518+
>
519+
520+
OUTPUT
521+
, $tester->getDisplay(true));
522+
}
523+
460524
public function provideInvalidCommandNamesSingle()
461525
{
462526
return array(
@@ -574,7 +638,8 @@ public function testFindAlternativeNamespace()
574638
$application->find('foo2:command');
575639
$this->fail('->find() throws a CommandNotFoundException if namespace does not exist');
576640
} catch (\Exception $e) {
577-
$this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if namespace does not exist');
641+
$this->assertInstanceOf('Symfony\Component\Console\Exception\NamespaceNotFoundException', $e, '->find() throws a NamespaceNotFoundException if namespace does not exist');
642+
$this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, 'NamespaceNotFoundException extends from CommandNotFoundException');
578643
$this->assertCount(3, $e->getAlternatives());
579644
$this->assertContains('foo', $e->getAlternatives());
580645
$this->assertContains('foo1', $e->getAlternatives());
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class FooWithoutAliasCommand extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this
12+
->setName('foo')
13+
->setDescription('The foo command')
14+
;
15+
}
16+
17+
protected function execute(InputInterface $input, OutputInterface $output)
18+
{
19+
$output->writeln('called');
20+
}
21+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.