Commit aeb8dd56 by Qiang Xue

Refactored console help system [skip ci]

parent fadfdf69
...@@ -285,7 +285,7 @@ class Controller extends \yii\base\Controller ...@@ -285,7 +285,7 @@ class Controller extends \yii\base\Controller
*/ */
public function getHelpSummary() public function getHelpSummary()
{ {
return HelpParser::getSummary(new \ReflectionClass($this)); return $this->parseDocCommentSummary(new \ReflectionClass($this));
} }
/** /**
...@@ -297,34 +297,214 @@ class Controller extends \yii\base\Controller ...@@ -297,34 +297,214 @@ class Controller extends \yii\base\Controller
*/ */
public function getHelp() public function getHelp()
{ {
return HelpParser::getDetail(new \ReflectionClass($this)); return $this->parseDocCommentDetail(new \ReflectionClass($this));
} }
/** /**
* Returns a one-line short summary describing the specified action. * Returns a one-line short summary describing the specified action.
* @param \yii\base\Action $action action to get summary for * @param Action $action action to get summary for
* @return string a one-line short summary describing the specified action. * @return string a one-line short summary describing the specified action.
*/ */
public function getActionHelpSummary($action) public function getActionHelpSummary($action)
{ {
if ($action instanceof InlineAction) { return $this->parseDocCommentSummary($this->getActionMethodReflection($action));
return HelpParser::getSummary(new \ReflectionMethod($this, $action->actionMethod));
} else {
return HelpParser::getSummary(new \ReflectionMethod($action, 'run'));
}
} }
/** /**
* Returns the detailed help information for the specified action. * Returns the detailed help information for the specified action.
* @param \yii\base\Action $action action to get help for * @param Action $action action to get help for
* @return string the detailed help information for the specified action. * @return string the detailed help information for the specified action.
*/ */
public function getActionHelp($action) public function getActionHelp($action)
{ {
if ($action instanceof InlineAction) { return $this->parseDocCommentDetail($this->getActionMethodReflection($action));
return HelpParser::getDetail(new \ReflectionMethod($this, $action->actionMethod)); }
} else {
return HelpParser::getDetail(new \ReflectionMethod($action, 'run')); /**
* Returns the help information for the anonymous arguments for the action.
* The returned value should be an array. The keys are the argument names, and the values are
* the corresponding help information. Each value must be an array of the following structure:
*
* - required: boolean, whether this argument is required.
* - type: string, the PHP type of this argument.
* - default: string, the default value of this argument
* - comment: string, the comment of this argument
*
* The default implementation will return the help information extracted from the doc-comment of
* the parameters corresponding to the action method.
*
* @param Action $action
* @return array the help information of the action arguments
*/
public function getActionArgsHelp($action)
{
$method = $this->getActionMethodReflection($action);
$tags = $this->parseDocCommentTags($method);
$params = isset($tags['param']) ? (array) $tags['param'] : [];
$args = [];
foreach ($method->getParameters() as $i => $reflection) {
$name = $reflection->getName();
$tag = isset($params[$i]) ? $params[$i] : '';
if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
$type = $matches[1];
$comment = $matches[3];
} else {
$type = null;
$comment = $tag;
}
if ($reflection->isDefaultValueAvailable()) {
$args[$name] = [
'required' => false,
'type' => $type,
'default' => $reflection->getDefaultValue(),
'comment' => $comment,
];
} else {
$args[$name] = [
'required' => true,
'type' => $type,
'default' => null,
'comment' => $comment,
];
}
}
return $args;
}
/**
* Returns the help information for the options for the action.
* The returned value should be an array. The keys are the option names, and the values are
* the corresponding help information. Each value must be an array of the following structure:
*
* - type: string, the PHP type of this argument.
* - default: string, the default value of this argument
* - comment: string, the comment of this argument
*
* The default implementation will return the help information extracted from the doc-comment of
* the properties corresponding to the action options.
*
* @param Action $action
* @return array the help information of the action options
*/
public function getActionOptionsHelp($action)
{
$optionNames = $this->options($action->id);
if (empty($optionNames)) {
return [];
}
$class = new \ReflectionClass($this);
$options = [];
foreach ($class->getProperties() as $property) {
$name = $property->getName();
if (!in_array($name, $optionNames, true)) {
continue;
}
$defaultValue = $property->getValue($this);
$tags = $this->parseDocCommentTags($property);
if (isset($tags['var']) || isset($tags['property'])) {
$doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
if (is_array($doc)) {
$doc = reset($doc);
}
if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
$type = $matches[1];
$comment = $matches[2];
} else {
$type = null;
$comment = $doc;
}
$options[$name] = [
'type' => $type,
'default' => $defaultValue,
'comment' => $comment,
];
} else {
$options[$name] = [
'type' => null,
'default' => $defaultValue,
'comment' => '',
];
}
}
return $options;
}
private $_reflections = [];
/**
* @param Action $action
* @return \ReflectionMethod
*/
protected function getActionMethodReflection($action)
{
if (!isset($this->_reflections[$action->id])) {
if ($action instanceof InlineAction) {
$this->_reflections[$action->id] = new \ReflectionMethod($this, $action->actionMethod);
} else {
$this->_reflections[$action->id] = new \ReflectionMethod($action, 'run');
}
}
return $this->_reflections[$action->id];
}
/**
* Parses the comment block into tags.
* @param \Reflector $reflection the comment block
* @return array the parsed tags
*/
protected function parseDocCommentTags($reflection)
{
$comment = $reflection->getDocComment();
$comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
$parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
$tags = [];
foreach ($parts as $part) {
if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
$name = $matches[1];
if (!isset($tags[$name])) {
$tags[$name] = trim($matches[2]);
} elseif (is_array($tags[$name])) {
$tags[$name][] = trim($matches[2]);
} else {
$tags[$name] = [$tags[$name], trim($matches[2])];
}
}
}
return $tags;
}
/**
* Returns the first line of docblock.
*
* @param \Reflector $reflection
* @return string
*/
protected function parseDocCommentSummary($reflection)
{
$docLines = preg_split('~\R~', $reflection->getDocComment());
if (isset($docLines[1])) {
return trim($docLines[1], ' *');
}
return '';
}
/**
* Returns full description from the docblock.
*
* @param \Reflector $reflection
* @return string
*/
protected function parseDocCommentDetail($reflection)
{
$comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($reflection->getDocComment(), '/'))), "\r", '');
if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
$comment = trim(substr($comment, 0, $matches[0][1]));
}
if ($comment !== '') {
return rtrim(Console::renderColoredString(Console::markdownToAnsi($comment)));
} }
return '';
} }
} }
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
use yii\helpers\Console;
/**
* HelpParser contains methods used to get help information from phpDoc.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
class HelpParser
{
/**
* Returns the first line of docblock.
*
* @param \Reflector $reflector
* @return string
*/
public static function getSummary(\Reflector $reflector)
{
$docLines = preg_split('~\R~', $reflector->getDocComment());
if (isset($docLines[1])) {
return trim($docLines[1], ' *');
}
return '';
}
/**
* Returns full description from the docblock.
*
* @param \Reflector $reflector
* @return string
*/
public static function getDetail(\Reflector $reflector)
{
$comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($reflector->getDocComment(), '/'))), "\r", '');
if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
$comment = trim(substr($comment, 0, $matches[0][1]));
}
if ($comment !== '') {
return rtrim(Console::renderColoredString(Console::markdownToAnsi($comment)));
}
return '';
}
}
\ No newline at end of file
...@@ -10,7 +10,6 @@ namespace yii\console\controllers; ...@@ -10,7 +10,6 @@ namespace yii\console\controllers;
use Yii; use Yii;
use yii\base\Application; use yii\base\Application;
use yii\base\InlineAction; use yii\base\InlineAction;
use yii\console\Action;
use yii\console\Controller; use yii\console\Controller;
use yii\console\Exception; use yii\console\Exception;
use yii\helpers\Console; use yii\helpers\Console;
...@@ -272,146 +271,40 @@ class HelpController extends Controller ...@@ -272,146 +271,40 @@ class HelpController extends Controller
echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW); echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
} }
if ($action instanceof InlineAction) { $args = $controller->getActionArgsHelp($action);
$method = new \ReflectionMethod($controller, $action->actionMethod); foreach ($args as $name => $arg) {
} else { if ($arg['required']) {
$method = new \ReflectionMethod($action, 'run'); $this->stdout(' <' . $name . '>', Console::FG_CYAN);
} else {
$this->stdout(' [' . $name . ']', Console::FG_CYAN);
}
} }
$tags = $this->parseComment($method->getDocComment());
$options = $this->getOptionHelps($controller, $actionID);
list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : []); $options = $controller->getActionOptionsHelp($action);
foreach ($required as $arg => $description) { $options[\yii\console\Application::OPTION_APPCONFIG] = [
$this->stdout(' <' . $arg . '>', Console::FG_CYAN); 'type' => 'string',
} 'default' => null,
foreach ($optional as $arg => $description) { 'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
$this->stdout(' [' . $arg . ']', Console::FG_CYAN); ];
} ksort($options);
if (!empty($options)) { if (!empty($options)) {
$this->stdout(' [...options...]', Console::FG_RED); $this->stdout(' [...options...]', Console::FG_RED);
} }
echo "\n\n"; echo "\n\n";
if (!empty($required) || !empty($optional)) { if (!empty($args)) {
echo implode("\n\n", array_merge($required, $optional)) . "\n\n"; foreach ($args as $name => $arg) {
echo $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), $arg['required'], $arg['type'], $arg['default'], $arg['comment']) . "\n\n";
}
} }
if (!empty($options)) { if (!empty($options)) {
$this->stdout("\nOPTIONS\n\n", Console::BOLD); $this->stdout("\nOPTIONS\n\n", Console::BOLD);
echo implode("\n\n", $options) . "\n\n"; foreach ($options as $name => $option) {
} echo $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $option['type'], $option['default'], $option['comment']) . "\n\n";
}
/**
* Returns the help information about arguments.
* @param \ReflectionMethod $method
* @param string $tags the parsed comment block related with arguments
* @return array the required and optional argument help information
*/
protected function getArgHelps($method, $tags)
{
if (is_string($tags)) {
$tags = [$tags];
}
$params = $method->getParameters();
$optional = $required = [];
foreach ($params as $i => $param) {
$name = $param->getName();
$tag = isset($tags[$i]) ? $tags[$i] : '';
if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
$type = $matches[1];
$comment = $matches[3];
} else {
$type = null;
$comment = $tag;
}
if ($param->isDefaultValueAvailable()) {
$optional[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), false, $type, $param->getDefaultValue(), $comment);
} else {
$required[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), true, $type, null, $comment);
}
}
return [$required, $optional];
}
/**
* Returns the help information about the options available for a console controller.
* @param Controller $controller the console controller
* @param string $actionID name of the action, if set include local options for that action
* @return array the help information about the options
*/
protected function getOptionHelps($controller, $actionID)
{
$optionNames = $controller->options($actionID);
if (empty($optionNames)) {
return [];
}
$class = new \ReflectionClass($controller);
$options = [];
foreach ($class->getProperties() as $property) {
$name = $property->getName();
if (!in_array($name, $optionNames, true)) {
continue;
}
$defaultValue = $property->getValue($controller);
$tags = $this->parseComment($property->getDocComment());
if (isset($tags['var']) || isset($tags['property'])) {
$doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
if (is_array($doc)) {
$doc = reset($doc);
}
if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
$type = $matches[1];
$comment = $matches[2];
} else {
$type = null;
$comment = $doc;
}
$options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $type, $defaultValue, $comment);
} else {
$options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, null, $defaultValue, '');
} }
} }
$name = \yii\console\Application::OPTION_APPCONFIG;
$options[$name] = $this->formatOptionHelp(
$this->ansiFormat('--' . $name, Console::FG_RED),
false,
'string',
null,
"custom application configuration file path.\nIf not set, default application configuration is used."
);
ksort($options);
return $options;
}
/**
* Parses the comment block into tags.
* @param string $comment the comment block
* @return array the parsed tags
*/
protected function parseComment($comment)
{
$tags = [];
$comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
$parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
foreach ($parts as $part) {
if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
$name = $matches[1];
if (!isset($tags[$name])) {
$tags[$name] = trim($matches[2]);
} elseif (is_array($tags[$name])) {
$tags[$name][] = trim($matches[2]);
} else {
$tags[$name] = [$tags[$name], trim($matches[2])];
}
}
}
return $tags;
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment