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