From aeb8dd560a3673f61ac4888da032a130d9734166 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 4 Sep 2014 21:14:09 -0400 Subject: [PATCH] Refactored console help system [skip ci] --- framework/console/Controller.php | 206 ++++++++++++++++-- framework/console/HelpParser.php | 52 ----- .../console/controllers/HelpController.php | 149 ++----------- 3 files changed, 214 insertions(+), 193 deletions(-) delete mode 100644 framework/console/HelpParser.php diff --git a/framework/console/Controller.php b/framework/console/Controller.php index 2c97537a4e..4e7f00695c 100644 --- a/framework/console/Controller.php +++ b/framework/console/Controller.php @@ -285,7 +285,7 @@ class Controller extends \yii\base\Controller */ 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 */ 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. - * @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. */ public function getActionHelpSummary($action) { - if ($action instanceof InlineAction) { - return HelpParser::getSummary(new \ReflectionMethod($this, $action->actionMethod)); - } else { - return HelpParser::getSummary(new \ReflectionMethod($action, 'run')); - } + return $this->parseDocCommentSummary($this->getActionMethodReflection($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. */ public function getActionHelp($action) { - if ($action instanceof InlineAction) { - return HelpParser::getDetail(new \ReflectionMethod($this, $action->actionMethod)); - } else { - return HelpParser::getDetail(new \ReflectionMethod($action, 'run')); + return $this->parseDocCommentDetail($this->getActionMethodReflection($action)); + } + + /** + * 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 ''; } } diff --git a/framework/console/HelpParser.php b/framework/console/HelpParser.php deleted file mode 100644 index beb86f0e7a..0000000000 --- a/framework/console/HelpParser.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @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 diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php index a55e8ce25c..3ee0e4c004 100644 --- a/framework/console/controllers/HelpController.php +++ b/framework/console/controllers/HelpController.php @@ -10,7 +10,6 @@ namespace yii\console\controllers; use Yii; use yii\base\Application; use yii\base\InlineAction; -use yii\console\Action; use yii\console\Controller; use yii\console\Exception; use yii\helpers\Console; @@ -272,146 +271,40 @@ class HelpController extends Controller echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW); } - if ($action instanceof InlineAction) { - $method = new \ReflectionMethod($controller, $action->actionMethod); - } else { - $method = new \ReflectionMethod($action, 'run'); + $args = $controller->getActionArgsHelp($action); + foreach ($args as $name => $arg) { + if ($arg['required']) { + $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'] : []); - foreach ($required as $arg => $description) { - $this->stdout(' <' . $arg . '>', Console::FG_CYAN); - } - foreach ($optional as $arg => $description) { - $this->stdout(' [' . $arg . ']', Console::FG_CYAN); - } + $options = $controller->getActionOptionsHelp($action); + $options[\yii\console\Application::OPTION_APPCONFIG] = [ + 'type' => 'string', + 'default' => null, + 'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.", + ]; + ksort($options); + if (!empty($options)) { $this->stdout(' [...options...]', Console::FG_RED); } echo "\n\n"; - if (!empty($required) || !empty($optional)) { - echo implode("\n\n", array_merge($required, $optional)) . "\n\n"; + if (!empty($args)) { + 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)) { $this->stdout("\nOPTIONS\n\n", Console::BOLD); - echo implode("\n\n", $options) . "\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); + foreach ($options as $name => $option) { + echo $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $option['type'], $option['default'], $option['comment']) . "\n\n"; } } - - 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; } /**