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