ab4ce45d1ca272ad1619926ca8f294ed7f79c545
[yaffs-website] / vendor / consolidation / annotated-command / src / CommandProcessor.php
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 }