complete.inc

  1. 8.0.x includes/complete.inc
  2. 6.x includes/complete.inc
  3. 7.x includes/complete.inc
  4. 5.x includes/complete.inc
  5. master includes/complete.inc

Provide completion output for shells.

This is not called directly, but by shell completion scripts specific to each shell (bash, csh etc). These run whenever the user triggers completion, typically when pressing <tab>. The shell completion scripts should call "drush complete <text>", where <text> is the full command line, which we take as input and use to produce a list of possible completions for the current/next word, separated by newlines. Typically, when multiple completions are returned the shell will display them to the user in a concise format - but when a single completion is returned it will autocomplete.

We provide completion for site aliases, commands, shell aliases, options, engines and arguments. Displaying all of these when the last word has no characters yet is not useful, as there are too many items. Instead we filter the possible completions based on position, in a similar way to git. For example:

  • We only display site aliases and commands if one is not already present.
  • We only display options if the user has already entered a hyphen.
  • We only display global options before a command is entered, and we only display command specific options after the command (Drush itself does not care about option placement, but this approach keeps things more concise).

Below is typical output of complete in different situations. Tokens in square brackets are optional, and [word] will filter available options that start with the same characters, or display all listed options if empty. drush --[word] : Output global options drush [word] : Output site aliases, sites, commands and shell aliases drush [@alias] [word] : Output commands drush [@alias] command [word] : Output command specific arguments drush [@alias] command --[word] : Output command specific options

Because the purpose of autocompletion is to make the command line more efficient for users we need to respond quickly with the list of completions. To do this, we call drush_complete() early in the Drush bootstrap, and implement a simple caching system.

To generate the list of completions, we set up the Drush environment as if the command was called on it's own, parse the command using the standard Drush functions, bootstrap the site (if any) and collect available completions from various sources. Because this can be somewhat slow, we cache the results. The cache strategy aims to balance accuracy and responsiveness:

  • We cache per site, if a site is available.
  • We generate (and cache) everything except arguments at the same time, so subsequent completions on the site don't need any bootstrap.
  • We generate and cache arguments on-demand, since these can often be expensive to generate. Arguments are also cached per-site.

For argument completions, commandfiles can implement COMMANDFILE_COMMAND_complete() returning an array containing a key 'values' containing an array of all possible argument completions for that command. For example, return array('values' => array('aardvark', 'aardwolf')) offers the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the letters 'aardw' are already present. Since command arguments are cached, commandfiles can bootstrap a site or perform other somewhat time consuming activities to retrieve the list of possible arguments. Commands can also clear the cache (or just the "arguments" cache for their command) when the completion results have likely changed - see drush_complete_cache_clear().

Commandfiles can also return a special optional element in their array with the key 'files' that contains an array of patterns/flags for the glob() function. These are used to produce file and directory completions (the results of these are not cached, since this is a fast operation). See http://php.net/glob for details of valid patterns and flags. For example the following will complete the command arguments on all directories, as well as files ending in tar.gz: return array( 'files' => array( 'directories' => array( 'pattern' => '*', 'flags' => GLOB_ONLYDIR, ), 'tar' => array( 'pattern' => '*.tar.gz', ), ), );

To check completion results without needing to actually trigger shell completion, you can call this manually using a command like:

drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]...

If you want to simulate the results of pressing tab after a space (i.e. and empty last word, include '' on the end of your command:

drush --early=includes/complete.inc [--complete-debug] drush ''

Functions

Namesort descending Description
drush_complete_cache_cid Generate a cache id.
drush_complete_cache_set Stores caches for completions.
drush_complete_get Retrieves from cache, or generates a listing of completion candidates of a specific type (and optionally, command).
drush_complete_match Retrieves the appropriate list of candidate completions, then filters this list using the last word that we are trying to complete.
drush_complete_match_file Retrieves the appropriate list of candidate file/directory completions, filtered by the last word that we are trying to complete.
drush_complete_process_argv This function resets the raw arguments so that Drush can parse the command as if it was run directly. The shell complete command passes the full command line as an argument, and the --early and --complete-debug options have to come before that, and…
drush_complete_rebuild Rebuild and cache completions for everything except command arguments.
drush_complete_rebuild_arguments Rebuild and cache completions for command arguments.
drush_complete_trailing_space Helper callback function that adds a trailing space to completes in an array.
drush_early_complete Produce autocomplete output.
drush_hyphenate_options Simple helper function to ensure options are properly hyphenated before we return them to the user (we match against the non-hyphenated versions internally).

File

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