Mercurial > hg > cmmr2012-drupal-site
comparison vendor/consolidation/annotated-command/src/AnnotatedCommand.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\HookManager; | |
5 use Consolidation\AnnotatedCommand\Parser\CommandInfo; | |
6 use Consolidation\AnnotatedCommand\Help\HelpDocumentAlter; | |
7 use Symfony\Component\Console\Command\Command; | |
8 use Symfony\Component\Console\Input\InputArgument; | |
9 use Symfony\Component\Console\Input\InputInterface; | |
10 use Symfony\Component\Console\Input\InputOption; | |
11 use Symfony\Component\Console\Output\OutputInterface; | |
12 | |
13 /** | |
14 * AnnotatedCommands are created automatically by the | |
15 * AnnotatedCommandFactory. Each command method in a | |
16 * command file will produce one AnnotatedCommand. These | |
17 * are then added to your Symfony Console Application object; | |
18 * nothing else is needed. | |
19 * | |
20 * Optionally, though, you may extend AnnotatedCommand directly | |
21 * to make a single command. The usage pattern is the same | |
22 * as for any other Symfony Console command, except that you may | |
23 * omit the 'Confiure' method, and instead place your annotations | |
24 * on the execute() method. | |
25 * | |
26 * @package Consolidation\AnnotatedCommand | |
27 */ | |
28 class AnnotatedCommand extends Command implements HelpDocumentAlter | |
29 { | |
30 protected $commandCallback; | |
31 protected $commandProcessor; | |
32 protected $annotationData; | |
33 protected $examples = []; | |
34 protected $topics = []; | |
35 protected $usesInputInterface; | |
36 protected $usesOutputInterface; | |
37 protected $returnType; | |
38 | |
39 public function __construct($name = null) | |
40 { | |
41 $commandInfo = false; | |
42 | |
43 // If this is a subclass of AnnotatedCommand, check to see | |
44 // if the 'execute' method is annotated. We could do this | |
45 // unconditionally; it is a performance optimization to skip | |
46 // checking the annotations if $this is an instance of | |
47 // AnnotatedCommand. Alternately, we break out a new subclass. | |
48 // The command factory instantiates the subclass. | |
49 if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') { | |
50 $commandInfo = CommandInfo::create($this, 'execute'); | |
51 if (!isset($name)) { | |
52 $name = $commandInfo->getName(); | |
53 } | |
54 } | |
55 parent::__construct($name); | |
56 if ($commandInfo && $commandInfo->hasAnnotation('command')) { | |
57 $this->setCommandInfo($commandInfo); | |
58 $this->setCommandOptions($commandInfo); | |
59 } | |
60 } | |
61 | |
62 public function setCommandCallback($commandCallback) | |
63 { | |
64 $this->commandCallback = $commandCallback; | |
65 return $this; | |
66 } | |
67 | |
68 public function setCommandProcessor($commandProcessor) | |
69 { | |
70 $this->commandProcessor = $commandProcessor; | |
71 return $this; | |
72 } | |
73 | |
74 public function commandProcessor() | |
75 { | |
76 // If someone is using an AnnotatedCommand, and is NOT getting | |
77 // it from an AnnotatedCommandFactory OR not correctly injecting | |
78 // a command processor via setCommandProcessor() (ideally via the | |
79 // DI container), then we'll just give each annotated command its | |
80 // own command processor. This is not ideal; preferably, there would | |
81 // only be one instance of the command processor in the application. | |
82 if (!isset($this->commandProcessor)) { | |
83 $this->commandProcessor = new CommandProcessor(new HookManager()); | |
84 } | |
85 return $this->commandProcessor; | |
86 } | |
87 | |
88 public function getReturnType() | |
89 { | |
90 return $this->returnType; | |
91 } | |
92 | |
93 public function setReturnType($returnType) | |
94 { | |
95 $this->returnType = $returnType; | |
96 return $this; | |
97 } | |
98 | |
99 public function getAnnotationData() | |
100 { | |
101 return $this->annotationData; | |
102 } | |
103 | |
104 public function setAnnotationData($annotationData) | |
105 { | |
106 $this->annotationData = $annotationData; | |
107 return $this; | |
108 } | |
109 | |
110 public function getTopics() | |
111 { | |
112 return $this->topics; | |
113 } | |
114 | |
115 public function setTopics($topics) | |
116 { | |
117 $this->topics = $topics; | |
118 return $this; | |
119 } | |
120 | |
121 public function setCommandInfo($commandInfo) | |
122 { | |
123 $this->setDescription($commandInfo->getDescription()); | |
124 $this->setHelp($commandInfo->getHelp()); | |
125 $this->setAliases($commandInfo->getAliases()); | |
126 $this->setAnnotationData($commandInfo->getAnnotations()); | |
127 $this->setTopics($commandInfo->getTopics()); | |
128 foreach ($commandInfo->getExampleUsages() as $usage => $description) { | |
129 $this->addUsageOrExample($usage, $description); | |
130 } | |
131 $this->setCommandArguments($commandInfo); | |
132 $this->setReturnType($commandInfo->getReturnType()); | |
133 // Hidden commands available since Symfony 3.2 | |
134 // http://symfony.com/doc/current/console/hide_commands.html | |
135 if (method_exists($this, 'setHidden')) { | |
136 $this->setHidden($commandInfo->getHidden()); | |
137 } | |
138 return $this; | |
139 } | |
140 | |
141 public function getExampleUsages() | |
142 { | |
143 return $this->examples; | |
144 } | |
145 | |
146 protected function addUsageOrExample($usage, $description) | |
147 { | |
148 $this->addUsage($usage); | |
149 if (!empty($description)) { | |
150 $this->examples[$usage] = $description; | |
151 } | |
152 } | |
153 | |
154 public function helpAlter(\DomDocument $originalDom) | |
155 { | |
156 $dom = new \DOMDocument('1.0', 'UTF-8'); | |
157 $dom->appendChild($commandXML = $dom->createElement('command')); | |
158 $commandXML->setAttribute('id', $this->getName()); | |
159 $commandXML->setAttribute('name', $this->getName()); | |
160 | |
161 // Get the original <command> element and its top-level elements. | |
162 $originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command'); | |
163 $originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages'); | |
164 $originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description'); | |
165 $originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help'); | |
166 $originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments'); | |
167 $originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options'); | |
168 | |
169 // Keep only the first of the <usage> elements | |
170 $newUsagesXML = $dom->createElement('usages'); | |
171 $firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage'); | |
172 $newUsagesXML->appendChild($firstUsageXML); | |
173 | |
174 // Create our own <example> elements | |
175 $newExamplesXML = $dom->createElement('examples'); | |
176 foreach ($this->examples as $usage => $description) { | |
177 $newExamplesXML->appendChild($exampleXML = $dom->createElement('example')); | |
178 $exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage)); | |
179 $exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description)); | |
180 } | |
181 | |
182 // Create our own <alias> elements | |
183 $newAliasesXML = $dom->createElement('aliases'); | |
184 foreach ($this->getAliases() as $alias) { | |
185 $newAliasesXML->appendChild($dom->createElement('alias', $alias)); | |
186 } | |
187 | |
188 // Create our own <topic> elements | |
189 $newTopicsXML = $dom->createElement('topics'); | |
190 foreach ($this->getTopics() as $topic) { | |
191 $newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic)); | |
192 } | |
193 | |
194 // Place the different elements into the <command> element in the desired order | |
195 $commandXML->appendChild($newUsagesXML); | |
196 $commandXML->appendChild($newExamplesXML); | |
197 $commandXML->appendChild($originalDescriptionXML); | |
198 $commandXML->appendChild($originalArgumentsXML); | |
199 $commandXML->appendChild($originalOptionsXML); | |
200 $commandXML->appendChild($originalHelpXML); | |
201 $commandXML->appendChild($newAliasesXML); | |
202 $commandXML->appendChild($newTopicsXML); | |
203 | |
204 return $dom; | |
205 } | |
206 | |
207 protected function getSingleElementByTagName($dom, $parent, $tagName) | |
208 { | |
209 // There should always be exactly one '<command>' element. | |
210 $elements = $parent->getElementsByTagName($tagName); | |
211 $result = $elements->item(0); | |
212 | |
213 $result = $dom->importNode($result, true); | |
214 | |
215 return $result; | |
216 } | |
217 | |
218 protected function setCommandArguments($commandInfo) | |
219 { | |
220 $this->setUsesInputInterface($commandInfo); | |
221 $this->setUsesOutputInterface($commandInfo); | |
222 $this->setCommandArgumentsFromParameters($commandInfo); | |
223 return $this; | |
224 } | |
225 | |
226 /** | |
227 * Check whether the first parameter is an InputInterface. | |
228 */ | |
229 protected function checkUsesInputInterface($params) | |
230 { | |
231 /** @var \ReflectionParameter $firstParam */ | |
232 $firstParam = reset($params); | |
233 return $firstParam && $firstParam->getClass() && $firstParam->getClass()->implementsInterface( | |
234 '\\Symfony\\Component\\Console\\Input\\InputInterface' | |
235 ); | |
236 } | |
237 | |
238 /** | |
239 * Determine whether this command wants to get its inputs | |
240 * via an InputInterface or via its command parameters | |
241 */ | |
242 protected function setUsesInputInterface($commandInfo) | |
243 { | |
244 $params = $commandInfo->getParameters(); | |
245 $this->usesInputInterface = $this->checkUsesInputInterface($params); | |
246 return $this; | |
247 } | |
248 | |
249 /** | |
250 * Determine whether this command wants to send its output directly | |
251 * to the provided OutputInterface, or whether it will returned | |
252 * structured output to be processed by the command processor. | |
253 */ | |
254 protected function setUsesOutputInterface($commandInfo) | |
255 { | |
256 $params = $commandInfo->getParameters(); | |
257 $index = $this->checkUsesInputInterface($params) ? 1 : 0; | |
258 $this->usesOutputInterface = | |
259 (count($params) > $index) && | |
260 $params[$index]->getClass() && | |
261 $params[$index]->getClass()->implementsInterface( | |
262 '\\Symfony\\Component\\Console\\Output\\OutputInterface' | |
263 ) | |
264 ; | |
265 return $this; | |
266 } | |
267 | |
268 protected function setCommandArgumentsFromParameters($commandInfo) | |
269 { | |
270 $args = $commandInfo->arguments()->getValues(); | |
271 foreach ($args as $name => $defaultValue) { | |
272 $description = $commandInfo->arguments()->getDescription($name); | |
273 $hasDefault = $commandInfo->arguments()->hasDefault($name); | |
274 $parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue); | |
275 $this->addArgument($name, $parameterMode, $description, $defaultValue); | |
276 } | |
277 return $this; | |
278 } | |
279 | |
280 protected function getCommandArgumentMode($hasDefault, $defaultValue) | |
281 { | |
282 if (!$hasDefault) { | |
283 return InputArgument::REQUIRED; | |
284 } | |
285 if (is_array($defaultValue)) { | |
286 return InputArgument::IS_ARRAY; | |
287 } | |
288 return InputArgument::OPTIONAL; | |
289 } | |
290 | |
291 public function setCommandOptions($commandInfo, $automaticOptions = []) | |
292 { | |
293 $inputOptions = $commandInfo->inputOptions(); | |
294 | |
295 $this->addOptions($inputOptions + $automaticOptions, $automaticOptions); | |
296 return $this; | |
297 } | |
298 | |
299 public function addOptions($inputOptions, $automaticOptions = []) | |
300 { | |
301 foreach ($inputOptions as $name => $inputOption) { | |
302 $description = $inputOption->getDescription(); | |
303 | |
304 if (empty($description) && isset($automaticOptions[$name])) { | |
305 $description = $automaticOptions[$name]->getDescription(); | |
306 $inputOption = static::inputOptionSetDescription($inputOption, $description); | |
307 } | |
308 $this->getDefinition()->addOption($inputOption); | |
309 } | |
310 } | |
311 | |
312 protected static function inputOptionSetDescription($inputOption, $description) | |
313 { | |
314 // Recover the 'mode' value, because Symfony is stubborn | |
315 $mode = 0; | |
316 if ($inputOption->isValueRequired()) { | |
317 $mode |= InputOption::VALUE_REQUIRED; | |
318 } | |
319 if ($inputOption->isValueOptional()) { | |
320 $mode |= InputOption::VALUE_OPTIONAL; | |
321 } | |
322 if ($inputOption->isArray()) { | |
323 $mode |= InputOption::VALUE_IS_ARRAY; | |
324 } | |
325 if (!$mode) { | |
326 $mode = InputOption::VALUE_NONE; | |
327 } | |
328 | |
329 $inputOption = new InputOption( | |
330 $inputOption->getName(), | |
331 $inputOption->getShortcut(), | |
332 $mode, | |
333 $description, | |
334 $inputOption->getDefault() | |
335 ); | |
336 return $inputOption; | |
337 } | |
338 | |
339 /** | |
340 * Returns all of the hook names that may be called for this command. | |
341 * | |
342 * @return array | |
343 */ | |
344 public function getNames() | |
345 { | |
346 return HookManager::getNames($this, $this->commandCallback); | |
347 } | |
348 | |
349 /** | |
350 * Add any options to this command that are defined by hook implementations | |
351 */ | |
352 public function optionsHook() | |
353 { | |
354 $this->commandProcessor()->optionsHook( | |
355 $this, | |
356 $this->getNames(), | |
357 $this->annotationData | |
358 ); | |
359 } | |
360 | |
361 public function optionsHookForHookAnnotations($commandInfoList) | |
362 { | |
363 foreach ($commandInfoList as $commandInfo) { | |
364 $inputOptions = $commandInfo->inputOptions(); | |
365 $this->addOptions($inputOptions); | |
366 foreach ($commandInfo->getExampleUsages() as $usage => $description) { | |
367 if (!in_array($usage, $this->getUsages())) { | |
368 $this->addUsageOrExample($usage, $description); | |
369 } | |
370 } | |
371 } | |
372 } | |
373 | |
374 /** | |
375 * {@inheritdoc} | |
376 */ | |
377 protected function interact(InputInterface $input, OutputInterface $output) | |
378 { | |
379 $this->commandProcessor()->interact( | |
380 $input, | |
381 $output, | |
382 $this->getNames(), | |
383 $this->annotationData | |
384 ); | |
385 } | |
386 | |
387 protected function initialize(InputInterface $input, OutputInterface $output) | |
388 { | |
389 // Allow the hook manager a chance to provide configuration values, | |
390 // if there are any registered hooks to do that. | |
391 $this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData); | |
392 } | |
393 | |
394 /** | |
395 * {@inheritdoc} | |
396 */ | |
397 protected function execute(InputInterface $input, OutputInterface $output) | |
398 { | |
399 // Validate, run, process, alter, handle results. | |
400 return $this->commandProcessor()->process( | |
401 $output, | |
402 $this->getNames(), | |
403 $this->commandCallback, | |
404 $this->createCommandData($input, $output) | |
405 ); | |
406 } | |
407 | |
408 /** | |
409 * This function is available for use by a class that may | |
410 * wish to extend this class rather than use annotations to | |
411 * define commands. Using this technique does allow for the | |
412 * use of annotations to define hooks. | |
413 */ | |
414 public function processResults(InputInterface $input, OutputInterface $output, $results) | |
415 { | |
416 $commandData = $this->createCommandData($input, $output); | |
417 $commandProcessor = $this->commandProcessor(); | |
418 $names = $this->getNames(); | |
419 $results = $commandProcessor->processResults( | |
420 $names, | |
421 $results, | |
422 $commandData | |
423 ); | |
424 return $commandProcessor->handleResults( | |
425 $output, | |
426 $names, | |
427 $results, | |
428 $commandData | |
429 ); | |
430 } | |
431 | |
432 protected function createCommandData(InputInterface $input, OutputInterface $output) | |
433 { | |
434 $commandData = new CommandData( | |
435 $this->annotationData, | |
436 $input, | |
437 $output | |
438 ); | |
439 | |
440 $commandData->setUseIOInterfaces( | |
441 $this->usesInputInterface, | |
442 $this->usesOutputInterface | |
443 ); | |
444 | |
445 // Allow the commandData to cache the list of options with | |
446 // special default values ('null' and 'true'), as these will | |
447 // need special handling. @see CommandData::options(). | |
448 $commandData->cacheSpecialDefaults($this->getDefinition()); | |
449 | |
450 return $commandData; | |
451 } | |
452 } |