lithium\console\command\Help

class

Get information about a particular class including methods, properties, and descriptions.

Source

class Help extends \lithium\console\Command {

	/**
	 * Auto run the help command.
	 *
	 * @param string $command Name of the command to return help about.
	 * @return boolean
	 */
	public function run($command = null) {
		if (!$command) {
			$this->_renderCommands();
			return true;
		}

		if (!preg_match('/\\\\/', $command)) {
			$command = Inflector::camelize($command);
		}

		if (!$class = Libraries::locate('command', $command)) {
			$this->error("Command `{$command}` not found");
			return false;
		}

		if (strpos($command, '\\') !== false) {
			$command = join('', array_slice(explode("\\", $command), -1));
		}
		$command = strtolower(Inflector::slug($command));

		$run = null;
		$methods = $this->_methods($class);
		$properties = $this->_properties($class);
		$info = Inspector::info($class);

		$this->out('USAGE', 'heading');

		if (isset($methods['run'])) {
			$run = $methods['run'];
			unset($methods['run']);
			$this->_renderUsage($command, $run, $properties);
		}
		foreach ($methods as $method) {
			$this->_renderUsage($command, $method);
		}
		$this->out();

		if (!empty($info['description'])) {
			$this->nl();
			$this->_renderDescription($info);
			$this->nl();
		}

		if ($properties || $methods) {
			$this->out('OPTIONS', 'heading');
		}
		if ($run) {
			$this->_render($run['args']);
		}
		if ($methods) {
			$this->_render($methods);
		}
		if ($properties) {
			$this->_render($properties);
		}
		$this->out();
		return true;
	}

	/**
	 * Gets the API for the class.
	 *
	 * @param string $class fully namespaced class in dot notation.
	 * @param string $type method|property
	 * @param string $name the name of the method or property.
	 * @return array
	 */
	public function api($class = null, $type = null, $name = null) {
		$class = str_replace(".", "\\", $class);

		switch ($type) {
			default:
				$info = Inspector::info($class);
				$result = ['class' => [
					'name' => $info['shortName'],
					'description' => trim($info['description'] . PHP_EOL . PHP_EOL . $info['text'])
				]];
			break;
			case 'method':
				$result = $this->_methods($class, compact('name'));
			break;
			case 'property':
				$result = $this->_properties($class, compact('name'));
			break;
		}
		$this->_render($result);
	}

	/**
	 * Get the methods for the class.
	 *
	 * @param string $class
	 * @param array $options
	 * @return array
	 */
	protected function _methods($class, $options = []) {
		$defaults = ['name' => null];
		$options += $defaults;

		$map = function($item) {
			if ($item->name[0] === '_') {
				return;
			}
			if ($setAccess = !$item->isPublic()) {
				$item->setAccessible(true);
			}
			$args = [];

			foreach ($item->getParameters() as $arg) {
				$args[] = [
					'name' => $arg->getName(),
					'optional' => $arg->isOptional(),
					'description' => null
				];
			}
			$result = compact('args') + [
				'docComment' => $item->getDocComment(),
				'name' => $item->getName()
			];
			if ($setAccess) {
				$item->setAccessible(false);
			}
			return $result;
		};

		$methods = Inspector::methods($class)->map($map, ['collect' => false]);
		$results = [];

		foreach (array_filter($methods) as $method) {
			$comment = Docblock::comment($method['docComment']);

			$name = $method['name'];
			$description = trim($comment['description'] . PHP_EOL . $comment['text']);
			$args = $method['args'];
			$return = null;

			foreach ($args as &$arg) {
				if (isset($comment['tags']['params']['$' . $arg['name']])) {
					$arg['description'] = $comment['tags']['params']['$' . $arg['name']]['text'];
				}
				$arg['usage'] = $arg['optional'] ? "[<{$arg['name']}>]" : "<{$arg['name']}>";
			}
			if (isset($comment['tags']['return'])) {
				$return = trim(strtok($comment['tags']['return'], ' '));
			}
			$results[$name] = compact('name', 'description', 'return', 'args');

			if ($name && $name == $options['name']) {
				return [$name => $results[$name]];
			}
		}
		return $results;
	}

