33d9ac5c49cfb115a826fafb67cc39c74383c6f9
[yaffs-website] / vendor / drush / drush / src / Preflight / PreflightArgs.php
1 <?php
2 namespace Drush\Preflight;
3
4 use Consolidation\Config\Config;
5 use Consolidation\Config\ConfigInterface;
6
7 use Drush\Symfony\DrushArgvInput;
8 use Drush\Utils\StringUtils;
9 use Drush\Symfony\LessStrictArgvInput;
10
11 /**
12  * Storage for arguments preprocessed during preflight.
13  *
14  * Holds @sitealias, if present, and a limited number of global options.
15  *
16  * TODO: The methods here with >~3 lines of logic could be refactored into a couple
17  * of different classes e.g. a helper to convert preflight args to configuration,
18  * and another to prepare the input object.
19  */
20 class PreflightArgs extends Config implements PreflightArgsInterface
21 {
22     /**
23      * @var array $args Remaining arguments not handled by the preprocessor
24      */
25     protected $args;
26
27     /**
28      * @var string $homeDir Path to directory to use when replacing ~ in paths
29      */
30     protected $homeDir;
31
32     /**
33      * @return string
34      */
35     public function homeDir()
36     {
37         return $this->homeDir;
38     }
39
40     /**
41      * @param string $homeDir
42      */
43     public function setHomeDir($homeDir)
44     {
45         $this->homeDir = $homeDir;
46     }
47
48     const DRUSH_CONFIG_PATH_NAMESPACE = 'drush.paths';
49     const DRUSH_RUNTIME_CONTEXT_NAMESPACE = 'runtime.contxt';
50     const ALIAS = 'alias';
51     const ALIAS_PATH = 'alias-path';
52     const COMMAND_PATH = 'include';
53     const CONFIG_PATH = 'config';
54     const COVERAGE_FILE = 'coverage-file';
55     const LOCAL = 'local';
56     const ROOT = 'root';
57     const URI = 'uri';
58     const SIMULATE = 'simulate';
59     const BACKEND = 'backend';
60     const STRICT = 'strict';
61     const DEBUG = 'preflight-debug';
62
63     /**
64      * PreflightArgs constructor
65      *
66      * @param array $data Initial data (not usually used)
67      */
68     public function __construct($data = [])
69     {
70         parent::__construct($data + [self::STRICT => true]);
71     }
72
73     /**
74      * @inheritdoc
75      */
76     public function optionsWithValues()
77     {
78         return [
79             '-r=' => 'setSelectedSite',
80             '--root=' => 'setSelectedSite',
81             '--debug' => 'setDebug',
82             '-d' => 'setDebug',
83             '-vv' => 'setDebug',
84             '-vvv' => 'setDebug',
85             '-l=' => 'setUri',
86             '--uri=' => 'setUri',
87             '-c=' => 'addConfigPath',
88             '--config=' => 'addConfigPath',
89             '--alias-path=' => 'addAliasPath',
90             '--include=' => 'addCommandPath',
91             '--local' => 'setLocal',
92             '--simulate' => 'setSimulate',
93             '-s' => 'setSimulate',
94             '--backend' => 'setBackend',
95             '--drush-coverage=' => 'setCoverageFile',
96             '--strict=' => 'setStrict',
97             '--help' => 'adjustHelpOption',
98             '-h' => 'adjustHelpOption',
99         ];
100     }
101
102     /**
103      * If the user enters '--help' or '-h', thrown that
104      * option away and add a 'help' command to the beginning
105      * of the argument list.
106      */
107     public function adjustHelpOption()
108     {
109         $drushPath = array_shift($this->args);
110         array_unshift($this->args, $drushPath, 'help');
111     }
112
113     /**
114      * Map of option key to the corresponding config key to store the
115      * preflight option in. The values of the config items in this map
116      * must be BOOLEANS or STRINGS.
117      */
118     protected function optionConfigMap()
119     {
120         return [
121             self::SIMULATE =>       \Robo\Config\Config::SIMULATE,
122             self::BACKEND =>        self::BACKEND,
123             self::LOCAL =>          self::DRUSH_RUNTIME_CONTEXT_NAMESPACE . '.' . self::LOCAL,
124         ];
125     }
126
127     /**
128      * Map of path option keys to the corresponding config key to store the
129      * preflight option in. The values of the items in this map must be
130      * STRINGS or ARRAYS OF STRINGS.
131      */
132     protected function optionConfigPathMap()
133     {
134         return [
135             self::ALIAS_PATH =>     self::DRUSH_CONFIG_PATH_NAMESPACE . '.' . self::ALIAS_PATH,
136             self::CONFIG_PATH =>    self::DRUSH_CONFIG_PATH_NAMESPACE . '.' . self::CONFIG_PATH,
137             self::COMMAND_PATH =>   self::DRUSH_CONFIG_PATH_NAMESPACE . '.' . self::COMMAND_PATH,
138         ];
139     }
140
141     /**
142      * @inheritdoc
143      *
144      * @see Environment::exportConfigData(), which also exports information to config.
145      */
146     public function applyToConfig(ConfigInterface $config)
147     {
148         // Copy the relevant preflight options to the applicable configuration namespace
149         foreach ($this->optionConfigMap() as $option_key => $config_key) {
150             $config->set($config_key, $this->get($option_key));
151         }
152         // Merging as they are lists.
153         foreach ($this->optionConfigPathMap() as $option_key => $config_key) {
154             $cli_paths = $this->get($option_key, []);
155             $config_paths = (array) $config->get($config_key, []);
156
157             $merged_paths = array_unique(array_merge($cli_paths, $config_paths));
158             $config->set($config_key, $merged_paths);
159             $this->set($option_key, $merged_paths);
160         }
161
162         // Store the runtime arguments and options (sans the runtime context items)
163         // in runtime.argv et. al.
164         $config->set('runtime.argv', $this->args());
165         $config->set('runtime.options', $this->getOptionNameList($this->args()));
166     }
167
168     /**
169      * @inheritdoc
170      */
171     public function args()
172     {
173         return $this->args;
174     }
175
176     /**
177      * @inheritdoc
178      */
179     public function applicationPath()
180     {
181         return reset($this->args);
182     }
183
184     /**
185      * @inheritdoc
186      */
187     public function addArg($arg)
188     {
189         $this->args[] = $arg;
190         return $this;
191     }
192
193     /**
194      * @inheritdoc
195      */
196     public function passArgs($args)
197     {
198         $this->args = array_merge($this->args, $args);
199         return $this;
200     }
201
202     /**
203      * @inheritdoc
204      */
205     public function alias()
206     {
207         return $this->get(self::ALIAS);
208     }
209
210     /**
211      * @inheritdoc
212      */
213     public function hasAlias()
214     {
215         return $this->has(self::ALIAS);
216     }
217
218     /**
219      * @inheritdoc
220      */
221     public function setAlias($alias)
222     {
223         // Treat `drush @self ...` as if an alias had not been used at all.
224         if ($alias == '@self') {
225             $alias = '';
226         }
227         return $this->set(self::ALIAS, $alias);
228     }
229
230     /**
231      * Get the selected site. Here, the default will typically be the cwd.
232      */
233     public function selectedSite($default = false)
234     {
235         return $this->get(self::ROOT, $default);
236     }
237
238     public function setDebug($value)
239     {
240         $this->set(self::DEBUG, $value);
241         $this->addArg('-vvv');
242     }
243
244     /**
245      * Set the selected site.
246      */
247     public function setSelectedSite($root)
248     {
249         return $this->set(self::ROOT, StringUtils::replaceTilde($root, $this->homeDir()));
250     }
251
252     /**
253      * Get the selected uri
254      */
255     public function uri($default = false)
256     {
257         return $this->get(self::URI, $default);
258     }
259
260     /**
261      * Set the uri option
262      */
263     public function setUri($uri)
264     {
265         return $this->set(self::URI, $uri);
266     }
267
268     /**
269      * Get the config path where drush.yml files may be found
270      */
271     public function configPaths()
272     {
273         return $this->get(self::CONFIG_PATH, []);
274     }
275
276     /**
277      * Add another location where drush.yml files may be found
278      *
279      * @param string $path
280      */
281     public function addConfigPath($path)
282     {
283         $paths = $this->configPaths();
284         $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
285         return $this->set(self::CONFIG_PATH, $paths);
286     }
287
288     /**
289      * Add multiple additional locations where drush.yml files may be found.
290      *
291      * @param string[] $configPaths
292      */
293     public function mergeConfigPaths($configPaths)
294     {
295         $paths = $this->configPaths();
296         $merged_paths = array_merge($paths, $configPaths);
297         return $this->set(self::CONFIG_PATH, $merged_paths);
298     }
299
300     /**
301      * Get the alias paths where drush site.site.yml files may be found
302      */
303     public function aliasPaths()
304     {
305         return $this->get(self::ALIAS_PATH, []);
306     }
307
308     /**
309      * Set one more path where aliases may be found.
310      *
311      * @param string $path
312      */
313     public function addAliasPath($path)
314     {
315         $paths = $this->aliasPaths();
316         $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
317         return $this->set(self::ALIAS_PATH, $paths);
318     }
319
320     /**
321      * Add multiple additional locations for alias paths.
322      *
323      * @param string $aliasPaths
324      */
325     public function mergeAliasPaths($aliasPaths)
326     {
327         $aliasPaths = array_map(
328             function ($item) {
329                 return StringUtils::replaceTilde($item, $this->homeDir());
330             },
331             $aliasPaths
332         );
333         $paths = $this->aliasPaths();
334         $merged_paths = array_merge($paths, $aliasPaths);
335         return $this->set(self::ALIAS_PATH, $merged_paths);
336     }
337
338     /**
339      * Get the path where Drush commandfiles e.g. FooCommands.php may be found.
340      */
341     public function commandPaths()
342     {
343         return $this->get(self::COMMAND_PATH, []);
344     }
345
346     /**
347      * Add one more path where commandfiles might be found.
348      *
349      * @param string $path
350      */
351     public function addCommandPath($path)
352     {
353         $paths = $this->commandPaths();
354         $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
355         return $this->set(self::COMMAND_PATH, $paths);
356     }
357
358     /**
359      * Add multiple paths where commandfiles might be found.
360      *
361      * @param $commanPaths
362      */
363     public function mergeCommandPaths($commandPaths)
364     {
365         $paths = $this->commandPaths();
366         $merged_paths = array_merge($paths, $commandPaths);
367         return $this->set(self::COMMAND_PATH, $merged_paths);
368     }
369
370     /**
371      * Determine whether Drush is in "local" mode
372      */
373     public function isLocal()
374     {
375         return $this->get(self::LOCAL);
376     }
377
378     /**
379      * Set local mode
380      *
381      * @param bool $isLocal
382      */
383     public function setLocal($isLocal)
384     {
385         return $this->set(self::LOCAL, $isLocal);
386     }
387
388     /**
389      * Determine whether Drush is in "simulated" mode.
390      */
391     public function isSimulated()
392     {
393         return $this->get(self::SIMULATE);
394     }
395
396     /**
397      * Set simulated mode
398      *
399      * @param bool $simulated
400      */
401     public function setSimulate($simulate)
402     {
403         return $this->set(self::SIMULATE, $simulate);
404     }
405
406     /**
407      * Determine whether Drush was placed in simulated mode.
408      */
409     public function isBackend()
410     {
411         return $this->get(self::BACKEND);
412     }
413
414     /**
415      * Set backend mode
416      *
417      * @param bool $backend
418      */
419     public function setBackend($backend)
420     {
421         return $this->set(self::BACKEND, $backend);
422     }
423
424     /**
425      * Get the path to the coverage file.
426      */
427     public function coverageFile()
428     {
429         return $this->get(self::COVERAGE_FILE);
430     }
431
432     /**
433      * Set the coverage file path.
434      *
435      * @param string
436      */
437     public function setCoverageFile($coverageFile)
438     {
439         return $this->set(self::COVERAGE_FILE, StringUtils::replaceTilde($coverageFile, $this->homeDir()));
440     }
441
442     /**
443      * Determine whether Drush is in "strict" mode or not.
444      */
445     public function isStrict()
446     {
447         return $this->get(self::STRICT);
448     }
449
450     /**
451      * Set strict mode.
452      *
453      * @param bool $strict
454      */
455     public function setStrict($strict)
456     {
457         return $this->set(self::STRICT, $strict);
458     }
459
460     /**
461      * Search through the provided argv list, and return
462      * just the option name of any item that is an option.
463      *
464      * @param array $argv e.g. ['foo', '--bar=baz', 'boz']
465      * @return string[] e.g. ['bar']
466      */
467     protected function getOptionNameList($argv)
468     {
469         return array_filter(
470             array_map(
471                 function ($item) {
472                     // Ignore configuration definitions
473                     if (substr($item, 0, 2) == '-D') {
474                         return null;
475                     }
476                     // Regular expression matches:
477                     //   ^-+        # anything that begins with one or more '-'
478                     //   ([^= ]*)   # any number of characters up to the first = or space
479                     if (preg_match('#^-+([^= ]*)#', $item, $matches)) {
480                         return $matches[1];
481                     }
482                 },
483                 $argv
484             )
485         );
486     }
487
488     /**
489      * Create a Symfony Input object.
490      */
491     public function createInput()
492     {
493         // In strict mode (the default), create an ArgvInput. When
494         // strict mode is disabled, create a more forgiving input object.
495         if ($this->isStrict() && !$this->isBackend()) {
496             return new DrushArgvInput($this->args());
497         }
498
499         // If in backend mode, read additional options from stdin.
500         // TODO: Maybe reading stdin options should be the responsibility of some
501         // backend manager class? Could be called from preflight and injected here.
502         $input = new LessStrictArgvInput($this->args());
503         $input->injectAdditionalOptions($this->readStdinOptions());
504
505         return $input;
506     }
507
508     /**
509      * Read options fron STDIN during POST requests.
510      *
511      * This function will read any text from the STDIN pipe,
512      * and attempts to generate an associative array if valid
513      * JSON was received.
514      *
515      * @return
516      *   An associative array of options, if successfull. Otherwise an empty array.
517      */
518     protected function readStdinOptions()
519     {
520         // If we move this method to a backend manager, then testing for
521         // backend mode will be the responsibility of the caller.
522         if (!$this->isBackend()) {
523             return [];
524         }
525
526         $fp = fopen('php://stdin', 'r');
527         // Windows workaround: we cannot count on stream_get_contents to
528         // return if STDIN is reading from the keyboard.  We will therefore
529         // check to see if there are already characters waiting on the
530         // stream (as there always should be, if this is a backend call),
531         // and if there are not, then we will exit.
532         // This code prevents drush from hanging forever when called with
533         // --backend from the commandline; however, overall it is still
534         // a futile effort, as it does not seem that backend invoke can
535         // successfully write data to that this function can read,
536         // so the argument list and command always come out empty. :(
537         // Perhaps stream_get_contents is the problem, and we should use
538         // the technique described here:
539         //   http://bugs.php.net/bug.php?id=30154
540         // n.b. the code in that issue passes '0' for the timeout in stream_select
541         // in a loop, which is not recommended.
542         // Note that the following DOES work:
543         //   drush ev 'print(json_encode(array("test" => "XYZZY")));' | drush status --backend
544         // So, redirecting input is okay, it is just the proc_open that is a problem.
545         if (drush_is_windows()) {
546             // Note that stream_select uses reference parameters, so we need variables (can't pass a constant NULL)
547             $read = [$fp];
548             $write = null;
549             $except = null;
550             // Question: might we need to wait a bit for STDIN to be ready,
551             // even if the process that called us immediately writes our parameters?
552             // Passing '100' for the timeout here causes us to hang indefinitely
553             // when called from the shell.
554             $changed_streams = stream_select($read, $write, $except, 0);
555             // Return on error or no changed streams (0).
556             // Oh, according to http://php.net/manual/en/function.stream-select.php,
557             // stream_select will return FALSE for streams returned by proc_open.
558             // That is not applicable to us, is it? Our stream is connected to a stream
559             // created by proc_open, but is not a stream returned by proc_open.
560             if ($changed_streams < 1) {
561                 return [];
562             }
563         }
564         stream_set_blocking($fp, false);
565         $string = stream_get_contents($fp);
566         fclose($fp);
567         if (trim($string)) {
568             return json_decode($string, true);
569         }
570         return [];
571     }
572 }