diff --git a/cake/console/console_option_parser.php b/cake/console/console_option_parser.php
index 95fa79409..60c289890 100644
--- a/cake/console/console_option_parser.php
+++ b/cake/console/console_option_parser.php
@@ -67,6 +67,13 @@ class ConsoleOptionParser {
*/
protected $_args = array();
+/**
+ * Subcommands for this Shell.
+ *
+ * @var array
+ */
+ protected $_subcommands = array();
+
/**
* Construct an OptionParser so you can define its behavior
*
@@ -214,6 +221,33 @@ class ConsoleOptionParser {
return $this;
}
+/**
+ * Append a subcommand to the subcommand list.
+ * Subcommands are usually methods on your Shell, but can also be used to document
+ * Tasks
+ *
+ * ### Params
+ *
+ * - `help` - Help text for the subcommand.
+ * - `parser` - A ConsoleOptionParser for the subcommand. This allows you to create method
+ * specific option parsers. When help is generated for a subcommand, if a parser is present
+ * it will be used.
+ *
+ * @param string $name Name of the subcommand
+ * @param array $params Array of params, see above.
+ * @return $this.
+ */
+ public function addSubcommand($name, $params = array()) {
+ $defaults = array(
+ 'name' => $name,
+ 'help' => '',
+ 'parser' => null
+ );
+ $options = array_merge($defaults, $params);
+ $this->_subcommands[$name] = $options;
+ return $this;
+ }
+
/**
* Gets the arguments defined in the parser.
*
@@ -278,6 +312,20 @@ class ConsoleOptionParser {
$out[] = 'Usage:';
$out[] = $this->_generateUsage();
$out[] = '';
+ if (!empty($this->_subcommands)) {
+ $out[] = 'Subcommands:';
+ $out[] = '';
+ $max = 0;
+ foreach ($this->_subcommands as $description) {
+ $max = (strlen($description['name']) > $max) ? strlen($description['name']) : $max;
+ }
+ $max += 2;
+ foreach ($this->_subcommands as $description) {
+ $out[] = $this->_subcommandHelp($description, $max);
+ }
+ $out[] = '';
+ }
+
if (!empty($this->_options)) {
$max = 0;
foreach ($this->_options as $description) {
@@ -296,7 +344,7 @@ class ConsoleOptionParser {
foreach ($this->_args as $description) {
$max = (strlen($description['name']) > $max) ? strlen($description['name']) : $max;
}
- $max += 1;
+ $max += 2;
$out[] = 'Arguments:';
$out[] = '';
foreach ($this->_args as $description) {
@@ -319,6 +367,9 @@ class ConsoleOptionParser {
*/
protected function _generateUsage() {
$usage = array('cake ' . $this->_command);
+ if (!empty($this->_subcommands)) {
+ $usage[] = '[subcommand]';
+ }
foreach ($this->_options as $definition) {
$name = empty($definition['short']) ? '--' . $definition['name'] : '-' . $definition['short'];
$default = '';
@@ -371,7 +422,20 @@ class ConsoleOptionParser {
if (!$definition['required']) {
$optional = ' (optional)';
}
- return sprintf('%s %s%s', $name, $definition['help'], $optional);
+ return sprintf('%s%s%s', $name, $definition['help'], $optional);
+ }
+
+/**
+ * Generate help for a single subcommand.
+ *
+ * @return string
+ */
+ protected function _subcommandHelp($definition, $width) {
+ $name = $definition['name'];
+ if (strlen($name) < $width) {
+ $name = str_pad($name, $width, ' ');
+ }
+ return $name . $definition['help'];
}
/**
diff --git a/cake/tests/cases/console/console_option_parser.test.php b/cake/tests/cases/console/console_option_parser.test.php
index 4f1271a15..5a6321f1a 100644
--- a/cake/tests/cases/console/console_option_parser.test.php
+++ b/cake/tests/cases/console/console_option_parser.test.php
@@ -234,6 +234,19 @@ class ConsoleOptionParserTest extends CakeTestCase {
$parser->parse(array('one'));
}
+/**
+ * test setting a subcommand up.
+ *
+ * @return void
+ */
+ function testSubcommand() {
+ $parser = new ConsoleOptionParser();
+ $result = $parser->addSubcommand('initdb', array(
+ 'help' => 'Initialize the database'
+ ));
+ $this->assertEquals($parser, $result, 'Adding a subcommand is not chainable');
+ }
+
/**
* test getting help with defined options.
*
@@ -296,7 +309,7 @@ TEXT;
*
* @return void
*/
- function testDescriptionAndEpilog() {
+ function testHelpDescriptionAndEpilog() {
$parser = new ConsoleOptionParser('mycommand', false);
$parser->description('Description text')
->epilog('epilog text')
@@ -323,4 +336,33 @@ epilog text
TEXT;
$this->assertEquals($expected, $result, 'Help is wrong.');
}
+
+/**
+ * test that help() outputs subcommands.
+ *
+ * @return void
+ */
+ function testHelpSubcommand() {
+ $parser = new ConsoleOptionParser('mycommand', false);
+ $parser->addSubcommand('method', array('help' => 'This is another command'))
+ ->addOption('test', array('help' => 'A test option.'));
+
+ $result = $parser->help();
+ $expected = <<Usage:
+cake mycommand [subcommand] [-h] [--test]
+
+Subcommands:
+
+method This is another command
+
+Options:
+
+--help, -h Display this help.
+--test A test option.
+
+TEXT;
+ $this->assertEquals($expected, $result, 'Help is not correct.');
+
+ }
}
\ No newline at end of file