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