PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/library/Microsoft/Console/Command.php

#
PHP | 427 lines | 251 code | 46 blank | 130 comment | 41 complexity | 953a5461c6f993e7e4c41f9b93112041 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Copyright (c) 2009 - 2012, RealDolmen
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * * Neither the name of RealDolmen nor the
  14. * names of its contributors may be used to endorse or promote products
  15. * derived from this software without specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY RealDolmen ''AS IS'' AND ANY
  18. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. * DISCLAIMED. IN NO EVENT SHALL RealDolmen BE LIABLE FOR ANY
  21. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. *
  28. * @category Microsoft
  29. * @package Microsoft_Console
  30. * @version $Id: Exception.php 55733 2011-01-03 09:17:16Z unknown $
  31. * @copyright Copyright (c) 2009 - 2012, RealDolmen (http://www.realdolmen.com)
  32. * @license http://phpazure.codeplex.com/license
  33. */
  34. /**
  35. * @see Microsoft_AutoLoader
  36. */
  37. require_once dirname(__FILE__) . '/../AutoLoader.php';
  38. /**
  39. * @category Microsoft
  40. * @package Microsoft_Console
  41. * @copyright Copyright (c) 2009 - 2012, RealDolmen (http://www.realdolmen.com)
  42. * @license http://phpazure.codeplex.com/license
  43. */
  44. class Microsoft_Console_Command
  45. {
  46. /**
  47. * The handler.
  48. *
  49. * @var array
  50. */
  51. protected $_handler;
  52. /**
  53. * Gets the handler.
  54. *
  55. * @return array
  56. */
  57. public function getHandler()
  58. {
  59. return $this->_handler;
  60. }
  61. /**
  62. * Sets the handler.
  63. *
  64. * @param array $handler
  65. * @return Microsoft_Console_Command
  66. */
  67. public function setHandler($handler)
  68. {
  69. $this->_handler = $handler;
  70. return $this;
  71. }
  72. /**
  73. * Replaces PHP's error handler
  74. *
  75. * @param mixed $errno
  76. * @param mixed $errstr
  77. * @param mixed $errfile
  78. * @param mixed $errline
  79. */
  80. public static function phpstderr($errno, $errstr, $errfile, $errline)
  81. {
  82. self::stderr($errno . ': Error in ' . $errfile . ':' . $errline . ' - ' . $errstr);
  83. }
  84. /**
  85. * Replaces PHP's exception handler
  86. *
  87. * @param Exception $exception
  88. */
  89. public static function phpstdex($exception)
  90. {
  91. self::stderr('Error: ' . $exception->getMessage());
  92. }
  93. /**
  94. * Writes output to STDERR, followed by a newline (optional)
  95. *
  96. * @param string $errorMessage
  97. * @param string $newLine
  98. */
  99. public static function stderr($errorMessage, $newLine = true)
  100. {
  101. if (error_reporting() === 0) {
  102. return;
  103. }
  104. file_put_contents('php://stderr', $errorMessage . ($newLine ? "\r\n" : ''));
  105. }
  106. /**
  107. * Bootstrap the shell command.
  108. *
  109. * @param array $argv PHP argument values.
  110. */
  111. public static function bootstrap($argv)
  112. {
  113. // Abort bootstrapping depending on the MICROSOFT_CONSOLE_COMMAND_HOST constant.
  114. if (defined('MICROSOFT_CONSOLE_COMMAND_HOST') && strtolower(MICROSOFT_CONSOLE_COMMAND_HOST) != 'console') {
  115. return;
  116. }
  117. // Replace error handler
  118. set_error_handler(array('Microsoft_Console_Command', 'phpstderr'));
  119. set_exception_handler(array('Microsoft_Console_Command', 'phpstdex'));
  120. // Build the application model
  121. $model = self::_buildModel();
  122. // Find a class that corresponds to the $argv[0] script name
  123. $requiredHandlerName = str_replace('.bat', '', str_replace('.sh', '', str_replace('.php', '', strtolower(basename($argv[0])))));
  124. $handler = null;
  125. foreach ($model as $possibleHandler) {
  126. if ($possibleHandler->handler == strtolower($requiredHandlerName)) {
  127. $handler = $possibleHandler;
  128. break;
  129. }
  130. }
  131. if (is_null($handler)) {
  132. self::stderr("No class found that implements handler '" . $requiredHandlerName . "'. Create a class that is named '" . $requiredHandlerName . "' and extends Microsoft_Console_Command or is decorated with a docblock comment '@command-handler " . $requiredHandlerName . "'. Make sure it is loaded either through an autoloader or explicitly using require_once().");
  133. die();
  134. }
  135. // Find a method that matches the command name
  136. $command = null;
  137. foreach ($handler->commands as $possibleCommand) {
  138. if (in_array(strtolower(isset($argv[1]) ? $argv[1] : '<default>'), $possibleCommand->aliases)) {
  139. $command = $possibleCommand;
  140. break;
  141. }
  142. }
  143. if (is_null($command)) {
  144. $commandName = (isset($argv[1]) ? $argv[1] : '<default>');
  145. self::stderr("No method found that implements command " . $commandName . ". Create a method in class '" . $handler->class . "' that is named '" . strtolower($commandName) . "Command' or is decorated with a docblock comment '@command-name " . $commandName . "'.");
  146. die();
  147. }
  148. // Parse parameter values
  149. $parameterValues = array();
  150. $missingParameterValues = array();
  151. $parameterInputs = array_splice($argv, 2);
  152. foreach ($command->parameters as $parameter) {
  153. // Default value: null
  154. $value = null;
  155. // Consult value providers for value. First one wins.
  156. foreach ($parameter->valueproviders as $valueProviderName) {
  157. if (!class_exists($valueProviderName)) {
  158. $valueProviderName = 'Microsoft_Console_Command_ParameterSource_' . $valueProviderName;
  159. }
  160. $valueProvider = new $valueProviderName();
  161. $value = $valueProvider->getValueForParameter($parameter, $parameterInputs);
  162. if (!is_null($value)) {
  163. break;
  164. }
  165. }
  166. if (is_null($value) && $parameter->required) {
  167. $missingParameterValues[] = $parameter->aliases[0];
  168. } else if (is_null($value)) {
  169. $value = $parameter->defaultvalue;
  170. }
  171. // Set value
  172. $parameterValues[] = $value;
  173. $argvValues[$parameter->aliases[0]] = $value;
  174. }
  175. // Mising parameters?
  176. if (count($missingParameterValues) > 0) {
  177. self::stderr("Some parameters are missing:\r\n" . implode("\r\n", $missingParameterValues));
  178. die();
  179. }
  180. // Supply argv in a nice way
  181. $parameterValues['argv'] = $parameterInputs;
  182. // Run the command
  183. $className = $handler->class;
  184. $classInstance = new $className();
  185. $classInstance->setHandler($handler);
  186. call_user_func_array(array($classInstance, $command->method), $parameterValues);
  187. // Restore error handler
  188. restore_exception_handler();
  189. restore_error_handler();
  190. }
  191. /**
  192. * Builds the handler model.
  193. *
  194. * @return array
  195. */
  196. protected static function _buildModel()
  197. {
  198. $model = array();
  199. $classes = get_declared_classes();
  200. foreach ($classes as $class) {
  201. $type = new ReflectionClass($class);
  202. $handlers = self::_findValueForDocComment('@command-handler', $type->getDocComment());
  203. if (count($handlers) == 0 && $type->isSubclassOf('Microsoft_Console_Command')) {
  204. // Fallback: if the class extends Microsoft_Console_Command, register it as
  205. // a command handler.
  206. $handlers[] = $class;
  207. }
  208. $handlerDescriptions = self::_findValueForDocComment('@command-handler-description', $type->getDocComment());
  209. $handlerHeaders = self::_findValueForDocComment('@command-handler-header', $type->getDocComment());
  210. $handlerFooters = self::_findValueForDocComment('@command-handler-footer', $type->getDocComment());
  211. for ($hi = 0; $hi < count($handlers); $hi++) {
  212. $handler = $handlers[$hi];
  213. $handlerDescription = isset($handlerDescriptions[$hi]) ? $handlerDescriptions[$hi] : isset($handlerDescriptions[0]) ? $handlerDescriptions[0] : '';
  214. $handlerDescription = str_replace('\r\n', "\r\n", $handlerDescription);
  215. $handlerDescription = str_replace('\n', "\n", $handlerDescription);
  216. $handlerModel = (object)array(
  217. 'handler' => strtolower($handler),
  218. 'description' => $handlerDescription,
  219. 'headers' => $handlerHeaders,
  220. 'footers' => $handlerFooters,
  221. 'class' => $class,
  222. 'commands' => array()
  223. );
  224. $methods = $type->getMethods();
  225. foreach ($methods as $method) {
  226. $commands = self::_findValueForDocComment('@command-name', $method->getDocComment());
  227. if (substr($method->getName(), -7) == 'Command' && !in_array(substr($method->getName(), 0, -7), $commands)) {
  228. // Fallback: if the method is named <commandname>Command,
  229. // register it as a command.
  230. $commands[] = substr($method->getName(), 0, -7);
  231. }
  232. for ($x = 0; $x < count($commands); $x++) {
  233. $commands[$x] = strtolower($commands[$x]);
  234. }
  235. $commands = array_unique($commands);
  236. $commandDescriptions = self::_findValueForDocComment('@command-description', $method->getDocComment());
  237. $commandExamples = self::_findValueForDocComment('@command-example', $method->getDocComment());
  238. if (count($commands) > 0) {
  239. $command = $commands[0];
  240. $commandDescription = isset($commandDescriptions[0]) ? $commandDescriptions[0] : '';
  241. $commandModel = (object)array(
  242. 'command' => $command,
  243. 'aliases' => $commands,
  244. 'description' => $commandDescription,
  245. 'examples' => $commandExamples,
  246. 'class' => $class,
  247. 'method' => $method->getName(),
  248. 'parameters' => array()
  249. );
  250. $parameters = $method->getParameters();
  251. $parametersFor = self::_findValueForDocComment('@command-parameter-for', $method->getDocComment());
  252. for ($pi = 0; $pi < count($parameters); $pi++) {
  253. // Initialize
  254. $parameter = $parameters[$pi];
  255. $parameterFor = null;
  256. $parameterForDefaultValue = null;
  257. // Is it a "catch-all" parameter?
  258. if ($parameter->getName() == 'argv') {
  259. continue;
  260. }
  261. // Find the $parametersFor with the same name defined
  262. foreach ($parametersFor as $possibleParameterFor) {
  263. $possibleParameterFor = explode(' ', $possibleParameterFor, 4);
  264. if ($possibleParameterFor[0] == '$' . $parameter->getName()) {
  265. $parameterFor = $possibleParameterFor;
  266. break;
  267. }
  268. }
  269. if (is_null($parameterFor)) {
  270. die('@command-parameter-for missing for parameter $' . $parameter->getName());
  271. }
  272. if (is_null($parameterForDefaultValue) && $parameter->isOptional()) {
  273. $parameterForDefaultValue = $parameter->getDefaultValue();
  274. }
  275. $parameterModel = (object)array(
  276. 'name' => '$' . $parameter->getName(),
  277. 'defaultvalue' => $parameterForDefaultValue,
  278. 'valueproviders' => explode('|', $parameterFor[1]),
  279. 'aliases' => explode('|', $parameterFor[2]),
  280. 'description' => (isset($parameterFor[3]) ? $parameterFor[3] : ''),
  281. 'required' => (isset($parameterFor[3]) ? strpos(strtolower($parameterFor[3]), 'required') !== false && strpos(strtolower($parameterFor[3]), 'required if') === false : false),
  282. );
  283. // Add to model
  284. $commandModel->parameters[] = $parameterModel;
  285. }
  286. // Add to model
  287. $handlerModel->commands[] = $commandModel;
  288. }
  289. }
  290. // Add to model
  291. $model[] = $handlerModel;
  292. }
  293. }
  294. return $model;
  295. }
  296. /**
  297. * Finds the value for a specific docComment.
  298. *
  299. * @param string $docCommentName Comment name
  300. * @param unknown_type $docComment Comment object
  301. * @return array
  302. */
  303. protected static function _findValueForDocComment($docCommentName, $docComment)
  304. {
  305. $returnValue = array();
  306. $commentLines = explode("\n", $docComment);
  307. foreach ($commentLines as $commentLine) {
  308. if (strpos($commentLine, $docCommentName . ' ') !== false) {
  309. $returnValue[] = trim(substr($commentLine, strpos($commentLine, $docCommentName) + strlen($docCommentName) + 1));
  310. }
  311. }
  312. return $returnValue;
  313. }
  314. /**
  315. * Display information on an object
  316. *
  317. * @param object $object Object
  318. * @param array $propertiesToDump Property names to display
  319. */
  320. protected function _displayObjectInformation($object, $propertiesToDump = array())
  321. {
  322. foreach ($propertiesToDump as $property) {
  323. printf('%-16s: %s' . "\r\n", $property, $object->$property);
  324. }
  325. printf("\r\n");
  326. }
  327. /**
  328. * Displays the help information.
  329. *
  330. * @command-name <default>
  331. * @command-name -h
  332. * @command-name -help
  333. * @command-description Displays the current help information.
  334. */
  335. public function helpCommand() {
  336. $handler = $this->getHandler();
  337. $newline = "\r\n";
  338. if (count($handler->headers) > 0) {
  339. foreach ($handler->headers as $header) {
  340. printf('%s%s', $header, $newline);
  341. }
  342. printf($newline);
  343. }
  344. printf('%s%s', $handler->description, $newline);
  345. printf($newline);
  346. printf('Available commands:%s', $newline);
  347. foreach ($handler->commands as $command) {
  348. $description = str_split($command->description, 50);
  349. printf(' %-25s %s%s', implode(', ', $command->aliases), $description[0], $newline);
  350. for ($di = 1; $di < count($description); $di++) {
  351. printf(' %-25s %s%s', '', $description[$di], $newline);
  352. }
  353. printf($newline);
  354. if (count($command->parameters) > 0) {
  355. foreach ($command->parameters as $parameter) {
  356. $description = str_split($parameter->description, 50);
  357. printf(' %-23s %s%s', implode(', ', $parameter->aliases), $description[0], $newline);
  358. for ($di = 1; $di < count($description); $di++) {
  359. printf(' %-23s %s%s', '', $description[$di], $newline);
  360. }
  361. printf($newline);
  362. }
  363. }
  364. printf($newline);
  365. if (count($command->examples) > 0) {
  366. printf(' Example usage:%s', $newline);
  367. foreach ($command->examples as $example) {
  368. printf(' %s%s', $example, $newline);
  369. }
  370. printf($newline);
  371. }
  372. }
  373. if (count($handler->footers) > 0) {
  374. printf($newline);
  375. foreach ($handler->footers as $footer) {
  376. printf('%s%s', $footer, $newline);
  377. }
  378. printf($newline);
  379. }
  380. }
  381. }