PageRenderTime 196ms CodeModel.GetById 120ms app.highlight 18ms RepoModel.GetById 53ms app.codeStats 0ms

/vendor/Microsoft/Console/Command.php

https://bitbucket.org/ktos/tinyshare
PHP | 427 lines | 251 code | 46 blank | 130 comment | 41 complexity | 7230d27eefabc38e49c84955020adc2f MD5 | raw file
  1<?php
  2/**
  3 * Copyright (c) 2009 - 2011, 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 - 2011, RealDolmen (http://www.realdolmen.com)
 32 * @license    http://phpazure.codeplex.com/license
 33 */
 34
 35/**
 36 * @see Microsoft_AutoLoader
 37 */
 38require_once dirname(__FILE__) . '/../AutoLoader.php';
 39
 40/**
 41 * @category   Microsoft
 42 * @package    Microsoft_Console
 43 * @copyright  Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com)
 44 * @license    http://phpazure.codeplex.com/license
 45 */
 46class Microsoft_Console_Command
 47{
 48	/**
 49	 * The handler.
 50	 *
 51	 * @var array
 52	 */
 53	protected $_handler;
 54	
 55	/**
 56	 * Gets the handler.
 57	 * 
 58	 * @return array
 59	 */
 60	public function getHandler()
 61	{
 62		return $this->_handler;
 63	}
 64	
 65	/**
 66	 * Sets the handler.
 67	 * 
 68	 * @param array $handler
 69	 * @return Microsoft_Console_Command
 70	 */
 71	public function setHandler($handler)
 72	{
 73		$this->_handler = $handler;
 74		return $this;
 75	}
 76	
 77	/**
 78	 * Replaces PHP's error handler
 79	 * 
 80	 * @param mixed $errno
 81	 * @param mixed $errstr
 82	 * @param mixed $errfile
 83	 * @param mixed $errline
 84	 */
 85	public static function phpstderr($errno, $errstr, $errfile, $errline)
 86	{
 87		self::stderr($errno . ': Error in ' . $errfile . ':' . $errline . ' - ' . $errstr);
 88	}
 89	
 90	/**
 91	 * Replaces PHP's exception handler
 92	 * 
 93	 * @param Exception $exception
 94	 */
 95	public static function phpstdex($exception)
 96	{
 97		self::stderr('Error: ' . $exception->getMessage());
 98	}
 99	
100	/**
101	 * Writes output to STDERR, followed by a newline (optional)
102	 * 
103	 * @param string $errorMessage
104	 * @param string $newLine
105	 */
106	public static function stderr($errorMessage, $newLine = true)
107	{
108		if (error_reporting() === 0) {
109			return;
110		}
111		file_put_contents('php://stderr', $errorMessage . ($newLine ? "\r\n" : ''));
112	}
113	
114	/**
115	 * Bootstrap the shell command.
116	 * 
117	 * @param array $argv PHP argument values.
118	 */
119	public static function bootstrap($argv)
120	{
121		// Abort bootstrapping depending on the MICROSOFT_CONSOLE_COMMAND_HOST constant.
122		if (defined('MICROSOFT_CONSOLE_COMMAND_HOST') && strtolower(MICROSOFT_CONSOLE_COMMAND_HOST) != 'console') {
123			return;
124		}
125		
126		// Replace error handler
127		set_error_handler(array('Microsoft_Console_Command', 'phpstderr'));
128		set_exception_handler(array('Microsoft_Console_Command', 'phpstdex'));
129		
130		// Build the application model
131		$model = self::_buildModel();
132		
133		// Find a class that corresponds to the $argv[0] script name
134		$requiredHandlerName = str_replace('.bat', '', str_replace('.sh', '', str_replace('.php', '', strtolower(basename($argv[0])))));
135		$handler = null;
136		foreach ($model as $possibleHandler) {
137			if ($possibleHandler->handler == strtolower($requiredHandlerName)) {
138				$handler = $possibleHandler;
139				break;
140			}
141		}
142		if (is_null($handler)) {
143			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().");
144			die();
145		}
146		
147		// Find a method that matches the command name
148		$command = null;
149		foreach ($handler->commands as $possibleCommand) {
150			if (in_array(strtolower(isset($argv[1]) ? $argv[1] : '<default>'), $possibleCommand->aliases)) {
151				$command = $possibleCommand;
152				break;
153			}
154		}
155		if (is_null($command)) {
156			$commandName = (isset($argv[1]) ? $argv[1] : '<default>');
157			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 . "'.");
158			die();
159		}
160
161		// Parse parameter values
162		$parameterValues = array();
163		$missingParameterValues = array();
164		$parameterInputs = array_splice($argv, 2);
165		foreach ($command->parameters as $parameter) {
166			// Default value: null
167			$value = null;
168			
169			// Consult value providers for value. First one wins.
170			foreach ($parameter->valueproviders as $valueProviderName) {
171				if (!class_exists($valueProviderName)) {
172					$valueProviderName = 'Microsoft_Console_Command_ParameterSource_' . $valueProviderName;
173				}
174				$valueProvider = new $valueProviderName();
175				
176				$value = $valueProvider->getValueForParameter($parameter, $parameterInputs);
177				if (!is_null($value)) {
178					break;
179				}
180			}
181			if (is_null($value) && $parameter->required) {
182				$missingParameterValues[] = $parameter->aliases[0];
183			} else if (is_null($value)) {
184				$value = $parameter->defaultvalue;
185			}
186			
187			// Set value
188			$parameterValues[] = $value;
189			$argvValues[$parameter->aliases[0]] = $value;
190		}
191
192		// Mising parameters?
193		if (count($missingParameterValues) > 0) {
194			self::stderr("Some parameters are missing:\r\n" . implode("\r\n", $missingParameterValues));
195			die();
196		}
197		
198		// Supply argv in a nice way
199		$parameterValues['argv'] = $parameterInputs;
200		
201		// Run the command
202		$className = $handler->class;
203		$classInstance = new $className();
204		$classInstance->setHandler($handler);
205		call_user_func_array(array($classInstance, $command->method), $parameterValues);
206		
207		// Restore error handler
208		restore_exception_handler();
209		restore_error_handler();
210	}
211	
212	/**
213	 * Builds the handler model.
214	 * 
215	 * @return array
216	 */
217	protected static function _buildModel()
218	{
219		$model = array();
220		
221		$classes = get_declared_classes();
222		foreach ($classes as $class) {
223			$type = new ReflectionClass($class);
224			
225			$handlers = self::_findValueForDocComment('@command-handler', $type->getDocComment());
226			if (count($handlers) == 0 && $type->isSubclassOf('Microsoft_Console_Command')) {
227				// Fallback: if the class extends Microsoft_Console_Command, register it as
228				// a command handler.
229				$handlers[] = $class; 
230			}
231			$handlerDescriptions = self::_findValueForDocComment('@command-handler-description', $type->getDocComment());
232			$handlerHeaders = self::_findValueForDocComment('@command-handler-header', $type->getDocComment());
233			$handlerFooters = self::_findValueForDocComment('@command-handler-footer', $type->getDocComment());
234			
235			for ($hi = 0; $hi < count($handlers); $hi++) {
236				$handler = $handlers[$hi];
237				$handlerDescription = isset($handlerDescriptions[$hi]) ? $handlerDescriptions[$hi] : isset($handlerDescriptions[0]) ? $handlerDescriptions[0] : '';
238				$handlerDescription = str_replace('\r\n', "\r\n", $handlerDescription);
239				$handlerDescription = str_replace('\n', "\n", $handlerDescription);
240				
241				$handlerModel = (object)array(
242					'handler'     => strtolower($handler),
243					'description' => $handlerDescription,
244					'headers'     => $handlerHeaders,
245					'footers'     => $handlerFooters,
246					'class'       => $class,
247					'commands'    => array()
248				);
249					
250				$methods = $type->getMethods();
251			    foreach ($methods as $method) {
252			       	$commands = self::_findValueForDocComment('@command-name', $method->getDocComment());
253			    	if (substr($method->getName(), -7) == 'Command' && !in_array(substr($method->getName(), 0, -7), $commands)) {
254						// Fallback: if the method is named <commandname>Command,
255						// register it as a command.
256						$commands[] = substr($method->getName(), 0, -7); 
257					}
258			       	for ($x = 0; $x < count($commands); $x++) {
259			       		$commands[$x] = strtolower($commands[$x]);
260			       	}
261			       	$commands = array_unique($commands);
262			       	$commandDescriptions = self::_findValueForDocComment('@command-description', $method->getDocComment());
263			       	$commandExamples = self::_findValueForDocComment('@command-example', $method->getDocComment());
264			       	
265			       	if (count($commands) > 0) {
266						$command = $commands[0];
267						$commandDescription = isset($commandDescriptions[0]) ? $commandDescriptions[0] : '';
268								
269						$commandModel = (object)array(
270							'command'     => $command,
271							'aliases'     => $commands,
272							'description' => $commandDescription,
273							'examples'    => $commandExamples,
274							'class'       => $class,
275							'method'      => $method->getName(),
276							'parameters'  => array()
277						);
278
279						$parameters = $method->getParameters();
280						$parametersFor = self::_findValueForDocComment('@command-parameter-for', $method->getDocComment());
281						for ($pi = 0; $pi < count($parameters); $pi++) {
282							// Initialize
283							$parameter = $parameters[$pi];
284							$parameterFor = null;
285							$parameterForDefaultValue = null;
286							
287							// Is it a "catch-all" parameter?
288							if ($parameter->getName() == 'argv') {
289								continue;
290							}
291							
292							// Find the $parametersFor with the same name defined
293							foreach ($parametersFor as $possibleParameterFor) {
294								$possibleParameterFor = explode(' ', $possibleParameterFor, 4);
295								if ($possibleParameterFor[0] == '$' . $parameter->getName()) {
296									$parameterFor = $possibleParameterFor;
297									break;
298								}
299							}
300							if (is_null($parameterFor)) {
301								die('@command-parameter-for missing for parameter $' . $parameter->getName());	
302							}
303							
304							if (is_null($parameterForDefaultValue) && $parameter->isOptional()) {
305								$parameterForDefaultValue = $parameter->getDefaultValue();
306							}
307							
308							$parameterModel = (object)array(
309								'name'           => '$' . $parameter->getName(),
310								'defaultvalue'   => $parameterForDefaultValue,
311								'valueproviders' => explode('|', $parameterFor[1]),
312								'aliases'        => explode('|', $parameterFor[2]),
313								'description'    => (isset($parameterFor[3]) ? $parameterFor[3] : ''),
314								'required'       => (isset($parameterFor[3]) ? strpos(strtolower($parameterFor[3]), 'required') !== false && strpos(strtolower($parameterFor[3]), 'required if') === false : false),
315							);
316							
317							// Add to model
318							$commandModel->parameters[] = $parameterModel;
319						}
320
321						// Add to model
322						$handlerModel->commands[] = $commandModel;
323			       	}
324				}
325					
326				// Add to model
327				$model[] = $handlerModel;
328			}
329		}
330	
331		return $model;
332	}
333	
334	/**
335	 * Finds the value for a specific docComment.
336	 * 
337	 * @param string $docCommentName Comment name
338	 * @param unknown_type $docComment Comment object
339	 * @return array
340	 */
341	protected static function _findValueForDocComment($docCommentName, $docComment)
342	{
343		$returnValue = array();
344		
345		$commentLines = explode("\n", $docComment);
346	    foreach ($commentLines as $commentLine) {
347	        if (strpos($commentLine, $docCommentName . ' ') !== false) {
348	            $returnValue[] = trim(substr($commentLine, strpos($commentLine, $docCommentName) + strlen($docCommentName) + 1));
349	        }
350	    }
351	    
352	    return $returnValue;
353	}
354	
355	/**
356	 * Display information on an object
357	 * 
358	 * @param object $object Object
359	 * @param array $propertiesToDump Property names to display
360	 */
361	protected function _displayObjectInformation($object, $propertiesToDump = array())
362	{
363		foreach ($propertiesToDump as $property) {
364			printf('%-16s: %s' . "\r\n", $property, $object->$property);
365		}
366		printf("\r\n");
367	}
368	
369	/**
370	 * Displays the help information.
371	 * 
372	 * @command-name <default>
373	 * @command-name -h
374	 * @command-name -help
375	 * @command-description Displays the current help information.
376	 */
377	public function helpCommand() {
378		$handler = $this->getHandler();
379		$newline = "\r\n";
380		
381		if (count($handler->headers) > 0) {
382			foreach ($handler->headers as $header) {
383				printf('%s%s', $header, $newline);		
384			}
385			printf($newline);
386		}
387		printf('%s%s', $handler->description, $newline);
388		printf($newline);
389		printf('Available commands:%s', $newline);
390		foreach ($handler->commands as $command) {
391			$description = str_split($command->description, 50);
392			printf('  %-25s %s%s', implode(', ', $command->aliases), $description[0], $newline);
393			for ($di = 1; $di < count($description); $di++) {
394				printf('  %-25s %s%s', '', $description[$di], $newline);
395			}
396			printf($newline);			
397			
398			if (count($command->parameters) > 0) {
399				foreach ($command->parameters as $parameter) {
400					$description = str_split($parameter->description, 50);
401					printf('    %-23s %s%s', implode(', ', $parameter->aliases), $description[0], $newline);
402					for ($di = 1; $di < count($description); $di++) {
403						printf('    %-23s %s%s', '', $description[$di], $newline);
404					}
405					printf($newline);
406				}
407			}
408			printf($newline);
409			
410			if (count($command->examples) > 0) {
411				printf('    Example usage:%s', $newline);
412				foreach ($command->examples as $example) {
413					printf('      %s%s', $example, $newline);
414				}
415				printf($newline);
416			}
417		}
418		
419		if (count($handler->footers) > 0) {
420			printf($newline);
421			foreach ($handler->footers as $footer) {
422				printf('%s%s', $footer, $newline);		
423			}
424			printf($newline);
425		}
426	}
427}