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