You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

429 lines
13 KiB

13 years ago
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
13 years ago
* @license http://www.yiiframework.com/license/
*/
namespace yii\console\controllers;
13 years ago
use Yii;
use yii\base\Application;
use yii\base\InlineAction;
use yii\console\Controller;
use yii\console\Exception;
use yii\helpers\Console;
12 years ago
use yii\helpers\Inflector;
13 years ago
/**
* This command provides help information about console commands.
13 years ago
*
* This command displays the available command list in
* the application or the detailed instructions about using
* a specific command.
13 years ago
*
* This command can be used as follows on command line:
13 years ago
*
* ~~~
* yii help [command name]
13 years ago
* ~~~
*
* In the above, if the command name is not provided, all
* available commands will be displayed.
13 years ago
*
* @property array $commands All available command names. This property is read-only.
*
13 years ago
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class HelpController extends Controller
13 years ago
{
/**
* Displays available commands or the detailed information
* about a particular command. For example,
*
* ~~~
* yii help # list available commands
* yii help message # display help info about "message"
* ~~~
*
12 years ago
* @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)
13 years ago
{
12 years ago
if ($command !== null) {
$result = Yii::$app->createController($command);
if ($result === false) {
throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array(
'command' => $this->ansiFormat($command, Console::FG_YELLOW),
)));
}
12 years ago
list($controller, $actionID) = $result;
12 years ago
$actions = $this->getActions($controller);
if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
$this->getActionHelp($controller, $actionID);
12 years ago
} 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)
{
12 years ago
$actions = array_keys($controller->actions());
$class = new \ReflectionClass($controller);
foreach ($class->getMethods() as $method) {
$name = $method->getName();
12 years ago
if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
12 years ago
$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)
{
12 years ago
$prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
$commands = array();
12 years ago
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[] = $prefix . $id . '/' . $command;
}
}
$files = scandir($module->getControllerPath());
foreach ($files as $file) {
if (strcmp(substr($file, -14), 'Controller.php') === 0) {
12 years ago
$commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
}
}
return $commands;
}
/**
* Displays all available commands.
*/
protected function getHelp()
{
13 years ago
$commands = $this->getCommands();
if (!empty($commands)) {
$this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
13 years ago
foreach ($commands as $command) {
echo "- " . $this->ansiFormat($command, Console::FG_YELLOW) . "\n";
13 years ago
}
$this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
echo "\n yii " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
. $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n";
13 years ago
} else {
$this->stdout("\nNo commands are found.\n\n", Console::BOLD);
13 years ago
}
}
13 years ago
/**
* Displays the overall information of the command.
* @param Controller $controller the controller instance
*/
protected function getControllerHelp($controller)
13 years ago
{
$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);
12 years ago
if ($action === $controller->defaultAction) {
$this->stdout(' (default)', Console::FG_GREEN);
12 years ago
}
$summary = $this->getActionSummary($controller, $action);
if ($summary !== '') {
echo ': ' . $summary;
}
echo "\n";
}
echo "\nTo see the detailed information about individual sub-commands, enter:\n";
echo "\n yii " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
. $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n";
}
13 years ago
}
13 years ago
/**
* 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
13 years ago
*/
protected function getActionHelp($controller, $actionID)
13 years ago
{
$action = $controller->createAction($actionID);
if ($action === null) {
throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array(
'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);
12 years ago
if ($tags['description'] !== '') {
$this->stdout("\nDESCRIPTION\n", Console::BOLD);
echo "\n" . Console::renderColoredString($tags['description']) . "\n\n";
12 years ago
}
$this->stdout("\nUSAGE\n\n", Console::BOLD);
if ($action->id === $controller->defaultAction) {
echo 'yii ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW);
} else {
echo 'yii ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
}
12 years ago
list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : array());
foreach ($required as $arg => $description) {
$this->stdout(' <' . $arg . '>', Console::FG_CYAN);
13 years ago
}
foreach ($optional as $arg => $description) {
$this->stdout(' [' . $arg . ']', Console::FG_CYAN);
}
if (!empty($options)) {
$this->stdout(' [...options...]', Console::FG_RED);
}
12 years ago
echo "\n\n";
12 years ago
if (!empty($required) || !empty($optional)) {
echo implode("\n\n", array_merge($required, $optional)) . "\n\n";
}
13 years ago
12 years ago
$options = $this->getOptionHelps($controller);
if (!empty($options)) {
$this->stdout("\nOPTIONS\n\n", Console::BOLD);
echo implode("\n\n", $options) . "\n\n";
}
}
/**
12 years ago
* Returns the help information about arguments.
* @param \ReflectionMethod $method
12 years ago
* @param string $tags the parsed comment block related with arguments
* @return array the required and optional argument help information
*/
12 years ago
protected function getArgHelps($method, $tags)
{
if (is_string($tags)) {
$tags = array($tags);
}
$params = $method->getParameters();
$optional = $required = array();
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);
}
}
12 years ago
return array($required, $optional);
}
/**
12 years ago
* 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
*/
12 years ago
protected function getOptionHelps($controller)
{
$optionNames = $controller->globalOptions();
if (empty($optionNames)) {
return array();
}
$class = new \ReflectionClass($controller);
$options = array();
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;
13 years ago
}
12 years ago
/**
* Parses the comment block into tags.
* @param string $comment the comment block
* @return array the parsed tags
*/
protected function parseComment($comment)
{
$tags = array();
$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] = array($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;
}
12 years ago
$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";
}
}