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 08da6e4

Browse filesBrowse files
committed
[Command] Added question helper for unknown or ambiguous commands
1 parent 835176c commit 08da6e4
Copy full SHA for 08da6e4
Expand file treeCollapse file tree

10 files changed

+263
-41
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+38-32Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111

1212
namespace Symfony\Component\Console;
1313

14+
use Symfony\Component\Console\Exception\AmbiguousCommandException;
15+
use Symfony\Component\Console\Exception\AmbiguousNamespaceException;
1416
use Symfony\Component\Console\Exception\ExceptionInterface;
17+
use Symfony\Component\Console\Exception\InvalidArgumentException;
18+
use Symfony\Component\Console\Exception\UnknownCommandException;
19+
use Symfony\Component\Console\Exception\UnknownNamespaceException;
1520
use Symfony\Component\Console\Helper\DebugFormatterHelper;
1621
use Symfony\Component\Console\Helper\ProcessHelper;
1722
use Symfony\Component\Console\Helper\QuestionHelper;
@@ -36,6 +41,7 @@
3641
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
3742
use Symfony\Component\Console\Exception\CommandNotFoundException;
3843
use Symfony\Component\Console\Exception\LogicException;
44+
use Symfony\Component\Console\Question\ChoiceQuestion;
3945
use Symfony\Component\Debug\Exception\FatalThrowableError;
4046
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
4147

@@ -185,7 +191,30 @@ public function doRun(InputInterface $input, OutputInterface $output)
185191
}
186192

187193
// the command name MUST be the first element of the input
188-
$command = $this->find($name);
194+
do {
195+
try {
196+
$command = $this->find($name);
197+
} catch (CommandNotFoundException $e) {
198+
$alternatives = $e->getAlternatives();
199+
if (0 === count($alternatives) || !$input->isInteractive() || !$this->getHelperSet()->has('question')) {
200+
throw $e;
201+
}
202+
203+
$helper = $this->getHelperSet()->get('question');
204+
$question = new ChoiceQuestion(strtok($e->getMessage(), "\n").' Please select one of these suggested commands:', $alternatives);
205+
$question->setMaxAttempts(1);
206+
207+
try {
208+
$name = $helper->ask($input, $output, $question);
209+
} catch (InvalidArgumentException $ex) {
210+
throw $e;
211+
}
212+
213+
if (null === $name) {
214+
throw $e;
215+
}
216+
}
217+
} while (!isset($command));
189218

190219
$this->runningCommand = $command;
191220
$exitCode = $this->doRunCommand($command, $input, $output);
@@ -477,7 +506,8 @@ public function getNamespaces()
477506
*
478507
* @return string A registered namespace
479508
*
480-
* @throws CommandNotFoundException When namespace is incorrect or ambiguous
509+
* @throws UnknownNamespaceException When namespace is incorrect
510+
* @throws AmbiguousNamespaceException When namespace is ambiguous
481511
*/
482512
public function findNamespace($namespace)
483513
{
@@ -486,24 +516,12 @@ public function findNamespace($namespace)
486516
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
487517

488518
if (empty($namespaces)) {
489-
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
490-
491-
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
492-
if (1 == count($alternatives)) {
493-
$message .= "\n\nDid you mean this?\n ";
494-
} else {
495-
$message .= "\n\nDid you mean one of these?\n ";
496-
}
497-
498-
$message .= implode("\n ", $alternatives);
499-
}
500-
501-
throw new CommandNotFoundException($message, $alternatives);
519+
throw new UnknownNamespaceException($namespace, $this->findAlternatives($namespace, $allNamespaces, array()));
502520
}
503521

504522
$exact = in_array($namespace, $namespaces, true);
505523
if (count($namespaces) > 1 && !$exact) {
506-
throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
524+
throw new AmbiguousNamespaceException($namespace, $namespaces);
507525
}
508526

509527
return $exact ? $namespace : reset($namespaces);
@@ -519,7 +537,8 @@ public function findNamespace($namespace)
519537
*
520538
* @return Command A Command instance
521539
*
522-
* @throws CommandNotFoundException When command name is incorrect or ambiguous
540+
* @throws UnknownCommandException When command name is incorrect
541+
* @throws AmbiguousCommandException When command name is ambiguous
523542
*/
524543
public function find($name)
525544
{
@@ -533,18 +552,7 @@ public function find($name)
533552
$this->findNamespace(substr($name, 0, $pos));
534553
}
535554

536-
$message = sprintf('Command "%s" is not defined.', $name);
537-
538-
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
539-
if (1 == count($alternatives)) {
540-
$message .= "\n\nDid you mean this?\n ";
541-
} else {
542-
$message .= "\n\nDid you mean one of these?\n ";
543-
}
544-
$message .= implode("\n ", $alternatives);
545-
}
546-
547-
throw new CommandNotFoundException($message, $alternatives);
555+
throw new UnknownCommandException($name, $this->findAlternatives($name, $allCommands, array()));
548556
}
549557

550558
// filter out aliases for commands which are already on the list
@@ -559,9 +567,7 @@ public function find($name)
559567

560568
$exact = in_array($name, $commands, true);
561569
if (count($commands) > 1 && !$exact) {
562-
$suggestions = $this->getAbbreviationSuggestions(array_values($commands));
563-
564-
throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands));
570+
throw new AmbiguousCommandException($name, array_values($commands));
565571
}
566572

