8000 feature #25732 [Console] Add option to automatically run suggested co… · symfony/symfony@7b8934b · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 7b8934b

Browse files
committed
feature #25732 [Console] Add option to automatically run suggested command if there is only 1 alternative (pierredup)
This PR was squashed before being merged into the 4.1-dev branch (closes #25732). Discussion ---------- [Console] Add option to automatically run suggested command if there is only 1 alternative | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | When mistyping a console command, you get an error giving suggested commands. If there is only 1 alternative suggestion, this PR will give you the option to run that command instead. This makes it easier to run the correct command without having to re-type/copy-paste/update the previous run command ![console](https://user-images.githubusercontent.com/144858/34724377-4b46c726-f556-11e7-94a3-a9d7c9d75e74.gif) Commits ------- 83d52f0 [Console] Add option to automatically run suggested command if there is only 1 alternative
2 parents cf045c0 + 83d52f0 commit 7b8934b

File tree

5 files changed

+147
-14
lines changed

5 files changed

+147
-14
lines changed

src/Symfony/Component/Console/Application.php

Lines 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($alte 8000 rnatives = $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

Lines 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

Lines 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

Lines 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());
Lines 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