More tidying.
[yaffs-website] / vendor / drush / drush / includes / complete.inc
1 <?php
2
3 use Consolidation\AnnotatedCommand\Parser\CommandInfo;
4 use Drush\Log\LogLevel;
5
6 /**
7  * @file
8  *
9  * Provide completion output for shells.
10  *
11  * This is not called directly, but by shell completion scripts specific to
12  * each shell (bash, csh etc). These run whenever the user triggers completion,
13  * typically when pressing <tab>. The shell completion scripts should call
14  * "drush complete <text>", where <text> is the full command line, which we take
15  * as input and use to produce a list of possible completions for the
16  * current/next word, separated by newlines. Typically, when multiple
17  * completions are returned the shell will display them to the user in a concise
18  * format - but when a single completion is returned it will autocomplete.
19  *
20  * We provide completion for site aliases, commands, shell aliases, options,
21  * engines and arguments. Displaying all of these when the last word has no
22  * characters yet is not useful, as there are too many items. Instead we filter
23  * the possible completions based on position, in a similar way to git.
24  * For example:
25  * - We only display site aliases and commands if one is not already present.
26  * - We only display options if the user has already entered a hyphen.
27  * - We only display global options before a command is entered, and we only
28  *   display command specific options after the command (Drush itself does not
29  *   care about option placement, but this approach keeps things more concise).
30  *
31  * Below is typical output of complete in different situations. Tokens in square
32  * brackets are optional, and [word] will filter available options that start
33  * with the same characters, or display all listed options if empty.
34  * drush --[word] : Output global options
35  * drush [word] : Output site aliases, sites, commands and shell aliases
36  * drush [@alias] [word] : Output commands
37  * drush [@alias] command [word] : Output command specific arguments
38  * drush [@alias] command --[word] : Output command specific options
39  *
40  * Because the purpose of autocompletion is to make the command line more
41  * efficient for users we need to respond quickly with the list of completions.
42  * To do this, we call drush_complete() early in the Drush bootstrap, and
43  * implement a simple caching system.
44  *
45  * To generate the list of completions, we set up the Drush environment as if
46  * the command was called on it's own, parse the command using the standard
47  * Drush functions, bootstrap the site (if any) and collect available
48  * completions from various sources. Because this can be somewhat slow, we cache
49  * the results. The cache strategy aims to balance accuracy and responsiveness:
50  * - We cache per site, if a site is available.
51  * - We generate (and cache) everything except arguments at the same time, so
52  *   subsequent completions on the site don't need any bootstrap.
53  * - We generate and cache arguments on-demand, since these can often be
54  *   expensive to generate. Arguments are also cached per-site.
55  *
56  * For argument completions, commandfiles can implement
57  * COMMANDFILE_COMMAND_complete() returning an array containing a key 'values'
58  * containing an array of all possible argument completions for that command.
59  * For example, return array('values' => array('aardvark', 'aardwolf')) offers
60  * the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the
61  * letters 'aardw' are already present. Since command arguments are cached,
62  * commandfiles can bootstrap a site or perform other somewhat time consuming
63  * activities to retrieve the list of possible arguments. Commands can also
64  * clear the cache (or just the "arguments" cache for their command) when the
65  * completion results have likely changed - see drush_complete_cache_clear().
66  *
67  * Commandfiles can also return a special optional element in their array with
68  * the key 'files' that contains an array of patterns/flags for the glob()
69  * function. These are used to produce file and directory completions (the
70  * results of these are not cached, since this is a fast operation).
71  * See http://php.net/glob for details of valid patterns and flags.
72  * For example the following will complete the command arguments on all
73  * directories, as well as files ending in tar.gz:
74  *   return array(
75  *         'files' => array(
76  *           'directories' => array(
77  *             'pattern' => '*',
78  *             'flags' => GLOB_ONLYDIR,
79  *           ),
80  *           'tar' => array(
81  *             'pattern' => '*.tar.gz',
82  *           ),
83  *         ),
84  *       );
85  *
86  * To check completion results without needing to actually trigger shell
87  * completion, you can call this manually using a command like:
88  *
89  * drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]...
90  *
91  * If you want to simulate the results of pressing tab after a space (i.e.
92  * and empty last word, include '' on the end of your command:
93  *
94  * drush --early=includes/complete.inc [--complete-debug] drush ''
95  */
96
97 /**
98  * Produce autocomplete output.
99  *
100  * Determine position (is there a site-alias or command set, and are we trying
101  * to complete an option). Then produce a list of completions for the last word
102  * and output them separated by newlines.
103  */
104 function drush_early_complete() {
105   // We use a distinct --complete-debug option to avoid unwanted debug messages
106   // being printed when users use this option for other purposes in the command
107   // they are trying to complete.
108   drush_set_option(LogLevel::DEBUG, FALSE);
109   if (drush_get_option('complete-debug', FALSE)) {
110     drush_set_context('DRUSH_DEBUG', TRUE);
111   }
112   // Set up as if we were running the command, and attempt to parse.
113   $argv = drush_complete_process_argv();
114   if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
115     $set_sitealias_name = $alias;
116     $set_sitealias = drush_sitealias_get_record($alias);
117   }
118
119   // Arguments have now had site-aliases and options removed, so we take the
120   // first item as our command. We need to know if the command is valid, so that
121   // we know if we are supposed to complete an in-progress command name, or
122   // arguments for a command. We do this by checking against our per-site cache
123   // of command names (which will only bootstrap if the cache needs to be
124   // regenerated), rather than drush_parse_command() which always requires a
125   // site bootstrap.
126   $arguments = drush_get_arguments();
127   $set_command_name = NULL;
128   if (isset($arguments[0]) && in_array($arguments[0] . ' ', drush_complete_get('command-names'))) {
129     $set_command_name = $arguments[0];
130   }
131   // We unset the command if it is "help" but that is not explicitly found in
132   // args, since Drush sets the command to "help" if no command is specified,
133   // which prevents completion of global options.
134   if ($set_command_name == 'help' && !array_search('help', $argv)) {
135     $set_command_name = NULL;
136   }
137
138   // Determine the word we are trying to complete, and if it is an option.
139   $last_word = end($argv);
140   $word_is_option = FALSE;
141   if (!empty($last_word) && $last_word[0] == '-') {
142     $word_is_option = TRUE;
143     $last_word = ltrim($last_word, '-');
144   }
145
146   $completions = array();
147   if (!$set_command_name) {
148     // We have no command yet.
149     if ($word_is_option) {
150       // Include global option completions.
151       $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options')));
152     }
153     else {
154       if (empty($set_sitealias_name)) {
155         // Include site alias completions.
156         $completions += drush_complete_match($last_word, drush_complete_get('site-aliases'));
157       }
158       // Include command completions.
159       $completions += drush_complete_match($last_word, drush_complete_get('command-names'));
160     }
161   }
162   else {
163     if ($last_word == $set_command_name) {
164       // The user just typed a valid command name, but we still do command
165       // completion, as there may be other commands that start with the detected
166       // command (e.g. "make" is a valid command, but so is "make-test").
167       // If there is only the single matching command, this will include in the
168       // completion list so they get a space inserted, confirming it is valid.
169       $completions += drush_complete_match($last_word, drush_complete_get('command-names'));
170     }
171     else if ($word_is_option) {
172       // Include command option completions.
173       $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options', $set_command_name)));
174     }
175     else {
176       // Include command argument completions.
177       $argument_completion = drush_complete_get('arguments', $set_command_name);
178       if (isset($argument_completion['values'])) {
179         $completions += drush_complete_match($last_word, $argument_completion['values']);
180       }
181       if (isset($argument_completion['files'])) {
182         $completions += drush_complete_match_file($last_word, $argument_completion['files']);
183       }
184     }
185   }
186
187   if (!empty($completions)) {
188     sort($completions);
189     return implode("\n", $completions);
190   }
191   return TRUE;
192 }
193
194 /**
195  * This function resets the raw arguments so that Drush can parse the command as
196  * if it was run directly. The shell complete command passes the
197  * full command line as an argument, and the --early and --complete-debug
198  * options have to come before that, and the "drush" bash script will add a
199  * --php option on the end, so we end up with something like this:
200  *
201  * /path/to/drush.php --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... --php=/usr/bin/php
202  *
203  * Note that "drush" occurs twice, and also that the second occurrence could be
204  * an alias, so we can't easily use it as to detect the start of the actual
205  * command. Hence our approach is to remove the initial "drush" and then any
206  * options directly following that - what remains is then the command we need
207  * to complete - i.e.:
208  *
209  * drush [@alias] [command]...
210  *
211  * Note that if completion is initiated following a space an empty argument is
212  * added to argv. So in that case argv looks something like this:
213  * array (
214  *  '0' => '/path/to/drush.php',
215  *  '1' => '--early=includes/complete.inc',
216  *  '2' => 'drush',
217  *  '3' => 'topic',
218  *  '4' => '',
219  *  '5' => '--php=/usr/bin/php',
220  * );
221  *
222  * @return $args
223  *   Array of arguments (argv), excluding the initial command and options
224  *   associated with the complete call.
225  *   array (
226  *    '0' => 'drush',
227  *    '1' => 'topic',
228  *    '2' => '',
229  *   );
230  */
231 function drush_complete_process_argv() {
232   $argv = drush_get_context('argv');
233   // Remove the first argument, which will be the "drush" command.
234   array_shift($argv);
235   while (substr($arg = array_shift($argv), 0, 2) == '--') {
236     // We remove all options, until we get to a non option, which
237     // marks the start of the actual command we are trying to complete.
238   }
239   // Replace the initial argument.
240   array_unshift($argv, $arg);
241   // Remove the --php option at the end if exists (added by the "drush" shell
242   // script that is called when completion is requested).
243   if (substr(end($argv), 0, 6) == '--php=') {
244     array_pop($argv);
245   }
246   drush_set_context('argv', $argv);
247   drush_set_command(NULL);
248   // Reparse arguments, site alias, and command.
249   drush_parse_args();
250   // Ensure the base environment is configured, so tests look in the correct
251   // places.
252   _drush_preflight_base_environment();
253   // Check for and record any site alias.
254   drush_sitealias_check_arg();
255   drush_sitealias_check_site_env();
256   // We might have just changed our root--run drush_select_bootstrap_class() again.
257   $bootstrap = drush_select_bootstrap_class();
258
259   // Return the new argv for easy reference.
260   return $argv;
261 }
262
263 /**
264  * Retrieves the appropriate list of candidate completions, then filters this
265  * list using the last word that we are trying to complete.
266  *
267  * @param string $last_word
268  *   The last word in the argument list (i.e. the subject of completion).
269  * @param array $values
270  *   Array of possible completion values to filter.
271  *
272  * @return array
273  *   Array of candidate completions that start with the same characters as the
274  *   last word. If the last word is empty, return all candidates.
275  */
276 function drush_complete_match($last_word, $values) {
277   // Using preg_grep appears to be faster that strpos with array_filter/loop.
278   return preg_grep('/^' . preg_quote($last_word, '/') . '/', $values);
279 }
280
281 /**
282  * Retrieves the appropriate list of candidate file/directory completions,
283  * filtered by the last word that we are trying to complete.
284  *
285  * @param string $last_word
286  *   The last word in the argument list (i.e. the subject of completion).
287  * @param array $files
288  *   Array of file specs, each with a pattern and flags subarray.
289  *
290  * @return array
291  *   Array of candidate file/directory completions that start with the same
292  *   characters as the last word. If the last word is empty, return all
293  *   candidates.
294  */
295 function drush_complete_match_file($last_word, $files) {
296   $return = array();
297   if ($last_word[0] == '~') {
298     // Complete does not do tilde expansion, so we do it here.
299     // We shell out (unquoted) to expand the tilde.
300     drush_shell_exec('echo ' . $last_word);
301     return drush_shell_exec_output();
302   }
303
304   $dir = '';
305   if (substr($last_word, -1) == '/' && is_dir($last_word)) {
306     // If we exactly match a trailing directory, then we use that as the base
307     // for the listing. We only do this if a trailing slash is present, since at
308     // this stage it is still possible there are other directories that start
309     // with this string.
310     $dir = $last_word;
311   }
312   else {
313     // Otherwise we discard the last part of the path (this is matched against
314     // the list later), and use that as our base.
315     $dir = dirname($last_word);
316     if (empty($dir) || $dir == '.' && $last_word != '.' && substr($last_word, 0, 2) != './') {
317       // We are looking at the current working directory, so unless the user is
318       // actually specifying a leading dot we leave the path empty.
319       $dir = '';
320     }
321     else {
322       // In all other cases we need to add a trailing slash.
323       $dir .= '/';
324     }
325   }
326
327   foreach ($files as $spec) {
328     // We always include GLOB_MARK, as an easy way to detect directories.
329     $flags = GLOB_MARK;
330     if (isset($spec['flags'])) {
331       $flags = $spec['flags'] | GLOB_MARK;
332     }
333     $listing = glob($dir . $spec['pattern'], $flags);
334     $return = array_merge($return, drush_complete_match($last_word, $listing));
335   }
336   // If we are returning a single item (which will become part of the final
337   // command), we need to use the full path, and we need to escape it
338   // appropriately.
339   if (count($return) == 1) {
340     // Escape common shell metacharacters (we don't use escapeshellarg as it
341     // single quotes everything, even when unnecessary).
342     $item = array_pop($return);
343     $item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item);
344     if (substr($item, -1) !== '/') {
345       // Insert a space after files, since the argument is complete.
346       $item = $item . ' ';
347     }
348     $return = array($item);
349   }
350   else {
351     $firstchar = TRUE;
352     if ($last_word[0] == '/') {
353       // If we are working with absolute paths, we need to check if the first
354       // character of all the completions matches. If it does, then we pass a
355       // full path for each match, so the shell completes as far as it can,
356       // matching the behaviour with relative paths.
357       $pos = strlen($last_word);
358       foreach ($return as $id => $item) {
359         if ($item[$pos] !== $return[0][$pos]) {
360           $firstchar = FALSE;
361           continue;
362         }
363       }
364     }
365     foreach ($return as $id => $item) {
366       // For directories we leave the path alone.
367       $slash_pos = strpos($last_word, '/');
368       if ($slash_pos === 0 && $firstchar) {
369         // With absolute paths where completions share initial characters, we
370         // pass in a resolved path.
371         $return[$id] = realpath($item);
372       }
373       else if ($slash_pos !== FALSE && $dir != './') {
374         // For files, we pass only the file name, ignoring the false match when
375         // the user is using a single dot relative path.
376         $return[$id] = basename($item);
377       }
378     }
379   }
380   return $return;
381 }
382
383 /**
384  * Simple helper function to ensure options are properly hyphenated before we
385  * return them to the user (we match against the non-hyphenated versions
386  * internally).
387  *
388  * @param array $options
389  *   Array of unhyphenated option names.
390  *
391  * @return array
392  *   Array of hyphenated option names.
393  */
394 function drush_hyphenate_options($options) {
395   foreach ($options as $key => $option) {
396     $options[$key] = '--' . ltrim($option, '--');
397   }
398   return $options;
399 }
400
401 /**
402  * Retrieves from cache, or generates a listing of completion candidates of a
403  * specific type (and optionally, command).
404  *
405  * @param string $type
406  *   String indicating type of completions to return.
407  *   See drush_complete_rebuild() for possible keys.
408  * @param string $command
409  *   An optional command name if command specific completion is needed.
410  *
411  * @return array
412  *   List of candidate completions.
413  */
414 function drush_complete_get($type, $command = NULL) {
415   static $complete;
416   if (empty($command)) {
417     // Quick return if we already have a complete static cache.
418     if (!empty($complete[$type])) {
419       return $complete[$type];
420     }
421     // Retrieve global items from a non-command specific cache, or rebuild cache
422     // if needed.
423     $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete');
424     if (isset($cache->data)) {
425       return $cache->data;
426     }
427     $complete = drush_complete_rebuild();
428     return $complete[$type];
429   }
430   // Retrieve items from a command specific cache.
431   $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete');
432   if (isset($cache->data)) {
433     return $cache->data;
434   }
435   // Build argument cache - built only on demand.
436   if ($type == 'arguments') {
437     return drush_complete_rebuild_arguments($command);
438   }
439   // Rebuild cache of general command specific items.
440   if (empty($complete)) {
441     $complete = drush_complete_rebuild();
442   }
443   if (!empty($complete['commands'][$command][$type])) {
444     return $complete['commands'][$command][$type];
445   }
446   return array();
447 }
448
449 /**
450  * Rebuild and cache completions for everything except command arguments.
451  *
452  * @return array
453  *   Structured array of completion types, commands and candidate completions.
454  */
455 function drush_complete_rebuild() {
456   $complete = array();
457   // Bootstrap to the site level (if possible) - commands may need to check
458   // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
459   drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
460   $commands = drush_get_commands();
461   foreach ($commands as $command_name => $command) {
462     // Add command options and suboptions.
463     $options = array_keys($command['options']);
464     foreach ($command['sub-options'] as $option => $sub_options) {
465       $options = array_merge($options, array_keys($sub_options));
466     }
467     $complete['commands'][$command_name]['options'] = $options;
468   }
469   // We treat shell aliases as commands for the purposes of completion.
470   $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_context('shell-aliases', array())));
471   $site_aliases = _drush_sitealias_all_list();
472   // TODO: Figure out where this dummy @0 alias is introduced.
473   unset($site_aliases['@0']);
474   $complete['site-aliases'] = array_keys($site_aliases);
475   $complete['options'] = array_keys(drush_get_global_options());
476
477   // We add a space following all completes. Eventually there may be some
478   // items (e.g. options that we know need values) where we don't add a space.
479   array_walk_recursive($complete, 'drush_complete_trailing_space');
480   drush_complete_cache_set($complete);
481   return $complete;
482 }
483
484 /**
485  * Helper callback function that adds a trailing space to completes in an array.
486  */
487 function drush_complete_trailing_space(&$item, $key) {
488   if (!is_array($item)) {
489     $item = (string)$item . ' ';
490   }
491 }
492
493 /**
494  * Rebuild and cache completions for command arguments.
495  *
496  * @param string $command
497  *   A specific command to retrieve and cache arguments for.
498  *
499  * @return array
500  *   Structured array of candidate completion arguments, keyed by the command.
501  */
502 function drush_complete_rebuild_arguments($command) {
503   // Bootstrap to the site level (if possible) - commands may need to check
504   // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
505   drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
506   $commands = drush_get_commands();
507   $command_info = $commands[$command];
508   if ($callback = $command_info['annotated-command-callback']) {
509     list($classname, $method) = $callback;
510     $commandInfo = new CommandInfo($classname, $method);
511     if ($callable = $commandInfo->getAnnotation('complete')) {
512       $result = call_user_func($callable);
513     }
514   }
515   else {
516     $hook = str_replace("-", "_", $command_info['command-hook']);
517     $result = drush_command_invoke_all($hook . '_complete');
518   }
519   if (isset($result['values'])) {
520     // We add a space following all completes. Eventually there may be some
521     // items (e.g. comma separated arguments) where we don't add a space.
522     array_walk($result['values'], 'drush_complete_trailing_space');
523   }
524
525   $complete = array(
526     'commands' => array(
527       $command => array(
528         'arguments' => $result,
529       )
530     )
531   );
532   drush_complete_cache_set($complete);
533   return $complete['commands'][$command]['arguments'];
534 }
535
536 /**
537  * Stores caches for completions.
538  *
539  * @param $complete
540  *   A structured array of completions, keyed by type, including a 'commands'
541  *   type that contains all commands with command specific completions keyed by
542  *   type. The array does not need to include all types - used by
543  *   drush_complete_rebuild_arguments().
544  */
545 function drush_complete_cache_set($complete) {
546   foreach ($complete as $type => $values) {
547     if ($type == 'commands') {
548       foreach ($values as $command_name => $command) {
549         foreach ($command as $command_type => $command_values) {
550           drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY);
551         }
552       }
553     }
554     else {
555       drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY);
556     }
557   }
558 }
559
560 /**
561  * Generate a cache id.
562  *
563  * @param $type
564  *   The completion type.
565  * @param $command
566  *   The command name (optional), if completions are command specific.
567  *
568  * @return string
569  *   Cache id.
570  */
571 function drush_complete_cache_cid($type, $command = NULL) {
572   // For per-site caches, we include the site root and uri/path in the cache id
573   // hash. These are quick to determine, and prevents a bootstrap to site just
574   // to get a validated root and URI. Because these are not validated, there is
575   // the possibility of cache misses/ but they should be rare, since sites are
576   // normally referred to the same way (e.g. a site alias, or using the current
577   // directory), at least within a single command completion session.
578   // We also static cache them, since we may get differing results after
579   // bootstrap, which prevents the caches from being found on the next call.
580   static $root, $site;
581   if (empty($root)) {
582     $root = drush_get_option(array('r', 'root'), drush_locate_root());
583     $site = drush_get_option(array('l', 'uri'), drush_site_path());
584   }
585   return drush_get_cid('complete', array(), array($type, $command, $root, $site));
586 }