567573
return $this->get($exact ? $name : reset($commands));
+36Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
* @author Martin Hasoň <martin.hason@gmail.com>
16+
*/
17+
class AmbiguousCommandException extends CommandNotFoundException
18+
{
19+
private $command;
20+
21+
public function __construct($command, $alternatives = array(), $code = null, $previous = null)
22+
{
23+
$this->command = $command;
24+
$message = sprintf('Command "%s" is ambiguous (%s).', $command, $this->getAbbreviationSuggestions($alternatives));
25+
26+
parent::__construct($message, $alternatives, $code, $previous);
27+
}
28+
29+
/**
30+
* @return string
31+
*/
32+
public function getCommand()
33+
{
34+
return $this->command;
35+
}
36+
}
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
* @author Martin Hasoň <martin.hason@gmail.com>
16+
*/
17+
class AmbiguousNamespaceException extends CommandNotFoundException
18+
{
19+
private $namespace;
20+
21+
public function __construct($namespace, $alternatives = array(), $code = null, $previous = null)
22+
{
23+
$this->command = $namespace;
24+
25+
$message = sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($alternatives));
26+
27+
parent::__construct($message, $alternatives, $code, $previous);
28+
}
29+
30+
/**
31+
* @return string
32+
*/
33+
public function getNamespace()
34+
{
35+
return $this->namespace;
36+
}
37+
}

‎src/Symfony/Component/Console/Exception/CommandNotFoundException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Exception/CommandNotFoundException.php
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,16 @@ public function getAlternatives()
4040
{
4141
return $this->alternatives;
4242
}
43+
44+
/**
45+
* Returns abbreviated suggestions in string format.
46+
*
47+
* @param array $abbrevs Abbreviated suggestions to convert
48+
*
49+
* @return string A formatted string of abbreviated suggestions
50+
*/
51+
protected function getAbbreviationSuggestions($abbrevs)
52+
{
53+
return sprintf('%s, %s%s', reset($abbrevs), next($abbrevs), count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
54+
}
4355
}
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
* @author Martin Hasoň <martin.hason@gmail.com>
16+
*/
17+
class UnknownCommandException extends CommandNotFoundException
18+
{
19+
private $command;
20+
21+
public function __construct($command, $alternatives = array(), $code = null, $previous = null)
22+
{
23+
$this->command = $command;
24+
25+
$message = sprintf('Command "%s" is not defined.', $command);
26+
27+
if ($alternatives) {
28+
if (1 == count($alternatives)) {
29+
$message .= "\n\nDid you mean this?\n ";
30+
} else {
31+
$message .= "\n\nDid you mean one of these?\n ";
32+
}
33+
34+
$message .= implode("\n ", $alternatives);
35+
}
36+
37+
parent::__construct($message, $alternatives, $code, $previous);
38+
}
39+
40+
/**
41+
* @return string
42+
*/
43+
public function getCommand()
44+
{
45+
return $this->command;
46+
}
47+
}
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
* @author Martin Hasoň <martin.hason@gmail.com>
16+
*/
17+
class UnknownNamespaceException extends CommandNotFoundException
18+
{
19+
private $namespace;
20+
21+
public function __construct($namespace, $alternatives = array(), $code = null, $previous = null)
22+
{
23+
$this->namespace = $namespace;
24+
25+
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
26+
27+
if ($alternatives) {
28+
if (1 == count($alternatives)) {
29+
$message .= "\n\nDid you mean this?\n ";
30+
} else {
31+
$message .= "\n\nDid you mean one of these?\n ";
32+
}
33+
34+
$message .= implode("\n ", $alternatives);
35+
}
36+
37+
parent::__construct($message, $alternatives, $code, $previous);
38+
}
39+
40+
/**
41+
* @return string
42+
*/
43+
public function getNamespace()
44+
{
45+
return $this->namespace;
46+
}
47+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,33 @@ public function testFindAlternativeCommands()
405405
}
406406
}
407407

408+
public function testFindAlternativeCommandsWithQuestion()
409+
{
410+
$application = new Application();
411+
$application->setAutoExit(false);
412+
putenv('COLUMNS=120');
413+
putenv('SHELL_INTERACTIVE=1');
414+
$application->add(new \FooCommand());
415+
$application->add(new \Foo1Command());
416+
$application->add(new \Foo2Command());
417+
418+
$input = new ArrayInput(array('command' => 'foo'));
419+
420+
$inputStream = fopen('php://memory', 'r+', false);
421+
fwrite($inputStream, "1\n");
422+
rewind($inputStream);
423+
$input->setStream($inputStream);
424+
425+
$output = new StreamOutput(fopen('php://memory', 'w', false), StreamOutput::VERBOSITY_NORMAL, false);
426+
427+
$application->run($input, $output);
428+
429+
rewind($output->getStream());
430+
$display = str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream()));
431+
432+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_unknown_command_question.txt', $display);
433+
}
434+
408435
public function testFindAlternativeCommandsWithAnAlias()
409436
{
410437
$fooCommand = new \FooCommand();
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
3-
[Symfony\Component\Console\Exception\CommandNotFoundException]
4-
Command "foo" is not defined.
5-
2+
3+
[Symfony\Component\Console\Exception\UnknownCommandException]
4+
Command "foo" is not defined.
5+
66

+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

2-
3-
[Symfony\Component\Console\Exception\CommandNotFoundException]
4-
Command "foo" is not define
5-
d.
6-
2+
3+
[Symfony\Component\Console\Exception\UnknownCommandException]
4+
Command "foo" is not define
5+
d.
6+
77

+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Command "foo" is not defined. Please select one of these suggested commands:
2+
[0] foo:bar1
3+
[1] foo:bar
4+
[2] foo1:bar
5+
[3] afoobar
6+
[4] afoobar1
7+
[5] afoobar2
8+
> 1
9+
interact called
10+
called

0 commit comments

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