Help.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\console\command;
  9. use lithium\core\Libraries;
  10. use lithium\core\Environment;
  11. use lithium\util\Inflector;
  12. use lithium\analysis\Inspector;
  13. use lithium\analysis\Docblock;
  14. /**
  15. * Get information about a particular class including methods, properties,
  16. * and descriptions.
  17. */
  18. class Help extends \lithium\console\Command {
  19. /**
  20. * Auto run the help command.
  21. *
  22. * @param string $command Name of the command to return help about.
  23. * @return void
  24. */
  25. public function run($command = null) {
  26. $message = 'Lithium console started in the ' . Environment::get() . ' environment.';
  27. $message .= ' Use the --env=environment key to alter this.';
  28. $this->out($message);
  29. if (!$command) {
  30. $this->_renderCommands();
  31. return true;
  32. }
  33. if (!preg_match('/\\\\/', $command)) {
  34. $command = Inflector::camelize($command);
  35. }
  36. if (!$class = Libraries::locate('command', $command)) {
  37. $this->error("Command `{$command}` not found");
  38. return false;
  39. }
  40. if (strpos($command, '\\') !== false) {
  41. $command = join('', array_slice(explode("\\", $command), -1));
  42. }
  43. $command = strtolower(Inflector::slug($command));
  44. $run = null;
  45. $methods = $this->_methods($class);
  46. $properties = $this->_properties($class);
  47. $info = Inspector::info($class);
  48. $this->out('USAGE', 'heading');
  49. if (isset($methods['run'])) {
  50. $run = $methods['run'];
  51. unset($methods['run']);
  52. $this->_renderUsage($command, $run, $properties);
  53. }
  54. foreach ($methods as $method) {
  55. $this->_renderUsage($command, $method);
  56. }
  57. if (!empty($info['description'])) {
  58. $this->nl();
  59. $this->_renderDescription($info);
  60. $this->nl();
  61. }
  62. if ($properties || $methods) {
  63. $this->out('OPTIONS', 'heading');
  64. }
  65. if ($run) {
  66. $this->_render($run['args']);
  67. }
  68. if ($methods) {
  69. $this->_render($methods);
  70. }
  71. if ($properties) {
  72. $this->_render($properties);
  73. }
  74. return true;
  75. }
  76. /**
  77. * Gets the API for the class.
  78. *
  79. * @param string $class fully namespaced class in dot notation.
  80. * @param string $type method|property
  81. * @param string $name the name of the method or property.
  82. * @return array
  83. */
  84. public function api($class = null, $type = null, $name = null) {
  85. $class = str_replace(".", "\\", $class);
  86. switch ($type) {
  87. default:
  88. $info = Inspector::info($class);
  89. $result = array('class' => array(
  90. 'name' => $info['shortName'],
  91. 'description' => trim($info['description'] . PHP_EOL . PHP_EOL . $info['text'])
  92. ));
  93. break;
  94. case 'method':
  95. $result = $this->_methods($class, compact('name'));
  96. break;
  97. case 'property':
  98. $result = $this->_properties($class, compact('name'));
  99. break;
  100. }
  101. $this->_render($result);
  102. }
  103. /**
  104. * Get the methods for the class.
  105. *
  106. * @param string $class
  107. * @param array $options
  108. * @return array
  109. */
  110. protected function _methods($class, $options = array()) {
  111. $defaults = array('name' => null);
  112. $options += $defaults;
  113. $map = function($item) {
  114. if ($item->name[0] === '_') {
  115. return;
  116. }
  117. $modifiers = array_values(Inspector::invokeMethod('_modifiers', array($item)));
  118. $setAccess = array_intersect($modifiers, array('private', 'protected')) != array();
  119. if ($setAccess) {
  120. $item->setAccessible(true);
  121. }
  122. $args = array();
  123. foreach ($item->getParameters() as $arg) {
  124. $args[] = array(
  125. 'name' => $arg->getName(),
  126. 'optional' => $arg->isOptional(),
  127. 'description' => null
  128. );
  129. }
  130. $result = compact('modifiers', 'args') + array(
  131. 'docComment' => $item->getDocComment(),
  132. 'name' => $item->getName()
  133. );
  134. if ($setAccess) {
  135. $item->setAccessible(false);
  136. }
  137. return $result;
  138. };
  139. $methods = Inspector::methods($class)->map($map, array('collect' => false));
  140. $results = array();
  141. foreach (array_filter($methods) as $method) {
  142. $comment = Docblock::comment($method['docComment']);
  143. $name = $method['name'];
  144. $description = trim($comment['description'] . PHP_EOL . $comment['text']);
  145. $args = $method['args'];
  146. $return = null;
  147. foreach ($args as &$arg) {
  148. if (isset($comment['tags']['params']['$' . $arg['name']])) {
  149. $arg['description'] = $comment['tags']['params']['$' . $arg['name']]['text'];
  150. }
  151. $arg['usage'] = $arg['optional'] ? "[<{$arg['name']}>]" : "<{$arg['name']}>";
  152. }
  153. if (isset($comment['tags']['return'])) {
  154. $return = trim(strtok($comment['tags']['return'], ' '));
  155. }
  156. $results[$name] = compact('name', 'description', 'return', 'args');
  157. if ($name && $name == $options['name']) {
  158. return array($name => $results[$name]);
  159. }
  160. }
  161. return $results;
  162. }
  163. /**
  164. * Get the properties for the class.
  165. *
  166. * @param string $class
  167. * @param array $options
  168. * @return array
  169. */
  170. protected function _properties($class, $options = array()) {
  171. $defaults = array('name' => null);
  172. $options += $defaults;
  173. $properties = Inspector::properties($class);
  174. $results = array();
  175. foreach ($properties as &$property) {
  176. $name = str_replace('_', '-', Inflector::underscore($property['name']));
  177. $comment = Docblock::comment($property['docComment']);
  178. $description = trim($comment['description']);
  179. $type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null;
  180. $usage = strlen($name) == 1 ? "-{$name}" : "--{$name}";
  181. if ($type != 'boolean') {
  182. $usage .= "=<{$type}>";
  183. }
  184. $usage = "[{$usage}]";
  185. $results[$name] = compact('name', 'description', 'type', 'usage');
  186. if ($name == $options['name']) {
  187. return array($name => $results[$name]);
  188. }
  189. }
  190. return $results;
  191. }
  192. /**
  193. * Output the formatted properties or methods.
  194. *
  195. * @see lithium\console\command\Help::_properties()
  196. * @see lithium\console\command\Help::_methods()
  197. * @param array $params From `_properties()` or `_methods()`.
  198. * @return void
  199. */
  200. protected function _render($params) {
  201. foreach ($params as $name => $param) {
  202. if ($name === 'run' || empty($param['name'])) {
  203. continue;
  204. }
  205. $usage = (!empty($param['usage'])) ? trim($param['usage'], ' []') : $param['name'];
  206. $this->out($this->_pad($usage), 'option');
  207. if ($param['description']) {
  208. $this->out($this->_pad($param['description'], 2));
  209. }
  210. $this->nl();
  211. }
  212. }
  213. /**
  214. * Output the formatted available commands.
  215. *
  216. * @return void
  217. */
  218. protected function _renderCommands() {
  219. $commands = Libraries::locate('command', null, array('recursive' => false));
  220. foreach ($commands as $key => $command) {
  221. $library = strtok($command, '\\');
  222. if (!$key || strtok($commands[$key - 1] , '\\') != $library) {
  223. $this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}");
  224. }
  225. $info = Inspector::info($command);
  226. $name = strtolower(Inflector::slug($info['shortName']));
  227. $this->out($this->_pad($name) , 'heading');
  228. $this->out($this->_pad($info['description']), 2);
  229. }
  230. $message = 'See `{:command}li3 help COMMAND{:end}`';
  231. $message .= ' for more information on a specific command.';
  232. $this->out($message, 2);
  233. }
  234. /**
  235. * Output the formatted usage.
  236. *
  237. * @see lithium\console\command\Help::_methods()
  238. * @see lithium\console\command\Help::_properties()
  239. * @param string $command The name of the command.
  240. * @param array $method Information about the method of the command to render usage for.
  241. * @param array $properties From `_properties()`.
  242. * @return void
  243. */
  244. protected function _renderUsage($command, $method, $properties = array()) {
  245. $params = array_reduce($properties, function($a, $b) {
  246. return "{$a} {$b['usage']}";
  247. });
  248. $args = array_reduce($method['args'], function($a, $b) {
  249. return "{$a} {$b['usage']}";
  250. });
  251. $format = "{:command}li3 %s%s{:end}{:command}%s{:end}{:option}%s{:end}";
  252. $name = $method['name'] == 'run' ? '' : " {$method['name']}";
  253. $this->out($this->_pad(sprintf($format, $command ?: 'COMMAND', $name, $params, $args)));
  254. }
  255. /**
  256. * Output the formatted command description.
  257. *
  258. * @param array $info Info from inspecting the class of the command.
  259. * @return void
  260. */
  261. protected function _renderDescription($info) {
  262. $this->out('DESCRIPTION', 'heading');
  263. $break = PHP_EOL . PHP_EOL;
  264. $description = trim("{$info['description']}{$break}{$info['text']}");
  265. $this->out($this->_pad($description, PHP_EOL));
  266. }
  267. /**
  268. * Add left padding for prettier display.
  269. *
  270. * @param string $message the text to render.
  271. * @param integer|string $level the level of indentation.
  272. * @return string
  273. */
  274. protected function _pad($message, $level = 1) {
  275. $padding = str_repeat(' ', $level * 4);
  276. return $padding . str_replace("\n", "\n{$padding}", $message);
  277. }
  278. }
  279. ?>