	/**
	 * Get the properties for the class.
	 *
	 * @param string $class
	 * @param array $options
	 * @return array
	 */
	protected function _properties($class, $options = []) {
		$defaults = ['name' => null];
		$options += $defaults;

		$properties = Inspector::properties(new $class(['init' => false]), ['self' => false]);
		$results = [];

		foreach ($properties as &$property) {
			if ($property['name'] === 'request' || $property['name'] === 'response') {
				continue;
			}
			$name = str_replace('_', '-', Inflector::underscore($property['name']));

			$comment = Docblock::comment($property['docComment']);
			$description = trim($comment['description']);
			$type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null;

			$usage = strlen($name) == 1 ? "-{$name}" : "--{$name}";

			if ($type != 'boolean') {
				$usage .= "=<{$type}>";
			}
			$usage = "[{$usage}]";

			$results[$name] = compact('name', 'description', 'type', 'usage');

			if ($name == $options['name']) {
				return [$name => $results[$name]];
			}
		}
		return $results;
	}

	/**
	 * Output the formatted properties or methods.
	 *
	 * @see lithium\console\command\Help::_properties()
	 * @see lithium\console\command\Help::_methods()
	 * @param array $params From `_properties()` or `_methods()`.
	 * @return void
	 */
	protected function _render($params) {
		foreach ($params as $name => $param) {
			if ($name === 'run' || empty($param['name'])) {
				continue;
			}
			$usage = (!empty($param['usage'])) ? trim($param['usage'], ' []') : $param['name'];
			$this->out($this->_pad($usage), 'option');

			if ($param['description']) {
				$this->out($this->_pad($param['description'], 2));
			}
			$this->nl();
		}
	}

	/**
	 * Output the formatted available commands.
	 *
	 * @return void
	 */
	protected function _renderCommands() {
		$commands = Libraries::locate('command', null, ['recursive' => false]);

		foreach ($commands as $key => $command) {
			$library = strtok($command, '\\');

			if (!$key || strtok($commands[$key - 1] , '\\') != $library) {
				$this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}");
			}
			$info = Inspector::info($command);
			$name = strtolower(Inflector::slug($info['shortName']));

			$this->out($this->_pad($name) , 'heading');
			$this->out($this->_pad($info['description']), 2);
		}

		$message  = 'See `{:command}li3 help COMMAND{:end}`';
		$message .= ' for more information on a specific command.';
		$this->out($message, 2);
	}

	/**
	 * Output the formatted usage.
	 *
	 * @see lithium\console\command\Help::_methods()
	 * @see lithium\console\command\Help::_properties()
	 * @param string $command The name of the command.
	 * @param array $method Information about the method of the command to render usage for.
	 * @param array $properties From `_properties()`.
	 * @return void
	 */
	protected function _renderUsage($command, $method, $properties = []) {
		$params = array_reduce($properties, function($a, $b) {
			return "{$a} {$b['usage']}";
		});
		$args = array_reduce($method['args'], function($a, $b) {
			return "{$a} {$b['usage']}";
		});
		$format = "{:command}li3 %s%s{:end}{:command}%s{:end}{:option}%s{:end}";
		$name = $method['name'] == 'run' ? '' : " {$method['name']}";
		$this->out($this->_pad(sprintf($format, $command ?: 'COMMAND', $name, $params, $args)));
	}

	/**
	 * Output the formatted command description.
	 *
	 * @param array $info Info from inspecting the class of the command.
	 * @return void
	 */
	protected function _renderDescription($info) {
		$this->out('DESCRIPTION', 'heading');
		$break = PHP_EOL . PHP_EOL;
		$description = trim("{$info['description']}{$break}{$info['text']}");
		$this->out($this->_pad($description), 2);
	}

	/**
	 * Add left padding for prettier display.
	 *
	 * @param string $message the text to render.
	 * @param integer|string $level the level of indentation.
	 * @return string
	 */
	protected function _pad($message, $level = 1) {
		$padding = str_repeat(' ', $level * 4);
		return $padding . str_replace("\n", "\n{$padding}", $message);
	}
}