annotate vendor/consolidation/annotated-command/src/CommandProcessor.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children a9cd425dd02b
rev   line source
Chris@0 1 <?php
Chris@0 2 namespace Consolidation\AnnotatedCommand;
Chris@0 3
Chris@0 4 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ReplaceCommandHookDispatcher;
Chris@0 5 use Psr\Log\LoggerAwareInterface;
Chris@0 6 use Psr\Log\LoggerAwareTrait;
Chris@0 7 use Symfony\Component\Console\Input\InputInterface;
Chris@0 8 use Symfony\Component\Console\Output\OutputInterface;
Chris@0 9 use Symfony\Component\Console\Output\ConsoleOutputInterface;
Chris@0 10
Chris@0 11 use Consolidation\OutputFormatters\FormatterManager;
Chris@0 12 use Consolidation\OutputFormatters\Options\FormatterOptions;
Chris@0 13 use Consolidation\AnnotatedCommand\Hooks\HookManager;
Chris@0 14 use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
Chris@0 15
Chris@0 16 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InitializeHookDispatcher;
Chris@0 17 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher;
Chris@0 18 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InteractHookDispatcher;
Chris@0 19 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ValidateHookDispatcher;
Chris@0 20 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ProcessResultHookDispatcher;
Chris@0 21 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\StatusDeterminerHookDispatcher;
Chris@0 22 use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ExtracterHookDispatcher;
Chris@0 23
Chris@0 24 /**
Chris@0 25 * Process a command, including hooks and other callbacks.
Chris@0 26 * There should only be one command processor per application.
Chris@0 27 * Provide your command processor to the AnnotatedCommandFactory
Chris@0 28 * via AnnotatedCommandFactory::setCommandProcessor().
Chris@0 29 */
Chris@0 30 class CommandProcessor implements LoggerAwareInterface
Chris@0 31 {
Chris@0 32 use LoggerAwareTrait;
Chris@0 33
Chris@0 34 /** var HookManager */
Chris@0 35 protected $hookManager;
Chris@0 36 /** var FormatterManager */
Chris@0 37 protected $formatterManager;
Chris@0 38 /** var callable */
Chris@0 39 protected $displayErrorFunction;
Chris@0 40 /** var PrepareFormatterOptions[] */
Chris@0 41 protected $prepareOptionsList = [];
Chris@0 42 /** var boolean */
Chris@0 43 protected $passExceptions;
Chris@0 44
Chris@0 45 public function __construct(HookManager $hookManager)
Chris@0 46 {
Chris@0 47 $this->hookManager = $hookManager;
Chris@0 48 }
Chris@0 49
Chris@0 50 /**
Chris@0 51 * Return the hook manager
Chris@0 52 * @return HookManager
Chris@0 53 */
Chris@0 54 public function hookManager()
Chris@0 55 {
Chris@0 56 return $this->hookManager;
Chris@0 57 }
Chris@0 58
Chris@0 59 public function addPrepareFormatter(PrepareFormatter $preparer)
Chris@0 60 {
Chris@0 61 $this->prepareOptionsList[] = $preparer;
Chris@0 62 }
Chris@0 63
Chris@0 64 public function setFormatterManager(FormatterManager $formatterManager)
Chris@0 65 {
Chris@0 66 $this->formatterManager = $formatterManager;
Chris@0 67 return $this;
Chris@0 68 }
Chris@0 69
Chris@0 70 public function setDisplayErrorFunction(callable $fn)
Chris@0 71 {
Chris@0 72 $this->displayErrorFunction = $fn;
Chris@0 73 return $this;
Chris@0 74 }
Chris@0 75
Chris@0 76 /**
Chris@0 77 * Set a mode to make the annotated command library re-throw
Chris@0 78 * any exception that it catches while processing a command.
Chris@0 79 *
Chris@0 80 * The default behavior in the current (2.x) branch is to catch
Chris@0 81 * the exception and replace it with a CommandError object that
Chris@0 82 * may be processed by the normal output processing passthrough.
Chris@0 83 *
Chris@0 84 * In the 3.x branch, exceptions will never be caught; they will
Chris@0 85 * be passed through, as if setPassExceptions(true) were called.
Chris@0 86 * This is the recommended behavior.
Chris@0 87 */
Chris@0 88 public function setPassExceptions($passExceptions)
Chris@0 89 {
Chris@0 90 $this->passExceptions = $passExceptions;
Chris@0 91 return $this;
Chris@0 92 }
Chris@0 93
Chris@0 94 public function commandErrorForException(\Exception $e)
Chris@0 95 {
Chris@0 96 if ($this->passExceptions) {
Chris@0 97 throw $e;
Chris@0 98 }
Chris@0 99 return new CommandError($e->getMessage(), $e->getCode());
Chris@0 100 }
Chris@0 101
Chris@0 102 /**
Chris@0 103 * Return the formatter manager
Chris@0 104 * @return FormatterManager
Chris@0 105 */
Chris@0 106 public function formatterManager()
Chris@0 107 {
Chris@0 108 return $this->formatterManager;
Chris@0 109 }
Chris@0 110
Chris@0 111 public function initializeHook(
Chris@0 112 InputInterface $input,
Chris@0 113 $names,
Chris@0 114 AnnotationData $annotationData
Chris@0 115 ) {
Chris@0 116 $initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
Chris@0 117 return $initializeDispatcher->initialize($input, $annotationData);
Chris@0 118 }
Chris@0 119
Chris@0 120 public function optionsHook(
Chris@0 121 AnnotatedCommand $command,
Chris@0 122 $names,
Chris@0 123 AnnotationData $annotationData
Chris@0 124 ) {
Chris@0 125 $optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
Chris@0 126 $optionsDispatcher->getOptions($command, $annotationData);
Chris@0 127 }
Chris@0 128
Chris@0 129 public function interact(
Chris@0 130 InputInterface $input,
Chris@0 131 OutputInterface $output,
Chris@0 132 $names,
Chris@0 133 AnnotationData $annotationData
Chris@0 134 ) {
Chris@0 135 $interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
Chris@0 136 return $interactDispatcher->interact($input, $output, $annotationData);
Chris@0 137 }
Chris@0 138
Chris@0 139 public function process(
Chris@0 140 OutputInterface $output,
Chris@0 141 $names,
Chris@0 142 $commandCallback,
Chris@0 143 CommandData $commandData
Chris@0 144 ) {
Chris@0 145 $result = [];
Chris@0 146 try {
Chris@0 147 $result = $this->validateRunAndAlter(
Chris@0 148 $names,
Chris@0 149 $commandCallback,
Chris@0 150 $commandData
Chris@0 151 );
Chris@0 152 return $this->handleResults($output, $names, $result, $commandData);
Chris@0 153 } catch (\Exception $e) {
Chris@0 154 $result = $this->commandErrorForException($e);
Chris@0 155 return $this->handleResults($output, $names, $result, $commandData);
Chris@0 156 }
Chris@0 157 }
Chris@0 158
Chris@0 159 public function validateRunAndAlter(
Chris@0 160 $names,
Chris@0 161 $commandCallback,
Chris@0 162 CommandData $commandData
Chris@0 163 ) {
Chris@0 164 // Validators return any object to signal a validation error;
Chris@0 165 // if the return an array, it replaces the arguments.
Chris@0 166 $validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
Chris@0 167 $validated = $validateDispatcher->validate($commandData);
Chris@0 168 if (is_object($validated)) {
Chris@0 169 return $validated;
Chris@0 170 }
Chris@0 171
Chris@0 172 $replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
Chris@0 173 if ($this->logger) {
Chris@0 174 $replaceDispatcher->setLogger($this->logger);
Chris@0 175 }
Chris@0 176 if ($replaceDispatcher->hasReplaceCommandHook()) {
Chris@0 177 $commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
Chris@0 178 }
Chris@0 179
Chris@0 180 // Run the command, alter the results, and then handle output and status
Chris@0 181 $result = $this->runCommandCallback($commandCallback, $commandData);
Chris@0 182 return $this->processResults($names, $result, $commandData);
Chris@0 183 }
Chris@0 184
Chris@0 185 public function processResults($names, $result, CommandData $commandData)
Chris@0 186 {
Chris@0 187 $processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
Chris@0 188 return $processDispatcher->process($result, $commandData);
Chris@0 189 }
Chris@0 190
Chris@0 191 /**
Chris@0 192 * Handle the result output and status code calculation.
Chris@0 193 */
Chris@0 194 public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
Chris@0 195 {
Chris@0 196 $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
Chris@0 197 $status = $statusCodeDispatcher->determineStatusCode($result);
Chris@0 198 // If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
Chris@0 199 if (is_integer($result) && !isset($status)) {
Chris@0 200 return $result;
Chris@0 201 }
Chris@0 202 $status = $this->interpretStatusCode($status);
Chris@0 203
Chris@0 204 // Get the structured output, the output stream and the formatter
Chris@0 205 $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
Chris@0 206 $structuredOutput = $extractDispatcher->extractOutput($result);
Chris@0 207 $output = $this->chooseOutputStream($output, $status);
Chris@0 208 if ($status != 0) {
Chris@0 209 return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
Chris@0 210 }
Chris@0 211 if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
Chris@0 212 return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
Chris@0 213 }
Chris@0 214 return $this->writeCommandOutput($output, $structuredOutput);
Chris@0 215 }
Chris@0 216
Chris@0 217 protected function dataCanBeFormatted($structuredOutput)
Chris@0 218 {
Chris@0 219 if (!isset($this->formatterManager)) {
Chris@0 220 return false;
Chris@0 221 }
Chris@0 222 return
Chris@0 223 is_object($structuredOutput) ||
Chris@0 224 is_array($structuredOutput);
Chris@0 225 }
Chris@0 226
Chris@0 227 /**
Chris@0 228 * Run the main command callback
Chris@0 229 */
Chris@0 230 protected function runCommandCallback($commandCallback, CommandData $commandData)
Chris@0 231 {
Chris@0 232 $result = false;
Chris@0 233 try {
Chris@0 234 $args = $commandData->getArgsAndOptions();
Chris@0 235 $result = call_user_func_array($commandCallback, $args);
Chris@0 236 } catch (\Exception $e) {
Chris@0 237 $result = $this->commandErrorForException($e);
Chris@0 238 }
Chris@0 239 return $result;
Chris@0 240 }
Chris@0 241
Chris@0 242 /**
Chris@0 243 * Determine the formatter that should be used to render
Chris@0 244 * output.
Chris@0 245 *
Chris@0 246 * If the user specified a format via the --format option,
Chris@0 247 * then always return that. Otherwise, return the default
Chris@0 248 * format, unless --pipe was specified, in which case
Chris@0 249 * return the default pipe format, format-pipe.
Chris@0 250 *
Chris@0 251 * n.b. --pipe is a handy option introduced in Drush 2
Chris@0 252 * (or perhaps even Drush 1) that indicates that the command
Chris@0 253 * should select the output format that is most appropriate
Chris@0 254 * for use in scripts (e.g. to pipe to another command).
Chris@0 255 *
Chris@0 256 * @return string
Chris@0 257 */
Chris@0 258 protected function getFormat(FormatterOptions $options)
Chris@0 259 {
Chris@0 260 // In Symfony Console, there is no way for us to differentiate
Chris@0 261 // between the user specifying '--format=table', and the user
Chris@0 262 // not specifying --format when the default value is 'table'.
Chris@0 263 // Therefore, we must make --field always override --format; it
Chris@0 264 // cannot become the default value for --format.
Chris@0 265 if ($options->get('field')) {
Chris@0 266 return 'string';
Chris@0 267 }
Chris@0 268 $defaults = [];
Chris@0 269 if ($options->get('pipe')) {
Chris@0 270 return $options->get('pipe-format', [], 'tsv');
Chris@0 271 }
Chris@0 272 return $options->getFormat($defaults);
Chris@0 273 }
Chris@0 274
Chris@0 275 /**
Chris@0 276 * Determine whether we should use stdout or stderr.
Chris@0 277 */
Chris@0 278 protected function chooseOutputStream(OutputInterface $output, $status)
Chris@0 279 {
Chris@0 280 // If the status code indicates an error, then print the
Chris@0 281 // result to stderr rather than stdout
Chris@0 282 if ($status && ($output instanceof ConsoleOutputInterface)) {
Chris@0 283 return $output->getErrorOutput();
Chris@0 284 }
Chris@0 285 return $output;
Chris@0 286 }
Chris@0 287
Chris@0 288 /**
Chris@0 289 * Call the formatter to output the provided data.
Chris@0 290 */
Chris@0 291 protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
Chris@0 292 {
Chris@0 293 $formatterOptions = $this->createFormatterOptions($commandData);
Chris@0 294 $format = $this->getFormat($formatterOptions);
Chris@0 295 $this->formatterManager->write(
Chris@0 296 $output,
Chris@0 297 $format,
Chris@0 298 $structuredOutput,
Chris@0 299 $formatterOptions
Chris@0 300 );
Chris@0 301 return 0;
Chris@0 302 }
Chris@0 303
Chris@0 304 /**
Chris@0 305 * Create a FormatterOptions object for use in writing the formatted output.
Chris@0 306 * @param CommandData $commandData
Chris@0 307 * @return FormatterOptions
Chris@0 308 */
Chris@0 309 protected function createFormatterOptions($commandData)
Chris@0 310 {
Chris@0 311 $options = $commandData->input()->getOptions();
Chris@0 312 $formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
Chris@0 313 foreach ($this->prepareOptionsList as $preparer) {
Chris@0 314 $preparer->prepare($commandData, $formatterOptions);
Chris@0 315 }
Chris@0 316 return $formatterOptions;
Chris@0 317 }
Chris@0 318
Chris@0 319 /**
Chris@0 320 * Description
Chris@0 321 * @param OutputInterface $output
Chris@0 322 * @param int $status
Chris@0 323 * @param string $structuredOutput
Chris@0 324 * @param mixed $originalResult
Chris@0 325 * @return type
Chris@0 326 */
Chris@0 327 protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
Chris@0 328 {
Chris@0 329 if (isset($this->displayErrorFunction)) {
Chris@0 330 call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
Chris@0 331 } else {
Chris@0 332 $this->writeCommandOutput($output, $structuredOutput);
Chris@0 333 }
Chris@0 334 return $status;
Chris@0 335 }
Chris@0 336
Chris@0 337 /**
Chris@0 338 * If the result object is a string, then print it.
Chris@0 339 */
Chris@0 340 protected function writeCommandOutput(
Chris@0 341 OutputInterface $output,
Chris@0 342 $structuredOutput
Chris@0 343 ) {
Chris@0 344 // If there is no formatter, we will print strings,
Chris@0 345 // but can do no more than that.
Chris@0 346 if (is_string($structuredOutput)) {
Chris@0 347 $output->writeln($structuredOutput);
Chris@0 348 }
Chris@0 349 return 0;
Chris@0 350 }
Chris@0 351
Chris@0 352 /**
Chris@0 353 * If a status code was set, then return it; otherwise,
Chris@0 354 * presume success.
Chris@0 355 */
Chris@0 356 protected function interpretStatusCode($status)
Chris@0 357 {
Chris@0 358 if (isset($status)) {
Chris@0 359 return $status;
Chris@0 360 }
Chris@0 361 return 0;
Chris@0 362 }
Chris@0 363 }