Skip to content
Permalink
Browse files

When an unknown subcommand is called, display a more useful message …

…rather than the help of the command
  • Loading branch information...
HavokInspiration committed Jul 31, 2017
1 parent 6cbfdab commit a66ebee09a49b6f5ff3d988f961aa5f7b5d25b6a
@@ -782,6 +782,64 @@ public function setRootName($name)
return $this;
}
/**
* Get the message output in the console stating that the command can not be found and tries to guess what the user
* wanted to say. Output a list of available subcommands as well.
*
* @param string $command Unknown command name trying to be dispatched.
* @return string The message to be displayed in the console.
*/
public function getCommandError($command)
{
$rootCommand = $this->getCommand();
$subcommands = array_keys((array)$this->subcommands());
$bestGuess = $this->findClosestCommand($command, $subcommands);
$out = [];
$out[] = sprintf(
'Unable to find the `%s %s` subcommand. See `bin/%s %s --help`.',
$rootCommand,
$command,
$this->rootName,
$rootCommand
);
$out[] = '';
if ($bestGuess !== null) {
$out[] = sprintf('Did you mean : `%s %s` ?', $rootCommand, $bestGuess);
$out[] = '';
}
$out[] = sprintf('Available subcommands for the `%s` command are : ', $rootCommand);
$out[] = '';
foreach ($subcommands as $subcommand) {
$out[] = ' - ' . $subcommand;
}
return implode("\n", $out);
}
/**
* Tries to guess the command the user originally wanted using the levenshtein algorithm.
*
* @param string $command Unknown command name trying to be dispatched.
* @param array $subcommands List of subcommands name this shell supports.
* @return string|null The closest name to the command submitted by the user.
*/
protected function findClosestCommand($command, $subcommands)
{
$bestGuess = null;
foreach ($subcommands as $subcommand) {
$score = levenshtein($command, $subcommand);
if (!isset($bestScore) || $score < $bestScore) {
$bestScore = $score;
$bestGuess = $subcommand;
}
}
return $bestGuess;
}
/**
* Parse the value for a long option out of $this->_tokens. Will handle
* options with an `=` in them.
@@ -507,7 +507,7 @@ public function runCommand($argv, $autoMethod = false, $extra = [])
return $this->main(...$this->args);
}
$this->out($this->OptionParser->help($command));
$this->err($this->OptionParser->getCommandError($command));
return false;
}
@@ -795,6 +795,46 @@ public function testHelpWithRootName()
--help, -h Display this help.
--test A test option.
TEXT;
$this->assertTextEquals($expected, $result, 'Help is not correct.');
}
/**
* test that getCommandError() with an unknown subcommand param shows a helpful message
*
* @return void
*/
public function testHelpUnknownSubcommand()
{
$subParser = [
'options' => [
'foo' => [
'short' => 'f',
'help' => 'Foo.',
'boolean' => true,
]
],
];
$parser = new ConsoleOptionParser('mycommand', false);
$parser
->addSubcommand('method', [
'help' => 'This is a subcommand',
'parser' => $subParser
])
->addOption('test', ['help' => 'A test option.'])
->addSubcommand('unstash');
$result = $parser->getCommandError('unknown');
$expected = <<<TEXT
Unable to find the `mycommand unknown` subcommand. See `bin/cake mycommand --help`.
Did you mean : `mycommand unstash` ?
Available subcommands for the `mycommand` command are :
- method
- unstash
TEXT;
$this->assertTextEquals($expected, $result, 'Help is not correct.');
}
@@ -954,7 +954,7 @@ public function testRunCommandAutoMethodOff()
public function testRunCommandWithMethodNotInSubcommands()
{
$parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
->setMethods(['help'])
->setMethods(['getCommandError'])
->setConstructorArgs(['knife'])
->getMock();
$io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
@@ -970,7 +970,7 @@ public function testRunCommandWithMethodNotInSubcommands()
->will($this->returnValue($parser));
$parser->expects($this->once())
->method('help');
->method('getCommandError');
$shell->expects($this->never())->method('startup');
$shell->expects($this->never())->method('roll');
@@ -1018,7 +1018,7 @@ public function testRunCommandWithMethodInSubcommands()
public function testRunCommandWithMissingMethodInSubcommands()
{
$parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
->setMethods(['help'])
->setMethods(['getCommandError'])
->setConstructorArgs(['knife'])
->getMock();
$parser->addSubCommand('slice');
@@ -1036,7 +1036,7 @@ public function testRunCommandWithMissingMethodInSubcommands()
->method('startup');
$parser->expects($this->once())
->method('help');
->method('getCommandError');
$shell->runCommand(['slice', 'cakes', '--verbose']);
}
@@ -1049,7 +1049,7 @@ public function testRunCommandWithMissingMethodInSubcommands()
public function testRunCommandBaseClassMethod()
{
$shell = $this->getMockBuilder('Cake\Console\Shell')
->setMethods(['startup', 'getOptionParser', 'out', 'hr'])
->setMethods(['startup', 'getOptionParser', 'err', 'hr'])
->disableOriginalConstructor()
->getMock();
@@ -1058,11 +1058,11 @@ public function testRunCommandBaseClassMethod()
->disableOriginalConstructor()
->getMock();
$parser->expects($this->once())->method('help');
$parser->expects($this->once())->method('getCommandError');
$shell->expects($this->once())->method('getOptionParser')
->will($this->returnValue($parser));
$shell->expects($this->never())->method('hr');
$shell->expects($this->once())->method('out');
$shell->expects($this->once())->method('err');
$shell->runCommand(['hr']);
}
@@ -1075,18 +1075,18 @@ public function testRunCommandBaseClassMethod()
public function testRunCommandMissingMethod()
{
$shell = $this->getMockBuilder('Cake\Console\Shell')
->setMethods(['startup', 'getOptionParser', 'out', 'hr'])
->setMethods(['startup', 'getOptionParser', 'err', 'hr'])
->disableOriginalConstructor()
->getMock();
$shell->io($this->getMockBuilder('Cake\Console\ConsoleIo')->getMock());
$parser = $this->getMockBuilder('Cake\Console\ConsoleOptionParser')
->disableOriginalConstructor()
->getMock();
$parser->expects($this->once())->method('help');
$parser->expects($this->once())->method('getCommandError');
$shell->expects($this->once())->method('getOptionParser')
->will($this->returnValue($parser));
$shell->expects($this->once())->method('out');
$shell->expects($this->once())->method('err');
$result = $shell->runCommand(['idontexist']);
$this->assertFalse($result);
@@ -1127,7 +1127,7 @@ public function testRunCommandTriggeringHelp()
public function testRunCommandNotCallUnexposedTask()
{
$shell = $this->getMockBuilder('Cake\Console\Shell')
->setMethods(['startup', 'hasTask', 'out'])
->setMethods(['startup', 'hasTask', 'err'])
->disableOriginalConstructor()
->getMock();
$shell->io($this->getMockBuilder('Cake\Console\ConsoleIo')->getMock());
@@ -1143,7 +1143,7 @@ public function testRunCommandNotCallUnexposedTask()
->method('hasTask')
->will($this->returnValue(true));
$shell->expects($this->never())->method('startup');
$shell->expects($this->once())->method('out');
$shell->expects($this->once())->method('err');
$shell->RunCommand = $task;
$result = $shell->runCommand(['run_command', 'one']);

0 comments on commit a66ebee

Please sign in to comment.
You can’t perform that action at this time.