2 namespace Drush\Preflight;
4 use Consolidation\Config\Config;
5 use Consolidation\Config\ConfigInterface;
7 use Drush\Symfony\DrushArgvInput;
8 use Drush\Utils\StringUtils;
9 use Drush\Symfony\LessStrictArgvInput;
12 * Storage for arguments preprocessed during preflight.
14 * Holds @sitealias, if present, and a limited number of global options.
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.
20 class PreflightArgs extends Config implements PreflightArgsInterface
23 * @var array $args Remaining arguments not handled by the preprocessor
28 * @var string $homeDir Path to directory to use when replacing ~ in paths
35 public function homeDir()
37 return $this->homeDir;
41 * @param string $homeDir
43 public function setHomeDir($homeDir)
45 $this->homeDir = $homeDir;
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';
58 const SIMULATE = 'simulate';
59 const BACKEND = 'backend';
60 const STRICT = 'strict';
61 const DEBUG = 'preflight-debug';
64 * PreflightArgs constructor
66 * @param array $data Initial data (not usually used)
68 public function __construct($data = [])
70 parent::__construct($data + [self::STRICT => true]);
76 public function optionsWithValues()
79 '-r=' => 'setSelectedSite',
80 '--root=' => 'setSelectedSite',
81 '--debug' => 'setDebug',
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',
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.
107 public function adjustHelpOption()
109 $drushPath = array_shift($this->args);
110 array_unshift($this->args, $drushPath, 'help');
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.
118 protected function optionConfigMap()
121 self::SIMULATE => \Robo\Config\Config::SIMULATE,
122 self::BACKEND => self::BACKEND,
123 self::LOCAL => self::DRUSH_RUNTIME_CONTEXT_NAMESPACE . '.' . self::LOCAL,
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.
132 protected function optionConfigPathMap()
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,
144 * @see Environment::exportConfigData(), which also exports information to config.
146 public function applyToConfig(ConfigInterface $config)
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));
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, []);
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);
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()));
171 public function args()
179 public function applicationPath()
181 return reset($this->args);
187 public function addArg($arg)
189 $this->args[] = $arg;
196 public function passArgs($args)
198 $this->args = array_merge($this->args, $args);
205 public function alias()
207 return $this->get(self::ALIAS);
213 public function hasAlias()
215 return $this->has(self::ALIAS);
221 public function setAlias($alias)
223 // Treat `drush @self ...` as if an alias had not been used at all.
224 if ($alias == '@self') {
227 return $this->set(self::ALIAS, $alias);
231 * Get the selected site. Here, the default will typically be the cwd.
233 public function selectedSite($default = false)
235 return $this->get(self::ROOT, $default);
238 public function setDebug($value)
240 $this->set(self::DEBUG, $value);
241 $this->addArg('-vvv');
245 * Set the selected site.
247 public function setSelectedSite($root)
249 return $this->set(self::ROOT, StringUtils::replaceTilde($root, $this->homeDir()));
253 * Get the selected uri
255 public function uri($default = false)
257 return $this->get(self::URI, $default);
263 public function setUri($uri)
265 return $this->set(self::URI, $uri);
269 * Get the config path where drush.yml files may be found
271 public function configPaths()
273 return $this->get(self::CONFIG_PATH, []);
277 * Add another location where drush.yml files may be found
279 * @param string $path
281 public function addConfigPath($path)
283 $paths = $this->configPaths();
284 $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
285 return $this->set(self::CONFIG_PATH, $paths);
289 * Add multiple additional locations where drush.yml files may be found.
291 * @param string[] $configPaths
293 public function mergeConfigPaths($configPaths)
295 $paths = $this->configPaths();
296 $merged_paths = array_merge($paths, $configPaths);
297 return $this->set(self::CONFIG_PATH, $merged_paths);
301 * Get the alias paths where drush site.site.yml files may be found
303 public function aliasPaths()
305 return $this->get(self::ALIAS_PATH, []);
309 * Set one more path where aliases may be found.
311 * @param string $path
313 public function addAliasPath($path)
315 $paths = $this->aliasPaths();
316 $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
317 return $this->set(self::ALIAS_PATH, $paths);
321 * Add multiple additional locations for alias paths.
323 * @param string $aliasPaths
325 public function mergeAliasPaths($aliasPaths)
327 $aliasPaths = array_map(
329 return StringUtils::replaceTilde($item, $this->homeDir());
333 $paths = $this->aliasPaths();
334 $merged_paths = array_merge($paths, $aliasPaths);
335 return $this->set(self::ALIAS_PATH, $merged_paths);
339 * Get the path where Drush commandfiles e.g. FooCommands.php may be found.
341 public function commandPaths()
343 return $this->get(self::COMMAND_PATH, []);
347 * Add one more path where commandfiles might be found.
349 * @param string $path
351 public function addCommandPath($path)
353 $paths = $this->commandPaths();
354 $paths[] = StringUtils::replaceTilde($path, $this->homeDir());
355 return $this->set(self::COMMAND_PATH, $paths);
359 * Add multiple paths where commandfiles might be found.
361 * @param $commanPaths
363 public function mergeCommandPaths($commandPaths)
365 $paths = $this->commandPaths();
366 $merged_paths = array_merge($paths, $commandPaths);
367 return $this->set(self::COMMAND_PATH, $merged_paths);
371 * Determine whether Drush is in "local" mode
373 public function isLocal()
375 return $this->get(self::LOCAL);
381 * @param bool $isLocal
383 public function setLocal($isLocal)
385 return $this->set(self::LOCAL, $isLocal);
389 * Determine whether Drush is in "simulated" mode.
391 public function isSimulated()
393 return $this->get(self::SIMULATE);
399 * @param bool $simulated
401 public function setSimulate($simulate)
403 return $this->set(self::SIMULATE, $simulate);
407 * Determine whether Drush was placed in simulated mode.
409 public function isBackend()
411 return $this->get(self::BACKEND);
417 * @param bool $backend
419 public function setBackend($backend)
421 return $this->set(self::BACKEND, $backend);
425 * Get the path to the coverage file.
427 public function coverageFile()
429 return $this->get(self::COVERAGE_FILE);
433 * Set the coverage file path.
437 public function setCoverageFile($coverageFile)
439 return $this->set(self::COVERAGE_FILE, StringUtils::replaceTilde($coverageFile, $this->homeDir()));
443 * Determine whether Drush is in "strict" mode or not.
445 public function isStrict()
447 return $this->get(self::STRICT);
453 * @param bool $strict
455 public function setStrict($strict)
457 return $this->set(self::STRICT, $strict);
461 * Search through the provided argv list, and return
462 * just the option name of any item that is an option.
464 * @param array $argv e.g. ['foo', '--bar=baz', 'boz']
465 * @return string[] e.g. ['bar']
467 protected function getOptionNameList($argv)
472 // Ignore configuration definitions
473 if (substr($item, 0, 2) == '-D') {
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)) {
489 * Create a Symfony Input object.
491 public function createInput()
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());
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());
509 * Read options fron STDIN during POST requests.
511 * This function will read any text from the STDIN pipe,
512 * and attempts to generate an associative array if valid
516 * An associative array of options, if successfull. Otherwise an empty array.
518 protected function readStdinOptions()
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()) {
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)
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) {
564 stream_set_blocking($fp, false);
565 $string = stream_get_contents($fp);
568 return json_decode($string, true);