123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- <?php
- /**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
- namespace yii\console\controllers;
- use Yii;
- use yii\base\Application;
- use yii\base\InlineAction;
- use yii\console\Controller;
- use yii\console\Exception;
- use yii\helpers\Console;
- use yii\helpers\Inflector;
- /**
- * This command provides help information about console commands.
- *
- * This command displays the available command list in
- * the application or the detailed instructions about using
- * a specific command.
- *
- * This command can be used as follows on command line:
- *
- * ~~~
- * yii help [command name]
- * ~~~
- *
- * In the above, if the command name is not provided, all
- * available commands will be displayed.
- *
- * @property array $commands All available command names. This property is read-only.
- *
- * @author Qiang Xue <[email protected]>
- * @since 2.0
- */
- class HelpController extends Controller
- {
- /**
- * Displays available commands or the detailed information
- * about a particular command. For example,
- *
- * @param string $command The name of the command to show help about.
- * If not provided, all available commands will be displayed.
- * @return integer the exit status
- * @throws Exception if the command for help is unknown
- */
- public function actionIndex($command = null)
- {
- if ($command !== null) {
- $result = Yii::$app->createController($command);
- if ($result === false) {
- throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', [
- 'command' => $this->ansiFormat($command, Console::FG_YELLOW),
- ]));
- }
- list($controller, $actionID) = $result;
- $actions = $this->getActions($controller);
- if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
- $this->getActionHelp($controller, $actionID);
- } else {
- $this->getControllerHelp($controller);
- }
- } else {
- $this->getHelp();
- }
- }
- /**
- * Returns all available command names.
- * @return array all available command names
- */
- public function getCommands()
- {
- $commands = $this->getModuleCommands(Yii::$app);
- sort($commands);
- return array_unique($commands);
- }
- /**
- * Returns all available actions of the specified controller.
- * @param Controller $controller the controller instance
- * @return array all available action IDs.
- */
- public function getActions($controller)
- {
- $actions = array_keys($controller->actions());
- $class = new \ReflectionClass($controller);
- foreach ($class->getMethods() as $method) {
- $name = $method->getName();
- if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
- $actions[] = Inflector::camel2id(substr($name, 6));
- }
- }
- sort($actions);
- return array_unique($actions);
- }
- /**
- * Returns available commands of a specified module.
- * @param \yii\base\Module $module the module instance
- * @return array the available command names
- */
- protected function getModuleCommands($module)
- {
- $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
- $commands = [];
- foreach (array_keys($module->controllerMap) as $id) {
- $commands[] = $prefix . $id;
- }
- foreach ($module->getModules() as $id => $child) {
- if (($child = $module->getModule($id)) === null) {
- continue;
- }
- foreach ($this->getModuleCommands($child) as $command) {
- $commands[] = $command;
- }
- }
- $controllerPath = $module->getControllerPath();
- if (is_dir($controllerPath)) {
- $files = scandir($controllerPath);
- foreach ($files as $file) {
- if (strcmp(substr($file, -14), 'Controller.php') === 0) {
- $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
- }
- }
- }
- return $commands;
- }
- /**
- * Displays all available commands.
- */
- protected function getHelp()
- {
- $commands = $this->getCommands();
- if (!empty($commands)) {
- $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
- foreach ($commands as $command) {
- echo "- " . $this->ansiFormat($command, Console::FG_YELLOW) . "\n";
- }
- $scriptName = $this->getScriptName();
- $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
- echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
- . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n";
- } else {
- $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
- }
- }
- /**
- * Displays the overall information of the command.
- * @param Controller $controller the controller instance
- */
- protected function getControllerHelp($controller)
- {
- $class = new \ReflectionClass($controller);
- $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", '');
- if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
- $comment = trim(substr($comment, 0, $matches[0][1]));
- }
- if ($comment !== '') {
- $this->stdout("\nDESCRIPTION\n", Console::BOLD);
- echo "\n" . Console::renderColoredString($comment) . "\n\n";
- }
- $actions = $this->getActions($controller);
- if (!empty($actions)) {
- $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
- $prefix = $controller->getUniqueId();
- foreach ($actions as $action) {
- echo '- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW);
- if ($action === $controller->defaultAction) {
- $this->stdout(' (default)', Console::FG_GREEN);
- }
- $summary = $this->getActionSummary($controller, $action);
- if ($summary !== '') {
- echo ': ' . $summary;
- }
- echo "\n";
- }
- $scriptName = $this->getScriptName();
- echo "\nTo see the detailed information about individual sub-commands, enter:\n";
- echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
- . $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n";
- }
- }
- /**
- * Returns the short summary of the action.
- * @param Controller $controller the controller instance
- * @param string $actionID action ID
- * @return string the summary about the action
- */
- protected function getActionSummary($controller, $actionID)
- {
- $action = $controller->createAction($actionID);
- if ($action === null) {
- return '';
- }
- if ($action instanceof InlineAction) {
- $reflection = new \ReflectionMethod($controller, $action->actionMethod);
- } else {
- $reflection = new \ReflectionClass($action);
- }
- $tags = $this->parseComment($reflection->getDocComment());
- if ($tags['description'] !== '') {
- $limit = 73 - strlen($action->getUniqueId());
- if ($actionID === $controller->defaultAction) {
- $limit -= 10;
- }
- if ($limit < 0) {
- $limit = 50;
- }
- $description = $tags['description'];
- if (($pos = strpos($tags['description'], "\n")) !== false) {
- $description = substr($description, 0, $pos);
- }
- $text = substr($description, 0, $limit);
- return strlen($description) > $limit ? $text . '...' : $text;
- } else {
- return '';
- }
- }
- /**
- * Displays the detailed information of a command action.
- * @param Controller $controller the controller instance
- * @param string $actionID action ID
- * @throws Exception if the action does not exist
- */
- protected function getActionHelp($controller, $actionID)
- {
- $action = $controller->createAction($actionID);
- if ($action === null) {
- throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', [
- 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
- ]));
- }
- if ($action instanceof InlineAction) {
- $method = new \ReflectionMethod($controller, $action->actionMethod);
- } else {
- $method = new \ReflectionMethod($action, 'run');
- }
- $tags = $this->parseComment($method->getDocComment());
- $options = $this->getOptionHelps($controller);
- if ($tags['description'] !== '') {
- $this->stdout("\nDESCRIPTION\n", Console::BOLD);
- echo "\n" . Console::renderColoredString($tags['description']) . "\n\n";
- }
- $this->stdout("\nUSAGE\n\n", Console::BOLD);
- $scriptName = $this->getScriptName();
- if ($action->id === $controller->defaultAction) {
- echo $scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW);
- } else {
- echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
- }
- 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);
- }
- 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";
- }
- $options = $this->getOptionHelps($controller);
- 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);
- }
- }
- return [$required, $optional];
- }
- /**
- * Returns the help information about the options available for a console controller.
- * @param Controller $controller the console controller
- * @return array the help information about the options
- */
- protected function getOptionHelps($controller)
- {
- $optionNames = $controller->globalOptions();
- 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, '');
- }
- }
- 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;
- }
- /**
- * Generates a well-formed string for an argument or option.
- * @param string $name the name of the argument or option
- * @param boolean $required whether the argument is required
- * @param string $type the type of the option or argument
- * @param mixed $defaultValue the default value of the option or argument
- * @param string $comment comment about the option or argument
- * @return string the formatted string for the argument or option
- */
- protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
- {
- $doc = '';
- $comment = trim($comment);
- if ($defaultValue !== null && !is_array($defaultValue)) {
- if ($type === null) {
- $type = gettype($defaultValue);
- }
- if (is_bool($defaultValue)) {
- // show as integer to avoid confusion
- $defaultValue = (int)$defaultValue;
- }
- $doc = "$type (defaults to " . var_export($defaultValue, true) . ")";
- } elseif (trim($type) !== '') {
- $doc = $type;
- }
- if ($doc === '') {
- $doc = $comment;
- } elseif ($comment !== '') {
- $doc .= "\n" . preg_replace("/^/m", " ", $comment);
- }
- $name = $required ? "$name (required)" : $name;
- return $doc === '' ? $name : "$name: $doc";
- }
- /**
- * @return string the name of the cli script currently running.
- */
- protected function getScriptName()
- {
- return basename(Yii::$app->request->scriptFile);
- }
- }
|