diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php
index 46080820e064b..1958a4fcc9a6f 100644
--- a/src/Symfony/Component/Console/Application.php
+++ b/src/Symfony/Component/Console/Application.php
@@ -268,6 +268,34 @@ public function getHelp()
return implode(PHP_EOL, $messages);
}
+ /**
+ * Gets the help message in Markdown.
+ *
+ * @return string A help message in Markdown.
+ */
+ public function getHelpInMarkdown()
+ {
+ $messages = array(
+ $this->getLongVersionInMarkdown(),
+ '',
+ 'Usage',
+ '-----',
+ sprintf(" [options] command [arguments]\n"),
+ 'Options',
+ '-------'
+ );
+
+ foreach ($this->getDefinition()->getOptions() as $option) {
+ $messages[] = sprintf(' %-16s %s %s',
+ '--'.$option->getName(),
+ $option->getShortcut() ? '(-'.$option->getShortcut().')' : ' ',
+ $option->getDescription()
+ );
+ }
+
+ return implode(PHP_EOL, $messages);
+ }
+
/**
* Sets whether to catch exceptions or not during commands execution.
*
@@ -356,6 +384,22 @@ public function getLongVersion()
return 'Console Tool';
}
+ /**
+ * Returns the long version of the application in Markdown.
+ *
+ * @return string The long application version in Markdown
+ *
+ * @api
+ */
+ public function getLongVersionInMarkdown()
+ {
+ if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
+ return sprintf('%s version %s', $this->getName(), $this->getVersion());
+ }
+
+ return implode(PHP_EOL, array('Console Tool', '============'));
+ }
+
/**
* Registers a new command.
*
@@ -680,11 +724,7 @@ public function asText($namespace = null, $raw = false)
{
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
- $width = 0;
- foreach ($commands as $command) {
- $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
- }
- $width += 2;
+ $width = $this->getMaximumCommandNameLength($commands) + 2;
if ($raw) {
$messages = array();
@@ -718,6 +758,43 @@ public function asText($namespace = null, $raw = false)
return implode(PHP_EOL, $messages);
}
+ /**
+ * Returns Markdown representation of the Application.
+ *
+ * @param string $namespace An optional namespace name
+ *
+ * @return string Markdown representing the Application
+ */
+ public function asMarkdown($namespace = null)
+ {
+ $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
+
+ $width = $this->getMaximumCommandNameLength($commands) + 2;
+
+ $messages = array($this->getHelpInMarkdown(), '');
+ if ($namespace) {
+ $message = sprintf("Available commands for the \"%s\" namespace", $namespace);
+ $messages[] = $message;
+ $messages[] = str_repeat('-', mb_strlen($message));
+ } else {
+ $messages[] = 'Available commands';
+ $messages[] = '------------------';
+ }
+
+ // add commands by namespace
+ foreach ($this->sortCommands($commands) as $space => $commands) {
+ if (!$namespace && '_global' !== $space) {
+ $messages[] = '';
+ }
+
+ foreach ($commands as $name => $command) {
+ $messages[] = sprintf(" %-${width}s %s", $name, $command->getDescription());
+ }
+ }
+
+ return implode(PHP_EOL, $messages);
+ }
+
/**
* Returns an XML representation of the Application.
*
@@ -954,6 +1031,15 @@ protected function getDefaultHelperSet()
));
}
+ private function getMaximumCommandNameLength(array $commands) {
+ $width = 0;
+ foreach ($commands as $command) {
+ $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
+ }
+
+ return $width;
+ }
+
/**
* Runs and parses stty -a if it's available, suppressing any error output
*
diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php
index 1b11bb8105d5e..98c9ec6e7534f 100644
--- a/src/Symfony/Component/Console/Command/Command.php
+++ b/src/Symfony/Component/Console/Command/Command.php
@@ -583,6 +583,48 @@ public function asText()
return implode("\n", $messages);
}
+ /**
+ * Returns Markdown representation of the command.
+ *
+ * @return string Markdown representing the command
+ */
+ public function asMarkdown()
+ {
+ if ($this->application && !$this->applicationDefinitionMerged) {
+ $this->getSynopsis();
+ $this->mergeApplicationDefinition(false);
+ }
+
+ $markdown = array();
+ $markdown[] = $this->name;
+ $markdown[] = \str_repeat('=', mb_strlen($this->name));
+ $markdown[] = '';
+ $markdown[] = $this->description;
+ $markdown[] = '';
+ $markdown[] = 'Usage';
+ $markdown[] = '-----';
+ $markdown[] = ' '.$this->getSynopsis();
+ $markdown[] = '';
+
+ if ($this->getAliases()) {
+ $markdown[] = 'Aliases';
+ $markdown[] = '-------';
+ foreach ($this->getAliases() as $alias) {
+ $markdown[] = '* '.$alias;
+ }
+ $markdown[] = '';
+ }
+
+ $markdown[] = $this->getNativeDefinition()->asMarkdown();
+ if ($help = $this->getProcessedHelp()) {
+ $markdown[] = 'Help';
+ $markdown[] = '----';
+ $markdown[] = ' '.strip_tags(str_replace("\n", "\n ", $help))."\n";
+ }
+
+ return implode("\n", $markdown);
+ }
+
/**
* Returns an XML representation of the command.
*
diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php
index ac4dd3d54d772..fd26bda2c268a 100644
--- a/src/Symfony/Component/Console/Command/HelpCommand.php
+++ b/src/Symfony/Component/Console/Command/HelpCommand.php
@@ -37,6 +37,7 @@ protected function configure()
->setName('help')
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
+ new InputOption('markdown', null, InputOption::VALUE_NONE, 'To output help as Markdown'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
))
->setDescription('Displays help for a command')
@@ -76,6 +77,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
if ($input->getOption('xml')) {
$output->writeln($this->command->asXml(), OutputInterface::OUTPUT_RAW);
+ } elseif ($input->getOption('markdown')) {
+ $output->writeln($this->command->asMarkdown(), OutputInterface::OUTPUT_RAW);
} else {
$output->writeln($this->command->asText());
}
diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php
index 69d2f1efe125b..fb596fc3d1cc0 100644
--- a/src/Symfony/Component/Console/Input/InputDefinition.php
+++ b/src/Symfony/Component/Console/Input/InputDefinition.php
@@ -407,20 +407,7 @@ public function getSynopsis()
*/
public function asText()
{
- // find the largest option or argument name
- $max = 0;
- foreach ($this->getOptions() as $option) {
- $nameLength = strlen($option->getName()) + 2;
- if ($option->getShortcut()) {
- $nameLength += strlen($option->getShortcut()) + 3;
- }
-
- $max = max($max, $nameLength);
- }
- foreach ($this->getArguments() as $argument) {
- $max = max($max, strlen($argument->getName()));
- }
- ++$max;
+ $max = $this->getMaximumOptionOrArgumentNameLength() + 1;
$text = array();
@@ -470,6 +457,61 @@ public function asText()
return implode("\n", $text);
}
+ /**
+ * Returns Markdown representation of the InputDefinition.
+ *
+ * @return string Markdown string representing the InputDefinition
+ */
+ public function asMarkdown()
+ {
+ $max = $this->getMaximumOptionOrArgumentNameLength() + 1;
+
+ $markdown = array();
+ if ($this->getArguments()) {
+ $markdown[] = 'Arguments';
+ $markdown[] = '---------';
+ foreach ($this->getArguments() as $argument) {
+ if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
+ $default = sprintf(' (default: %s)', $this->formatDefaultValue($argument->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $description = str_replace("\n", "\n".str_pad('', $max + 2, ' '), $argument->getDescription());
+
+ $markdown[] = sprintf(" %-${max}s %s%s", $argument->getName(), $description, $default);
+ }
+ $markdown[] = '';
+ }
+
+ if ($this->getOptions()) {
+ $markdown[] = 'Options';
+ $markdown[] = '-------';
+ foreach ($this->getOptions() as $option) {
+ if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
+ $default = sprintf(' (default: %s)', $this->formatDefaultValue($option->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $multiple = $option->isArray() ? ' (multiple values allowed)' : '';
+ $description = str_replace("\n", "\n".str_pad('', $max + 2, ' '), $option->getDescription());
+
+ $optionMax = $max - strlen($option->getName()) - 2;
+ $markdown[] = sprintf(" %s %-${optionMax}s%s%s%s",
+ '--'.$option->getName(),
+ $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
+ $description,
+ $default,
+ $multiple
+ );
+ }
+ $markdown[] = '';
+ }
+
+ return implode("\n", $markdown);
+ }
+
/**
* Returns an XML representation of the InputDefinition.
*
@@ -524,6 +566,30 @@ public function asXml($asDom = false)
return $asDom ? $dom : $dom->saveXml();
}
+ /**
+ * Finds the longest option or argument name and returns its length.
+ *
+ * @return integer Length of the longest option or argument name
+ */
+ private function getMaximumOptionOrArgumentNameLength()
+ {
+ // find the largest option or argument name
+ $max = 0;
+ foreach ($this->getOptions() as $option) {
+ $nameLength = strlen($option->getName()) + 2;
+ if ($option->getShortcut()) {
+ $nameLength += strlen($option->getShortcut()) + 3;
+ }
+
+ $max = max($max, $nameLength);
+ }
+ foreach ($this->getArguments() as $argument) {
+ $max = max($max, strlen($argument->getName()));
+ }
+
+ return $max;
+ }
+
private function formatDefaultValue($default)
{
if (version_compare(PHP_VERSION, '5.4', '<')) {
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index 23edd490b3658..30a41add25ad1 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -377,6 +377,15 @@ public function testAsText()
$this->assertStringEqualsFile(self::$fixturesPath.'/application_astext2.txt', $this->normalizeLineBreaks($application->asText('foo')), '->asText() returns a text representation of the application');
}
+ public function testAsMarkdown()
+ {
+ $application = new Application();
+ $application->add(new \FooCommand);
+ $this->ensureStaticCommandHelp($application);
+ $this->assertStringEqualsFile(self::$fixturesPath.'/application_asmarkdown1.txt', $application->asMarkdown(), '->asMarkdown() returns Markdown representation of the application');
+ $this->assertStringEqualsFile(self::$fixturesPath.'/application_asmarkdown2.txt', $application->asMarkdown('foo'), '->asMarkdown() returns Markdown representation of the application');
+ }
+
public function testAsXml()
{
$application = new Application();
diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
index 166ce7879ea30..11c1e9c44d835 100644
--- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php
+++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
@@ -274,6 +274,15 @@ public function testAsText()
$this->assertStringEqualsFile(self::$fixturesPath.'/command_astext.txt', $command->asText(), '->asText() returns a text representation of the command');
}
+ public function testAsMarkdown()
+ {
+ $command = new \TestCommand();
+ $command->setApplication(new Application());
+ $tester = new CommandTester($command);
+ $tester->execute(array('command' => $command->getName()));
+ $this->assertStringEqualsFile(self::$fixturesPath.'/command_asmarkdown.txt', $command->asMarkdown(), '->asMarkdown() returns Markdown representation of the command');
+ }
+
public function testAsXml()
{
$command = new \TestCommand();
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown1.txt
new file mode 100644
index 0000000000000..8d4d623f58767
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown1.txt
@@ -0,0 +1,24 @@
+Console Tool
+============
+
+Usage
+-----
+ [options] command [arguments]
+
+Options
+-------
+ --help (-h) Display this help message.
+ --quiet (-q) Do not output any message.
+ --verbose (-v) Increase verbosity of messages.
+ --version (-V) Display this application version.
+ --ansi Force ANSI output.
+ --no-ansi Disable ANSI output.
+ --no-interaction (-n) Do not ask any interactive question.
+
+Available commands
+------------------
+ afoobar The foo:bar command
+ help Displays help for a command
+ list Lists commands
+
+ foo:bar The foo:bar command
\ No newline at end of file
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown2.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown2.txt
new file mode 100644
index 0000000000000..7a958c822276d
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/Fixtures/application_asmarkdown2.txt
@@ -0,0 +1,20 @@
+Console Tool
+============
+
+Usage
+-----
+ [options] command [arguments]
+
+Options
+-------
+ --help (-h) Display this help message.
+ --quiet (-q) Do not output any message.
+ --verbose (-v) Increase verbosity of messages.
+ --version (-V) Display this application version.
+ --ansi Force ANSI output.
+ --no-ansi Disable ANSI output.
+ --no-interaction (-n) Do not ask any interactive question.
+
+Available commands for the "foo" namespace
+------------------------------------------
+ foo:bar The foo:bar command
\ No newline at end of file
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt
index ed168f25cbcda..fb9f10785c7a6 100644
--- a/src/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt
+++ b/src/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt
@@ -2,7 +2,7 @@
- help [--xml] [command_name]
+ help [--markdown] [--xml] [command_name]
Displays help for a command
The <info>help</info> command displays help for a given command:
@@ -23,6 +23,9 @@
+
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_run2.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_run2.txt
index 6831afdd128c1..7fc9438bc0600 100644
--- a/src/Symfony/Component/Console/Tests/Fixtures/application_run2.txt
+++ b/src/Symfony/Component/Console/Tests/Fixtures/application_run2.txt
@@ -1,11 +1,12 @@
Usage:
- help [--xml] [command_name]
+ help [--markdown] [--xml] [command_name]
Arguments:
command The command to execute
command_name The command name (default: "help")
Options:
+ --markdown To output help as Markdown
--xml To output help as XML
--help (-h) Display this help message.
--quiet (-q) Do not output any message.
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/command_asmarkdown.txt b/src/Symfony/Component/Console/Tests/Fixtures/command_asmarkdown.txt
new file mode 100644
index 0000000000000..29ca05c1916c0
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/Fixtures/command_asmarkdown.txt
@@ -0,0 +1,30 @@
+namespace:name
+==============
+
+description
+
+Usage
+-----
+ namespace:name
+
+Aliases
+-------
+* name
+
+Arguments
+---------
+ command The command to execute
+
+Options
+-------
+ --help (-h) Display this help message.
+ --quiet (-q) Do not output any message.
+ --verbose (-v) Increase verbosity of messages.
+ --version (-V) Display this application version.
+ --ansi Force ANSI output.
+ --no-ansi Disable ANSI output.
+ --no-interaction (-n) Do not ask any interactive question.
+
+Help
+----
+ help
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/definition_asmarkdown.txt b/src/Symfony/Component/Console/Tests/Fixtures/definition_asmarkdown.txt
new file mode 100644
index 0000000000000..81d5b0dc69515
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/Fixtures/definition_asmarkdown.txt
@@ -0,0 +1,13 @@
+Arguments
+---------
+ foo The foo argument
+ baz The baz argument (default: true)
+ bar The bar argument (default: ["http://foo.com/"])
+
+Options
+-------
+ --foo (-f) The foo option
+ --baz The baz option (default: false)
+ --bar (-b) The bar option (default: "bar")
+ --qux The qux option (default: ["http://foo.com/","bar"]) (multiple values allowed)
+ --qux2 The qux2 option (default: {"foo":"bar"}) (multiple values allowed)
diff --git a/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php b/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
index c322ed42bca68..61cd3e2cdc8ca 100644
--- a/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
+++ b/src/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php
@@ -332,6 +332,21 @@ public function testAsText()
$this->assertStringEqualsFile(self::$fixtures.'/definition_astext.txt', $definition->asText(), '->asText() returns a textual representation of the InputDefinition');
}
+ public function testAsMarkdown()
+ {
+ $definition = new InputDefinition(array(
+ new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'),
+ new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true),
+ new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('http://foo.com/')),
+ new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'),
+ new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false),
+ new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'),
+ new InputOption('qux', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux option', array('http://foo.com/', 'bar')),
+ new InputOption('qux2', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux2 option', array('foo' => 'bar')),
+ ));
+ $this->assertStringEqualsFile(self::$fixtures.'/definition_asmarkdown.txt', $definition->asMarkdown(), '->asMarkdown() returns Markdown representation of the InputDefinition');
+ }
+
public function testAsXml()
{
$definition = new InputDefinition(array(