Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / consolidation / robo / src / Runner.php
1 <?php
2 namespace Robo;
3
4 use Composer\Autoload\ClassLoader;
5 use Symfony\Component\Console\Input\ArgvInput;
6 use Symfony\Component\Console\Input\StringInput;
7 use Robo\Contract\BuilderAwareInterface;
8 use Robo\Collection\CollectionBuilder;
9 use Robo\Common\IO;
10 use Robo\Exception\TaskExitException;
11 use League\Container\ContainerAwareInterface;
12 use League\Container\ContainerAwareTrait;
13
14 class Runner implements ContainerAwareInterface
15 {
16     const ROBOCLASS = 'RoboFile';
17     const ROBOFILE = 'RoboFile.php';
18
19     use IO;
20     use ContainerAwareTrait;
21
22     /**
23      * @var string
24      */
25     protected $roboClass;
26
27     /**
28      * @var string
29      */
30     protected $roboFile;
31
32     /**
33      * @var string working dir of Robo
34      */
35     protected $dir;
36
37     /**
38      * @var string[]
39      */
40     protected $errorConditions = [];
41
42     /**
43      * @var string GitHub Repo for SelfUpdate
44      */
45     protected $selfUpdateRepository = null;
46
47     /**
48      * @var \Composer\Autoload\ClassLoader
49      */
50     protected $classLoader = null;
51
52     /**
53      * @var string
54      */
55     protected $relativePluginNamespace;
56
57     /**
58      * Class Constructor
59      *
60      * @param null|string $roboClass
61      * @param null|string $roboFile
62      */
63     public function __construct($roboClass = null, $roboFile = null)
64     {
65         // set the const as class properties to allow overwriting in child classes
66         $this->roboClass = $roboClass ? $roboClass : self::ROBOCLASS ;
67         $this->roboFile  = $roboFile ? $roboFile : self::ROBOFILE;
68         $this->dir = getcwd();
69     }
70
71     protected function errorCondition($msg, $errorType)
72     {
73         $this->errorConditions[$msg] = $errorType;
74     }
75
76     /**
77      * @param \Symfony\Component\Console\Output\OutputInterface $output
78      *
79      * @return bool
80      */
81     protected function loadRoboFile($output)
82     {
83         // If we have not been provided an output object, make a temporary one.
84         if (!$output) {
85             $output = new \Symfony\Component\Console\Output\ConsoleOutput();
86         }
87
88         // If $this->roboClass is a single class that has not already
89         // been loaded, then we will try to obtain it from $this->roboFile.
90         // If $this->roboClass is an array, we presume all classes requested
91         // are available via the autoloader.
92         if (is_array($this->roboClass) || class_exists($this->roboClass)) {
93             return true;
94         }
95         if (!file_exists($this->dir)) {
96             $this->errorCondition("Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.", 'red');
97             return false;
98         }
99
100         $realDir = realpath($this->dir);
101
102         $roboFilePath = $realDir . DIRECTORY_SEPARATOR . $this->roboFile;
103         if (!file_exists($roboFilePath)) {
104             $requestedRoboFilePath = $this->dir . DIRECTORY_SEPARATOR . $this->roboFile;
105             $this->errorCondition("Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile.", 'red');
106             return false;
107         }
108         require_once $roboFilePath;
109
110         if (!class_exists($this->roboClass)) {
111             $this->errorCondition("Class {$this->roboClass} was not loaded.", 'red');
112             return false;
113         }
114         return true;
115     }
116
117     /**
118      * @param array $argv
119      * @param null|string $appName
120      * @param null|string $appVersion
121      * @param null|\Symfony\Component\Console\Output\OutputInterface $output
122      *
123      * @return int
124      */
125     public function execute($argv, $appName = null, $appVersion = null, $output = null)
126     {
127         $argv = $this->shebang($argv);
128         $argv = $this->processRoboOptions($argv);
129         $app = null;
130         if ($appName && $appVersion) {
131             $app = Robo::createDefaultApplication($appName, $appVersion);
132         }
133         $commandFiles = $this->getRoboFileCommands($output);
134         return $this->run($argv, $output, $app, $commandFiles, $this->classLoader);
135     }
136
137     /**
138      * @param null|\Symfony\Component\Console\Input\InputInterface $input
139      * @param null|\Symfony\Component\Console\Output\OutputInterface $output
140      * @param null|\Robo\Application $app
141      * @param array[] $commandFiles
142      * @param null|ClassLoader $classLoader
143      *
144      * @return int
145      */
146     public function run($input = null, $output = null, $app = null, $commandFiles = [], $classLoader = null)
147     {
148         // Create default input and output objects if they were not provided
149         if (!$input) {
150             $input = new StringInput('');
151         }
152         if (is_array($input)) {
153             $input = new ArgvInput($input);
154         }
155         if (!$output) {
156             $output = new \Symfony\Component\Console\Output\ConsoleOutput();
157         }
158         $this->setInput($input);
159         $this->setOutput($output);
160
161         // If we were not provided a container, then create one
162         if (!$this->getContainer()) {
163             $userConfig = 'robo.yml';
164             $roboAppConfig = dirname(__DIR__) . '/robo.yml';
165             $config = Robo::createConfiguration([$userConfig, $roboAppConfig]);
166             $container = Robo::createDefaultContainer($input, $output, $app, $config, $classLoader);
167             $this->setContainer($container);
168             // Automatically register a shutdown function and
169             // an error handler when we provide the container.
170             $this->installRoboHandlers();
171         }
172
173         if (!$app) {
174             $app = Robo::application();
175         }
176         if ($app instanceof \Robo\Application) {
177             $app->addSelfUpdateCommand($this->getSelfUpdateRepository());
178             if (!isset($commandFiles)) {
179                 $this->errorCondition("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow');
180                 $app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
181                 $commandFiles = [];
182             }
183         }
184
185         if (!empty($this->relativePluginNamespace)) {
186             $commandClasses = $this->discoverCommandClasses($this->relativePluginNamespace);
187             $commandFiles = array_merge((array)$commandFiles, $commandClasses);
188         }
189
190         $this->registerCommandClasses($app, $commandFiles);
191
192         try {
193             $statusCode = $app->run($input, $output);
194         } catch (TaskExitException $e) {
195             $statusCode = $e->getCode() ?: 1;
196         }
197
198         // If there were any error conditions in bootstrapping Robo,
199         // print them only if the requested command did not complete
200         // successfully.
201         if ($statusCode) {
202             foreach ($this->errorConditions as $msg => $color) {
203                 $this->yell($msg, 40, $color);
204             }
205         }
206         return $statusCode;
207     }
208
209     /**
210      * @param \Symfony\Component\Console\Output\OutputInterface $output
211      *
212      * @return null|string
213      */
214     protected function getRoboFileCommands($output)
215     {
216         if (!$this->loadRoboFile($output)) {
217             return;
218         }
219         return $this->roboClass;
220     }
221
222     /**
223      * @param \Robo\Application $app
224      * @param array $commandClasses
225      */
226     public function registerCommandClasses($app, $commandClasses)
227     {
228         foreach ((array)$commandClasses as $commandClass) {
229             $this->registerCommandClass($app, $commandClass);
230         }
231     }
232
233     /**
234      * @param $relativeNamespace
235      *
236      * @return array|string[]
237      */
238     protected function discoverCommandClasses($relativeNamespace)
239     {
240         /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */
241         $discovery = Robo::service('relativeNamespaceDiscovery');
242         $discovery->setRelativeNamespace($relativeNamespace.'\Commands')
243             ->setSearchPattern('*Commands.php');
244         return $discovery->getClasses();
245     }
246
247     /**
248      * @param \Robo\Application $app
249      * @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
250      *
251      * @return mixed|void
252      */
253     public function registerCommandClass($app, $commandClass)
254     {
255         $container = Robo::getContainer();
256         $roboCommandFileInstance = $this->instantiateCommandClass($commandClass);
257         if (!$roboCommandFileInstance) {
258             return;
259         }
260
261         // Register commands for all of the public methods in the RoboFile.
262         $commandFactory = $container->get('commandFactory');
263         $commandList = $commandFactory->createCommandsFromClass($roboCommandFileInstance);
264         foreach ($commandList as $command) {
265             $app->add($command);
266         }
267         return $roboCommandFileInstance;
268     }
269
270     /**
271      * @param string|BuilderAwareInterface|ContainerAwareInterface  $commandClass
272      *
273      * @return null|object
274      */
275     protected function instantiateCommandClass($commandClass)
276     {
277         $container = Robo::getContainer();
278
279         // Register the RoboFile with the container and then immediately
280         // fetch it; this ensures that all of the inflectors will run.
281         // If the command class is already an instantiated object, then
282         // just use it exactly as it was provided to us.
283         if (is_string($commandClass)) {
284             if (!class_exists($commandClass)) {
285                 return;
286             }
287             $reflectionClass = new \ReflectionClass($commandClass);
288             if ($reflectionClass->isAbstract()) {
289                 return;
290             }
291
292             $commandFileName = "{$commandClass}Commands";
293             $container->share($commandFileName, $commandClass);
294             $commandClass = $container->get($commandFileName);
295         }
296         // If the command class is a Builder Aware Interface, then
297         // ensure that it has a builder.  Every command class needs
298         // its own collection builder, as they have references to each other.
299         if ($commandClass instanceof BuilderAwareInterface) {
300             $builder = CollectionBuilder::create($container, $commandClass);
301             $commandClass->setBuilder($builder);
302         }
303         if ($commandClass instanceof ContainerAwareInterface) {
304             $commandClass->setContainer($container);
305         }
306         return $commandClass;
307     }
308
309     public function installRoboHandlers()
310     {
311         register_shutdown_function(array($this, 'shutdown'));
312         set_error_handler(array($this, 'handleError'));
313     }
314
315     /**
316      * Process a shebang script, if one was used to launch this Runner.
317      *
318      * @param array $args
319      *
320      * @return array $args with shebang script removed
321      */
322     protected function shebang($args)
323     {
324         // Option 1: Shebang line names Robo, but includes no parameters.
325         // #!/bin/env robo
326         // The robo class may contain multiple commands; the user may
327         // select which one to run, or even get a list of commands or
328         // run 'help' on any of the available commands as usual.
329         if ((count($args) > 1) && $this->isShebangFile($args[1])) {
330             return array_merge([$args[0]], array_slice($args, 2));
331         }
332         // Option 2: Shebang line stipulates which command to run.
333         // #!/bin/env robo mycommand
334         // The robo class must contain a public method named 'mycommand'.
335         // This command will be executed every time.  Arguments and options
336         // may be provided on the commandline as usual.
337         if ((count($args) > 2) && $this->isShebangFile($args[2])) {
338             return array_merge([$args[0]], explode(' ', $args[1]), array_slice($args, 3));
339         }
340         return $args;
341     }
342
343     /**
344      * Determine if the specified argument is a path to a shebang script.
345      * If so, load it.
346      *
347      * @param string $filepath file to check
348      *
349      * @return bool Returns TRUE if shebang script was processed
350      */
351     protected function isShebangFile($filepath)
352     {
353         if (!is_file($filepath)) {
354             return false;
355         }
356         $fp = fopen($filepath, "r");
357         if ($fp === false) {
358             return false;
359         }
360         $line = fgets($fp);
361         $result = $this->isShebangLine($line);
362         if ($result) {
363             while ($line = fgets($fp)) {
364                 $line = trim($line);
365                 if ($line == '<?php') {
366                     $script = stream_get_contents($fp);
367                     if (preg_match('#^class *([^ ]+)#m', $script, $matches)) {
368                         $this->roboClass = $matches[1];
369                         eval($script);
370                         $result = true;
371                     }
372                 }
373             }
374         }
375         fclose($fp);
376
377         return $result;
378     }
379
380     /**
381      * Test to see if the provided line is a robo 'shebang' line.
382      *
383      * @param string $line
384      *
385      * @return bool
386      */
387     protected function isShebangLine($line)
388     {
389         return ((substr($line, 0, 2) == '#!') && (strstr($line, 'robo') !== false));
390     }
391
392     /**
393      * Check for Robo-specific arguments such as --load-from, process them,
394      * and remove them from the array.  We have to process --load-from before
395      * we set up Symfony Console.
396      *
397      * @param array $argv
398      *
399      * @return array
400      */
401     protected function processRoboOptions($argv)
402     {
403         // loading from other directory
404         $pos = $this->arraySearchBeginsWith('--load-from', $argv) ?: array_search('-f', $argv);
405         if ($pos === false) {
406             return $argv;
407         }
408
409         $passThru = array_search('--', $argv);
410         if (($passThru !== false) && ($passThru < $pos)) {
411             return $argv;
412         }
413
414         if (substr($argv[$pos], 0, 12) == '--load-from=') {
415             $this->dir = substr($argv[$pos], 12);
416         } elseif (isset($argv[$pos +1])) {
417             $this->dir = $argv[$pos +1];
418             unset($argv[$pos +1]);
419         }
420         unset($argv[$pos]);
421         // Make adjustments if '--load-from' points at a file.
422         if (is_file($this->dir) || (substr($this->dir, -4) == '.php')) {
423             $this->roboFile = basename($this->dir);
424             $this->dir = dirname($this->dir);
425             $className = basename($this->roboFile, '.php');
426             if ($className != $this->roboFile) {
427                 $this->roboClass = $className;
428             }
429         }
430         // Convert directory to a real path, but only if the
431         // path exists. We do not want to lose the original
432         // directory if the user supplied a bad value.
433         $realDir = realpath($this->dir);
434         if ($realDir) {
435             chdir($realDir);
436             $this->dir = $realDir;
437         }
438
439         return $argv;
440     }
441
442     /**
443      * @param string $needle
444      * @param string[] $haystack
445      *
446      * @return bool|int
447      */
448     protected function arraySearchBeginsWith($needle, $haystack)
449     {
450         for ($i = 0; $i < count($haystack); ++$i) {
451             if (substr($haystack[$i], 0, strlen($needle)) == $needle) {
452                 return $i;
453             }
454         }
455         return false;
456     }
457
458     public function shutdown()
459     {
460         $error = error_get_last();
461         if (!is_array($error)) {
462             return;
463         }
464         $this->writeln(sprintf("<error>ERROR: %s \nin %s:%d\n</error>", $error['message'], $error['file'], $error['line']));
465     }
466
467     /**
468      * This is just a proxy error handler that checks the current error_reporting level.
469      * In case error_reporting is disabled the error is marked as handled, otherwise
470      * the normal internal error handling resumes.
471      *
472      * @return bool
473      */
474     public function handleError()
475     {
476         if (error_reporting() === 0) {
477             return true;
478         }
479         return false;
480     }
481
482     /**
483      * @return string
484      */
485     public function getSelfUpdateRepository()
486     {
487         return $this->selfUpdateRepository;
488     }
489
490     /**
491      * @param $selfUpdateRepository
492      *
493      * @return $this
494      */
495     public function setSelfUpdateRepository($selfUpdateRepository)
496     {
497         $this->selfUpdateRepository = $selfUpdateRepository;
498         return $this;
499     }
500
501     /**
502      * @param \Composer\Autoload\ClassLoader $classLoader
503      *
504      * @return $this
505      */
506     public function setClassLoader(ClassLoader $classLoader)
507     {
508         $this->classLoader = $classLoader;
509         return $this;
510     }
511
512     /**
513      * @param string $relativeNamespace
514      *
515      * @return $this
516      */
517     public function setRelativePluginNamespace($relativeNamespace)
518     {
519         $this->relativePluginNamespace = $relativeNamespace;
520         return $this;
521     }
522 }