Microsoft_Console
[ class tree: Microsoft_Console ] [ index: Microsoft_Console ] [ all elements ]

Source for file Command.php

Documentation is available at Command.php

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

Documentation generated on Sat, 03 Dec 2011 13:59:16 +0100 by phpDocumentor 1.4.3