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 }
|