command.inc

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

The drush command engine.

Since drush can be invoked independently of a proper Drupal installation and commands may operate across sites, a distinct command engine is needed.

It mimics the Drupal module engine in order to economize on concepts and to make developing commands as familiar as possible to traditional Drupal module developers.

Functions

Namesort descending Description
drush_adjust_args_if_shebang_script Special checking for "shebang" script handling.
drush_command Entry point for commands into the drush_invoke API
drush_commandfile_list Collect a list of all available drush command files.
drush_command_defaults
drush_command_default_options Conditionally include default options based on the command used.
drush_command_get_includes
drush_command_hook Determine whether a command file implements a hook.
drush_command_implements Determine which command files are implementing a hook.
drush_command_include Conditionally include files based on the command used.
drush_command_invoke_all Invoke a hook in all available command files that implement it.
drush_command_invoke_all_ref A drush_command_invoke_all() that wants the first parameter to be passed by reference.
drush_command_translate Translates description and other keys of a command definition.
drush_dispatch Given a command record, dispatch it as if it were the original command. Executes in the currently bootstrapped site using the current option contexts. Note that drush_dispatch will not bootstrap any further than the current command has already…
drush_enforce_requirement_bootstrap_phase Check that a command is valid for the current bootstrap phase.
drush_enforce_requirement_core Check that a command is valid for the current major version of core. Handles explicit version numbers and 'plus' numbers like 6+ (compatible with 6, 7 ...).
drush_enforce_requirement_drupal_dependencies Check that a command has its declared dependencies available or have no dependencies.
drush_enforce_requirement_drush_dependencies Check that a command has its declared drush dependencies available or have no dependencies. Drush dependencies are helpful when a command is invoking another command, or implementing its API.
drush_filename_blacklist Substrings to ignore during commandfile searching.
drush_get_commandfile_for_command Get the short commandfile name that matches the command.
drush_get_commands Get a list of all implemented commands. This invokes hook_drush_command().
drush_invoke Invoke drush api calls.
drush_invoke_args As drush_invoke, but args are passed in as an array rather than as individual function parameters.
drush_invoke_process Invoke a command in a new process, targeting the site specified by the provided site alias record.
drush_invoke_process_args Invoke a command in a new process.
drush_invoke_sitealias Invoke a command in a new process, targeting the site specified by the provided site alias record.
drush_invoke_sitealias_args Invoke a command in a new process, targeting the site specified by the provided site alias record.
drush_is_command
drush_parse_args Parse console arguments.
drush_parse_command Matches a commands array, as returned by drush_get_arguments, with the current command table.
drush_process_bootstrap_to_first_arg Process the --bootstrap-to-first-arg option, if it is present.
drush_redispatch_get_options Get the options that were passed to the current command.
drush_scan_directory Finds all files that match a given mask in a given directory. Directories and files beginning with a period are excluded; this prevents hidden files and directories (such as SVN working directories and GIT repositories) from being scanned.
drush_shift Pop an argument off of drush's argument list
_drush_add_commandfiles
_drush_command_set_default_options
_drush_command_translate Helper function for drush_command_translate().
_drush_find_commandfiles
_drush_invoke_args Variant of drush_invoke_args that allows the commandfile to be specified correctly; this allows the hook functions to be correctly determined for commands that provide a 'callback' function.
_drush_prepare_command

File

includes/command.inc
View source
  1. <?php
  2. /**
  3. * @defgroup dispatching Command dispatching functions.
  4. * @{
  5. *
  6. * These functions handle command dispatching, and can
  7. * be used to programatically invoke drush commands in
  8. * different ways.
  9. */
  10. /**
  11. * Invoke drush api calls.
  12. *
  13. * Executes the specified command with the specified arguments on the
  14. * currently bootstrapped site using the current option contexts.
  15. * Note that drush_invoke will not bootstrap any further than the
  16. * current command has already bootstrapped; therefore, you should only invoke
  17. * commands that have the same (or lower) bootstrap requirements.
  18. *
  19. * Call the correct hook for all the modules that implement it.
  20. * Additionally, the ability to rollback when an error has been encountered is also provided.
  21. * If at any point during execution, the drush_get_error() function returns anything but 0,
  22. * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
  23. * in reverse order from how they were executed. Rollbacks are also triggered any
  24. * time a hook function returns FALSE.
  25. *
  26. * This function will also trigger pre_$hook and post_$hook variants of the hook
  27. * and its rollbacks automatically.
  28. *
  29. * HOW DRUSH HOOK FUNCTIONS ARE NAMED:
  30. *
  31. * The name of the hook is composed from the name of the command and the name of
  32. * the command file that the command definition is declared in. The general
  33. * form for the hook filename is:
  34. *
  35. * drush_COMMANDFILE_COMMANDNAME
  36. *
  37. * In many cases, drush commands that are functionally part of a common collection
  38. * of similar commands will all be declared in the same file, and every command
  39. * defined in that file will start with the same command prefix. For example, the
  40. * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
  41. * In the case of "pm-enable", the command file is "pm", and and command name is
  42. * "pm-enable". When the command name starts with the same sequence of characters
  43. * as the command file, then the repeated sequence is dropped; thus, the command
  44. * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
  45. *
  46. * @param command
  47. * The drush command to execute.
  48. * @return
  49. * A boolean specifying whether or not the command was successfully completed.
  50. */
  51. function drush_invoke($command) {
  52. $args = func_get_args();
  53. array_shift($args);
  54. return drush_invoke_args($command, $args);
  55. }
  56. /**
  57. * As drush_invoke, but args are passed in as an array
  58. * rather than as individual function parameters.
  59. *
  60. * @see drush_invoke()
  61. */
  62. function drush_invoke_args($command, $args) {
  63. return _drush_invoke_args($command, $args, drush_get_commandfile_for_command($command));
  64. }
  65. /**
  66. * Variant of drush_invoke_args that allows the
  67. * commandfile to be specified correctly; this allows
  68. * the hook functions to be correctly determined for
  69. * commands that provide a 'callback' function.
  70. *
  71. * This function should not be called directly; use
  72. * @see drush_dispatch() and @see drush_invoke_args()
  73. */
  74. function _drush_invoke_args($command, $args, $defined_in_commandfile) {
  75. drush_command_include($command);
  76. // Generate the base name for the hook by converting all
  77. // dashes in the command name to underscores.
  78. $hook = str_replace("-", "_", $command);
  79. // Call the hook init function, if it exists.
  80. // If a command needs to bootstrap, it is advisable
  81. // to do so in _init; otherwise, new commandfiles
  82. // will miss out on participating in any stage that
  83. // has passed or started at the time it was discovered.
  84. $func = 'drush_' . $hook . '_init';
  85. if (function_exists($func)) {
  86. drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), 'bootstrap');
  87. call_user_func_array($func, $args);
  88. _drush_log_drupal_messages();
  89. if (drush_get_error()) {
  90. drush_log(dt('The command @command could not be initialized.', array('@command' => $command)), 'error');
  91. return FALSE;
  92. }
  93. }
  94. $rollback = FALSE;
  95. $completed = array();
  96. $available_rollbacks = array();
  97. $all_available_hooks = array();
  98. // Iterate through the different hook variations
  99. $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook");
  100. foreach ($variations as $var_hook) {
  101. // Get the list of command files.
  102. // We re-fetch the list every time through
  103. // the loop in case one of the hook function
  104. // does something that will add additional
  105. // commandfiles to the list (i.e. bootstrapping
  106. // to a higher phase will do this).
  107. $list = drush_commandfile_list();
  108. // Run all of the functions available for this variation
  109. foreach ($list as $commandfile => $filename) {
  110. $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
  111. if (($defined_in_commandfile == $commandfile) && ($commandfile . "_" == substr($var_hook . "_",0,strlen($commandfile)+ 1))) {
  112. $oldfunc = $func;
  113. $func = sprintf("drush_%s", $var_hook);
  114. if (($oldfunc != $func) && (function_exists($oldfunc))) {
  115. drush_log(dt("The drush command hook naming conventions changed in March 2010; the function !oldfunc must be renamed to !func in order to be called.", array('!oldfunc' => $oldfunc, '!func' => $func)), "error");
  116. }
  117. }
  118. if (function_exists($func)) {
  119. $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
  120. $available_rollbacks[] = $func . '_rollback';
  121. $completed[] = $func;
  122. $result = call_user_func_array($func, $args);
  123. // Only the 'main' callback can send data to backend.
  124. if ($var_hook == $hook) {
  125. // If the hook already called drush_backend_set_result,
  126. // then return that value. If it did not, then the return
  127. // value from the hook will be the value returned from
  128. // this routine.
  129. $return = drush_backend_get_result();
  130. if (empty($return)) {
  131. drush_backend_set_result($result);
  132. $return = $result;
  133. }
  134. }
  135. _drush_log_drupal_messages();
  136. if (drush_get_error() || ($result === FALSE)) {
  137. $rollback = TRUE;
  138. // break out of the foreach variations and foreach list
  139. break 2;
  140. }
  141. }
  142. else {
  143. $all_available_hooks[] = $func;
  144. }
  145. }
  146. }
  147. // If no hook functions were found, print a warning.
  148. if (empty($completed)) {
  149. $default_command_hook = sprintf("drush_%s_%s", $defined_in_commandfile, $hook);
  150. if (($defined_in_commandfile . "_" == substr($hook . "_",0,strlen($defined_in_commandfile)+ 1))) {
  151. $default_command_hook = sprintf("drush_%s", $hook);
  152. }
  153. drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning');
  154. drush_log(dt("The primary drush_invoke() hook for !command is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks.", array('!command' => $command, '!default_func' => $default_command_hook)), 'warning');
  155. }
  156. if (drush_get_option('show-invoke')) {
  157. // We show all available hooks up to and including the one that failed (or all, if there were no failures)
  158. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'ok');
  159. }
  160. if (drush_get_option('show-invoke') && !empty($available_rollbacks)) {
  161. drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'ok');
  162. }
  163. // something went wrong, we need to undo
  164. if ($rollback) {
  165. if (drush_get_option('confirm-rollback', FALSE)) {
  166. // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process.
  167. drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
  168. drush_set_context('DRUSH_NEGATIVE', FALSE);
  169. $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)'));
  170. }
  171. if ($rollback) {
  172. foreach (array_reverse($completed) as $func) {
  173. $rb_func = $func . '_rollback';
  174. if (function_exists($rb_func)) {
  175. call_user_func_array($rb_func, $args);
  176. _drush_log_drupal_messages();
  177. drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), 'rollback');
  178. }
  179. }
  180. }
  181. }
  182. return !$rollback;
  183. }
  184. /**
  185. * Given a command record, dispatch it as if it were
  186. * the original command. Executes in the currently
  187. * bootstrapped site using the current option contexts.
  188. * Note that drush_dispatch will not bootstrap any further than the
  189. * current command has already bootstrapped; therefore, you should only invoke
  190. * commands that have the same (or lower) bootstrap requirements.
  191. *
  192. * @param command
  193. * A full $command such as returned by drush_get_commands().
  194. * @param arguments
  195. * An array of argument values.
  196. *
  197. * @see drush_topic_docs_topic().
  198. */
  199. function drush_dispatch($command, $arguments = array()) {
  200. drush_set_command($command);
  201. $return = FALSE;
  202. // Set warning for Windows users. We have already loaded site-specific drushrc.
  203. drush_environment_check_os();
  204. if ($command) {
  205. // Add arguments, if this has not already been done.
  206. // (If the command was fetched from drush_parse_command,
  207. // then you cannot provide arguments to drush_dispatch.)
  208. if (empty($command['arguments'])) {
  209. _drush_prepare_command($command, $arguments);
  210. }
  211. // Add command-specific options, if applicable
  212. drush_command_default_options($command);
  213. // Print a warning if someone tries to use a deprecated alias.
  214. if (isset($command['deprecated'])) {
  215. drush_log(dt('Warning: The command name "!deprecated" is deprecated. Please use a recommended form instead (!recommended).', array('!deprecated' => $command['deprecated-name'], '!recommended' => implode(',', array_merge(array($command['command']), $command['aliases'])))), 'warning');
  216. }
  217. // Call the callback function of the active command.
  218. $return = call_user_func_array($command['callback'], $command['arguments']);
  219. }
  220. // Add a final log entry, just so a timestamp appears.
  221. drush_log(dt('Command dispatch complete'), 'notice');
  222. return $return;
  223. }
  224. /**
  225. * Invoke a command in a new process, targeting the site specified by
  226. * the provided site alias record.
  227. *
  228. * @param array $site_alias_record
  229. * The site record to execute the command on.
  230. * @param string $command_name
  231. * The command to invoke.
  232. * @param array $commandline_args
  233. * The arguments to pass to the command.
  234. * @param array $commandline_options
  235. * The options (e.g. --select) to provide to the command.
  236. * @return
  237. * If the command could not be completed successfully, FALSE.
  238. * If the command was completed, this will return an associative
  239. * array containing the results of the API call.
  240. * @see drush_backend_get_result()
  241. *
  242. * This function may also be called via its deprecated function signature
  243. * (drush-3.x compatible): drush_invoke_process($command_name, $arg1, $arg2, ...);
  244. * Instead of this old form, prefer: drush_invoke_process("@self", $command_name, array($arg1, $arg2, ...));
  245. */
  246. function drush_invoke_process($site_alias_record /*, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = array() */) {
  247. if (is_array($site_alias_record) || ($site_alias_record[0] == '@')) {
  248. $args = func_get_args();
  249. array_shift($args);
  250. $command_name = array_shift($args);
  251. $commandline_args = empty($args) ? array() : array_shift($args);
  252. $commandline_options = empty($args) ? array() : array_shift($args);
  253. // Forwards-compatibility with drush_invoke_process in Drush-5.x.
  254. $backend_options = empty($args) ? array() : array_shift($args);
  255. foreach ($backend_options as $key => $value) {
  256. $commandline_options['#' . $key] = $value;
  257. }
  258. $integrate = TRUE;
  259. return drush_invoke_sitealias_args($site_alias_record, $command_name, $commandline_args, $commandline_options, $integrate);
  260. }
  261. else {
  262. $args = func_get_args();
  263. $command_name = array_shift($args);
  264. return drush_invoke_process_args($command_name, $args);
  265. }
  266. }
  267. /**
  268. * Invoke a command in a new process.
  269. *
  270. * @param command_name
  271. * The drush command to execute.
  272. * @return
  273. * If the command could not be completed successfully, FALSE.
  274. * If the command was completed, this will return an associative
  275. * array containing the results of the API call.
  276. * @see drush_backend_get_result()
  277. * @deprecated; @see drush_invoke_process("@self", $command_name, $commandline_args, $commandline_options) for a better option
  278. */
  279. function drush_invoke_process_args($command_name, $commandline_args, $commandline_options = array()) {
  280. return drush_backend_invoke_args($command_name, $commandline_args, $commandline_options);
  281. }
  282. /**
  283. * Invoke a command in a new process, targeting the site specified by
  284. * the provided site alias record.
  285. *
  286. * @param array $site_alias_record
  287. * The site record to execute the command on.
  288. * @param string $command_name
  289. * The command to invoke.
  290. * @return
  291. * If the command could not be completed successfully, FALSE.
  292. * If the command was completed, this will return an associative
  293. * array containing the results of the API call.
  294. * @see drush_backend_get_result()
  295. * @deprecated; @see drush_invoke_process($site_alias_record, $command_name, array($arg1, $arg2, ...)) for a better option
  296. */
  297. function drush_invoke_sitealias($site_alias_record, $command_name) {
  298. $args = func_get_args();
  299. array_shift($args);
  300. array_shift($args);
  301. return drush_invoke_sitealias_args($site_alias_record, $command_name, $args);
  302. }
  303. /**
  304. * Invoke a command in a new process, targeting the site specified by
  305. * the provided site alias record.
  306. *
  307. * @param array $site_alias_record
  308. * The site record to execute the command on.
  309. * @param string $command_name
  310. * The command to invoke.
  311. * @param array $commandline_args
  312. * The arguments to pass to the command.
  313. * @param array $commandline_options
  314. * The options (e.g. --select) to provide to the command.
  315. * @param integrate
  316. * @see drush_backend_invoke
  317. * @return
  318. * If the command could not be completed successfully, FALSE.
  319. * If the command was completed, this will return an associative
  320. * array containing the results of the API call.
  321. * @see drush_backend_get_result()
  322. * @deprecated; @see drush_invoke_process($site_alias_record, $command_name, $commandline_args, $commandline_options) for a better option
  323. */
  324. function drush_invoke_sitealias_args($site_alias_record, $command_name, $commandline_args, $commandline_options = array(), $integrate = FALSE) {
  325. // If the first parameter is not a site alias record,
  326. // then presume it is an alias name, and try to look up
  327. // the alias record.
  328. if (!is_array($site_alias_record)) {
  329. $site_alias_record = drush_sitealias_get_record($site_alias_record);
  330. }
  331. return drush_do_site_command($site_alias_record, $command_name, $commandline_args, $commandline_options, $integrate);
  332. }
  333. /**
  334. * Get the options that were passed to the current command.
  335. *
  336. * This function returns an array that contains all of the options
  337. * that are appropriate for forwarding along to one of the drush_invoke_*_args
  338. * functions.
  339. *
  340. * @return
  341. * An associative array of option key => value pairs.
  342. */
  343. function drush_redispatch_get_options() {
  344. // Start off by taking everything from the site alias and command line
  345. // ('cli' context)
  346. $options = array_merge(drush_get_context('alias'), drush_get_context('cli'));
  347. $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys()));
  348. unset($options['command-specific']);
  349. unset($options['path-aliases']);
  350. // If we can parse the current command, then examine all contexts
  351. // in order for any option that is directly related to the current command
  352. $command = drush_parse_command();
  353. if (is_array($command)) {
  354. foreach ($command['options'] as $key => $value) {
  355. // Strip leading --
  356. $key = ltrim($key, '-');
  357. $value = drush_get_option($key);
  358. if (isset($value)) {
  359. $options[$key] = $value;
  360. }
  361. }
  362. }
  363. // 'php', if needed, will be included in DRUSH_COMMAND. If DRUSH_COMMAND
  364. // is not used (e.g. when calling a remote instance of drush), then --php
  365. // should not be passed along.
  366. unset($options['php']);
  367. // If --bootstrap-to-first-arg is specified, do not
  368. // pass it along to remote commands.
  369. unset($options['bootstrap-to-first-arg']);
  370. return $options;
  371. }
  372. /**
  373. * @} End of "defgroup dispatching".
  374. */
  375. /**
  376. * @file
  377. * The drush command engine.
  378. *
  379. * Since drush can be invoked independently of a proper Drupal
  380. * installation and commands may operate across sites, a distinct
  381. * command engine is needed.
  382. *
  383. * It mimics the Drupal module engine in order to economize on
  384. * concepts and to make developing commands as familiar as possible
  385. * to traditional Drupal module developers.
  386. */
  387. /**
  388. * Parse console arguments.
  389. */
  390. function drush_parse_args() {
  391. $args = drush_get_context('argv');
  392. // TODO: commandfiles should be able to extend this list.
  393. static $arg_opts = array('c', 'u', 'r', 'l', 'i');
  394. // Check to see if we were executed via a "#!/usr/bin/env drush" script
  395. drush_adjust_args_if_shebang_script($args);
  396. // Now process the command line arguments. We will divide them
  397. // into options (starting with a '-') and arguments.
  398. $arguments = $options = array();
  399. for ($i = 1; $i < count($args); $i++) {
  400. $opt = $args[$i];
  401. // Is the arg an option (starting with '-')?
  402. if ($opt{0} == "-" && strlen($opt) != 1) {
  403. // Do we have multiple options behind one '-'?
  404. if (strlen($opt) > 2 && $opt{1} != "-") {
  405. // Each char becomes a key of its own.
  406. for ($j = 1; $j < strlen($opt); $j++) {
  407. $options[substr($opt, $j, 1)] = true;
  408. }
  409. }
  410. // Do we have a longopt (starting with '--')?
  411. elseif ($opt{1} == "-") {
  412. if ($pos = strpos($opt, '=')) {
  413. $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
  414. }
  415. else {
  416. $options[substr($opt, 2)] = true;
  417. }
  418. }
  419. else {
  420. $opt = substr($opt, 1);
  421. // Check if the current opt is in $arg_opts (= has to be followed by an argument).
  422. if ((in_array($opt, $arg_opts))) {
  423. if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
  424. drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
  425. }
  426. $options[$opt] = $args[$i + 1];
  427. $i++;
  428. }
  429. else {
  430. $options[$opt] = true;
  431. }
  432. }
  433. }
  434. // If it's not an option, it's a command.
  435. else {
  436. $arguments[] = $opt;
  437. }
  438. }
  439. // If no arguments are specified, then the command will
  440. // be either 'help' or 'version' (the later if --version is specified)
  441. if (!sizeof($arguments)) {
  442. if (array_key_exists('version', $options)) {
  443. $arguments = array('version');
  444. }
  445. else {
  446. $arguments = array('help');
  447. }
  448. }
  449. // Handle the "@shift" alias, if present
  450. drush_process_bootstrap_to_first_arg($arguments);
  451. drush_set_arguments($arguments);
  452. drush_set_context('cli', $options);
  453. }
  454. /**
  455. * Get the short commandfile name that matches the
  456. * command.
  457. *
  458. * @param $command
  459. * The name of the command (e.g. search-index)
  460. * @return String
  461. * The short commandfile name where that command was
  462. * defined (e.g. search, not search.drush.inc)
  463. */
  464. function drush_get_commandfile_for_command($command) {
  465. $commandfile = FALSE;
  466. $commands = drush_get_commands();
  467. if (array_key_exists($command, $commands)) {
  468. $commandfile = $commands[$command]['commandfile'];
  469. }
  470. return $commandfile;
  471. }
  472. /**
  473. * Pop an argument off of drush's argument list
  474. */
  475. function drush_shift() {
  476. $arguments = drush_get_arguments();
  477. $result = NULL;
  478. if (!empty($arguments)) {
  479. // The php-script command uses the DRUSH_SHIFT_SKIP
  480. // context to cause drush_shift to skip the 'php-script'
  481. // command and the script path argument when it is
  482. // called from the user script.
  483. $skip_count = drush_get_context('DRUSH_SHIFT_SKIP');
  484. if (is_numeric($skip_count)) {
  485. for ($i = 0; $i < $skip_count; $i++) {
  486. array_shift($arguments);
  487. }
  488. $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0);
  489. }
  490. $result = array_shift($arguments);
  491. drush_set_arguments($arguments);
  492. }
  493. return $result;
  494. }
  495. /**
  496. * Special checking for "shebang" script handling.
  497. *
  498. * If there is a file 'script.php' that begins like so:
  499. * #!/path/to/drush
  500. * Then $args will be:
  501. * /path/to/drush /path/to/script userArg1 userArg2 ...
  502. * If it instead starts like this:
  503. * #!/path/to/drush --flag php-script
  504. * Then $args will be:
  505. * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ...
  506. * (Note that execve does not split the parameters from
  507. * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29)
  508. * When drush is called via one of the "shebang" lines above,
  509. * the first or second parameter will be the full path
  510. * to the "shebang" script file -- and if the path to the
  511. * script is in the second position, then we will expect that
  512. * the argument in the first position must begin with a
  513. * '@' (alias) or '-' (flag). Under ordinary circumstances,
  514. * we do not expect that the drush command must come before
  515. * any argument that is the full path to a file. We use
  516. * this assumption to detect "shebang" script execution.
  517. */
  518. function drush_adjust_args_if_shebang_script(&$args) {
  519. if (_drush_is_drush_shebang_script($args[1])) {
  520. // If $args[1] is a drush "shebang" script, we will insert
  521. // the option "--bootstrap-to-first-arg" and the command
  522. // "php-script" at the beginning of @args, so the command
  523. // line args become:
  524. // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ...
  525. drush_set_option('bootstrap-to-first-arg', TRUE);
  526. array_splice($args, 1, 0, array('php-script'));
  527. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  528. }
  529. elseif (((strpos($args[1], ' ') !== FALSE) || (!ctype_alnum($args[1][0]))) && (_drush_is_drush_shebang_script($args[2]))) {
  530. // If $args[2] is a drush "shebang" script, we will insert
  531. // the space-exploded $arg[1] in place of $arg[1], so the
  532. // command line args become:
  533. // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ...
  534. // If none of the script arguments look like a drush command,
  535. // then we will insert "php-script" as the default command to
  536. // execute.
  537. $script_args = explode(' ', $args[1]);
  538. $has_command = FALSE;
  539. foreach ($script_args as $script_arg) {
  540. if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) {
  541. $has_command = TRUE;
  542. }
  543. }
  544. if (!$has_command) {
  545. $script_args[] = 'php-script';
  546. }
  547. array_splice($args, 1, 1, $script_args);
  548. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  549. }
  550. }
  551. /**
  552. * Process the --bootstrap-to-first-arg option, if it is present.
  553. *
  554. * This option checks to see if the first user-provided argument is an alias
  555. * or site specification; if it is, it will be shifted into the first argument
  556. * position, where it will specify the site to bootstrap. The result of this
  557. * is that if your shebang line looks like this:
  558. *
  559. * #!/path/to/drush --bootstrap-to-first-arg php-script
  560. *
  561. * Then when you run that script, you can optionally provide an alias such
  562. * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1
  563. * scriptarg2). Since this is the behavior that one would usually want,
  564. * it is default behavior for a canonical script. That is, a script
  565. * with a simple shebang line, like so:
  566. *
  567. * #!/path/to/drush
  568. *
  569. * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore
  570. * behave exactly like the first example. To write a script that does not
  571. * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly
  572. * included, like so:
  573. *
  574. * #!/path/to/drush php-script
  575. */
  576. function drush_process_bootstrap_to_first_arg(&$arguments) {
  577. if (drush_get_option('bootstrap-to-first-arg', FALSE)) {
  578. $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE);
  579. if (sizeof($arguments) >= $shift_alias_pos) {
  580. $shifted_alias = $arguments[$shift_alias_pos];
  581. $alias_record = drush_sitealias_get_record($shifted_alias);
  582. if (!empty($alias_record)) {
  583. // Move the alias we shifted from its current position
  584. // in the argument list to the front of the list
  585. array_splice($arguments, $shift_alias_pos, 1);
  586. array_unshift($arguments, $shifted_alias);
  587. }
  588. }
  589. }
  590. }
  591. /**
  592. * Get a list of all implemented commands.
  593. * This invokes hook_drush_command().
  594. *
  595. * @return
  596. * Associative array of currently active command descriptors.
  597. *
  598. */
  599. function drush_get_commands() {
  600. $commands = $available_commands = array();
  601. $list = drush_commandfile_list();
  602. foreach ($list as $commandfile => $path) {
  603. if (drush_command_hook($commandfile, 'drush_command')) {
  604. $function = $commandfile . '_drush_command';
  605. $result = $function();
  606. foreach ((array)$result as $key => $command) {
  607. // Add some defaults and normalize the command descriptor
  608. $command += drush_command_defaults($key, $commandfile, $path);
  609. // Translate command.
  610. drush_command_translate($command);
  611. // If command callback function name begins with "drush_$commandfile_",
  612. // then fix up the command entry so that drush_invoke will be
  613. // called by way of drush_command. This will cause all
  614. // of the applicable hook functions to be called for the
  615. // command when it is invoked. If the callback function does
  616. // -not- begin with its commandfile name, then it will be
  617. // called directly by drush_dispatch, and no hook functions
  618. // will be called (e.g. you cannot hook drush_print_file).
  619. if ($command['callback'] != 'drush_command') {
  620. $required_command_prefix = 'drush_' . $commandfile . '_';
  621. if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) {
  622. $command['command-hook'] = substr($command['callback'], strlen('drush_'));
  623. $command['callback'] = 'drush_command';
  624. }
  625. }
  626. $commands[$key] = $command;
  627. // For every alias, make a copy of the command and store it in the command list
  628. // using the alias as a key
  629. if (isset($command['aliases']) && count($command['aliases'])) {
  630. foreach ($command['aliases'] as $alias) {
  631. $commands[$alias] = $command;
  632. $commands[$alias]['is_alias'] = TRUE;
  633. }
  634. }
  635. // Do the same operation on the deprecated aliases.
  636. if (isset($command['deprecated-aliases']) && count($command['deprecated-aliases'])) {
  637. foreach ($command['deprecated-aliases'] as $alias) {
  638. $commands[$alias] = $command;
  639. $commands[$alias]['is_alias'] = TRUE;
  640. $commands[$alias]['deprecated'] = TRUE;
  641. $commands[$alias]['deprecated-name'] = $alias;
  642. }
  643. }
  644. }
  645. }
  646. }
  647. return drush_set_context('DRUSH_COMMANDS', $commands);
  648. }
  649. function drush_command_defaults($key, $commandfile, $path) {
  650. return array(
  651. 'command' => $key,
  652. 'command-hook' => $key,
  653. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
  654. 'callback arguments' => array(),
  655. 'commandfile' => $commandfile,
  656. 'path' => dirname($path),
  657. 'engines' => array(), // Helpful for drush_show_help().
  658. 'callback' => 'drush_command',
  659. 'description' => NULL,
  660. 'sections' => array(
  661. 'examples' => 'Examples',
  662. 'arguments' => 'Arguments',
  663. 'options' => 'Options',
  664. ),
  665. 'arguments' => array(),
  666. 'options' => array(),
  667. 'examples' => array(),
  668. 'aliases' => array(),
  669. 'deprecated-aliases' => array(),
  670. 'core' => array(),
  671. 'scope' => 'site',
  672. 'drupal dependencies' => array(),
  673. 'drush dependencies' => array(),
  674. 'bootstrap_errors' => array(),
  675. 'topics' => array(),
  676. 'hidden' => FALSE,
  677. );
  678. }
  679. /**
  680. * Translates description and other keys of a command definition.
  681. *
  682. * @param $command
  683. * A command definition.
  684. */
  685. function drush_command_translate(&$command) {
  686. $command['description'] = _drush_command_translate($command['description']);
  687. $keys = array('arguments', 'options', 'examples', 'engines', 'sections');
  688. foreach ($keys as $key) {
  689. foreach ($command[$key] as $k => $v) {
  690. $command[$key][$k] = _drush_command_translate($v);
  691. }
  692. }
  693. }
  694. /**
  695. * Helper function for drush_command_translate().
  696. *
  697. * @param $source
  698. * String or array.
  699. */
  700. function _drush_command_translate($source) {
  701. return is_array($source) ? call_user_func_array('dt', $source) : dt($source);
  702. }
  703. /**
  704. * Matches a commands array, as returned by drush_get_arguments, with the
  705. * current command table.
  706. *
  707. * Note that not all commands may be discoverable at the point-of-call,
  708. * since Drupal modules can ship commands as well, and they are
  709. * not available until after bootstrapping.
  710. *
  711. * drush_parse_command returns a normalized command descriptor, which
  712. * is an associative array. Some of its entries are:
  713. * - callback arguments: an array of arguments to pass to the calback.
  714. * - description: description of the command.
  715. * - arguments: an array of arguments that are understood by the command. for help texts.
  716. * - options: an array of options that are understood by the command. for help texts.
  717. * - examples: an array of examples that are understood by the command. for help texts.
  718. * - scope: one of 'system', 'project', 'site'.
  719. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
  720. * - core: Drupal major version required.
  721. * - drupal dependencies: drupal modules required for this command.
  722. * - drush dependencies: other drush command files required for this command.
  723. *
  724. */
  725. function drush_parse_command() {
  726. $args = drush_get_arguments();
  727. $command = FALSE;
  728. // Get a list of all implemented commands.
  729. $implemented = drush_get_commands();
  730. if (isset($implemented[$args[0]])) {
  731. $command = $implemented[$args[0]];
  732. $arguments = array_slice($args, 1);
  733. }
  734. // We have found a command that matches. Set the appropriate values.
  735. if ($command) {
  736. // Special case. Force help command if --help option was specified.
  737. if (drush_get_option(array('h', 'help'))) {
  738. $arguments = array($command['command']);
  739. $command = $implemented['help'];
  740. $command['arguments'] = $arguments;
  741. }
  742. else {
  743. _drush_prepare_command($command, $arguments);
  744. }
  745. drush_set_command($command);
  746. }
  747. return $command;
  748. }
  749. /*
  750. * Called by drush_parse_command. If a command is dispatched
  751. * directly by drush_dispatch, then drush_dispatch will call
  752. * this function.
  753. */
  754. function _drush_prepare_command(&$command, $arguments = array()) {
  755. // Merge specified callback arguments, which precede the arguments passed on the command line.
  756. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
  757. $arguments = array_merge($command['callback arguments'], $arguments);
  758. }
  759. $command['arguments'] = $arguments;
  760. }
  761. /**
  762. * Entry point for commands into the drush_invoke API
  763. *
  764. * If a command does not have a callback specified, this function will be called.
  765. *
  766. * This function will trigger $hook_drush_init, then if no errors occur,
  767. * it will call drush_invoke() with the command that was dispatch.
  768. *
  769. * If no errors have occured, it will run $hook_drush_exit.
  770. */
  771. function drush_command() {
  772. $args = func_get_args();
  773. $command = drush_get_command();
  774. foreach (drush_command_implements("drush_init") as $name) {
  775. $func = $name . '_drush_init';
  776. drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap');
  777. call_user_func_array($func, $args);
  778. _drush_log_drupal_messages();
  779. }
  780. if (!drush_get_error()) {
  781. _drush_invoke_args($command['command-hook'], $args, $command['commandfile']);
  782. }
  783. if (!drush_get_error()) {
  784. foreach (drush_command_implements('drush_exit') as $name) {
  785. $func = $name . '_drush_exit';
  786. call_user_func_array($func, $args);
  787. _drush_log_drupal_messages();
  788. }
  789. }
  790. }
  791. /**
  792. * Invoke a hook in all available command files that implement it.
  793. *
  794. * @see drush_command_invoke_all_ref()
  795. *
  796. * @param $hook
  797. * The name of the hook to invoke.
  798. * @param ...
  799. * Arguments to pass to the hook.
  800. * @return
  801. * An array of return values of the hook implementations. If commands return
  802. * arrays from their implementations, those are merged into one array.
  803. */
  804. function drush_command_invoke_all() {
  805. $args = func_get_args();
  806. if (count($args) == 1) {
  807. $args[] = NULL;
  808. }
  809. $reference_value = $args[1];
  810. $args[1] = &$reference_value;
  811. return call_user_func_array('drush_command_invoke_all_ref', $args);
  812. }
  813. /**
  814. * A drush_command_invoke_all() that wants the first parameter to be passed by reference.
  815. *
  816. * @see drush_command_invoke_all()
  817. */
  818. function drush_command_invoke_all_ref($hook, &$reference_parameter) {
  819. $args = func_get_args();
  820. array_shift($args);
  821. // Insure that call_user_func_array can alter first parameter
  822. $args[0] = &$reference_parameter;
  823. $return = array();
  824. foreach (drush_command_implements($hook) as $module) {
  825. $function = $module .'_'. $hook;
  826. $result = call_user_func_array($function, $args);
  827. if (isset($result) && is_array($result)) {
  828. $return = array_merge_recursive($return, $result);
  829. }
  830. else if (isset($result)) {
  831. $return[] = $result;
  832. }
  833. }
  834. return $return;
  835. }
  836. /**
  837. * Determine which command files are implementing a hook.
  838. *
  839. * @param $hook
  840. * The name of the hook (e.g. "drush_help" or "drush_command").
  841. *
  842. * @return
  843. * An array with the names of the command files which are implementing this hook.
  844. */
  845. function drush_command_implements($hook) {
  846. $implementations[$hook] = array();
  847. $list = drush_commandfile_list();
  848. foreach ($list as $commandfile => $file) {
  849. if (drush_command_hook($commandfile, $hook)) {
  850. $implementations[$hook][] = $commandfile;
  851. }
  852. }
  853. return (array)$implementations[$hook];
  854. }
  855. /**
  856. * @param string
  857. * name of command to check.
  858. *
  859. * @return boolean
  860. * TRUE if the given command has an implementation.
  861. */
  862. function drush_is_command($command) {
  863. $commands = drush_get_commands();
  864. return isset($commands[$command]);
  865. }
  866. /**
  867. * Collect a list of all available drush command files.
  868. *
  869. * Scans the following paths for drush command files:
  870. *
  871. * - The "/path/to/drush/commands" folder.
  872. * - Folders listed in the 'include' option (see example.drushrc.php).
  873. * - The system-wide drush commands folder, e.g. /usr/share/drush/commands
  874. * - The ".drush" folder in the user's HOME folder.
  875. * - All modules in the current Drupal installation whether they are enabled or
  876. * not. Commands implementing hook_drush_load() in MODULE.drush.load.inc with
  877. * a return value FALSE will not be loaded.
  878. *
  879. * A drush command file is a file that matches "*.drush.inc".
  880. *
  881. * @see drush_scan_directory()
  882. *
  883. * @return
  884. * An associative array whose keys and values are the names of all available
  885. * command files.
  886. */
  887. function drush_commandfile_list() {
  888. return drush_get_context('DRUSH_COMMAND_FILES', array());
  889. }
  890. function _drush_find_commandfiles($phase, $phase_max = FALSE) {
  891. if (!$phase_max) {
  892. $phase_max = $phase;
  893. }
  894. $searchpath = array();
  895. switch ($phase) {
  896. case DRUSH_BOOTSTRAP_DRUSH:
  897. // Core commands shipping with drush
  898. $searchpath[] = realpath(dirname(__FILE__) . '/../commands/');
  899. // User commands, specified by 'include' option
  900. if ($include = drush_get_option(array('i', 'include'), FALSE)) {
  901. foreach (explode(PATH_SEPARATOR, $include) as $path) {
  902. $searchpath[] = $path;
  903. }
  904. }
  905. // System commands, residing in $SHARE_PREFIX/share/drush/commands
  906. $share_path = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands';
  907. if (is_dir($share_path)) {
  908. $searchpath[] = $share_path;
  909. }
  910. // User commands, residing in ~/.drush
  911. if (!is_null(drush_server_home())) {
  912. $searchpath[] = drush_server_home() . '/.drush';
  913. }
  914. break;
  915. case DRUSH_BOOTSTRAP_DRUPAL_SITE:
  916. // If we are going to stop bootstrapping at the site, then
  917. // we will quickly add all commandfiles that we can find for
  918. // any module associated with the site, whether it is enabled
  919. // or not. If we are, however, going to continue on to bootstrap
  920. // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will
  921. // instead wait for that phase, which will more carefully add
  922. // only those drush command files that are associated with
  923. // enabled modules.
  924. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  925. $searchpath[] = conf_path() . '/modules';
  926. // Too early for variable_get('install_profile', 'default'); Just use default.
  927. $searchpath[] = "profiles/default/modules";
  928. // Add all module paths, even disabled modules. Prefer speed over accuracy.
  929. $searchpath[] = 'sites/all/modules';
  930. }
  931. $searchpath[] = 'sites/all/themes';
  932. $searchpath[] = conf_path() . '/themes';
  933. break;
  934. case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION:
  935. // See comment above regarding this if() condition.
  936. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  937. // You must define your install_profile in settings.php. The DB is not sufficient.
  938. // Drupal core does not yet do that. See http://drupal.org/node/545452.
  939. if ($profile = variable_get('install_profile', NULL)) {
  940. $searchpath[] = "profiles/$profile/modules";
  941. }
  942. }
  943. break;
  944. case DRUSH_BOOTSTRAP_DRUPAL_FULL:
  945. // Add enabled module paths. Since we are bootstrapped,
  946. // we can use the Drupal API.
  947. $ignored_modules = drush_get_option_list('ignored-modules', array());
  948. foreach (array_diff(module_list(), $ignored_modules) as $module) {
  949. $filename = drupal_get_filename('module', $module);
  950. $searchpath[] = dirname($filename);
  951. }
  952. break;
  953. }
  954. _drush_add_commandfiles($searchpath, $phase);
  955. }
  956. function _drush_add_commandfiles($searchpath, $phase = NULL) {
  957. $cache =& drush_get_context('DRUSH_COMMAND_FILES', array());
  958. static $evaluated = array();
  959. static $deferred = array();
  960. if (sizeof($searchpath)) {
  961. // Build a list of all of the modules to attempt to load.
  962. // Start with any modules deferred from a previous phase.
  963. $list = $deferred;
  964. // Scan for drush command files; add to list for consideration if found.
  965. foreach (array_unique($searchpath) as $path) {
  966. if (is_dir($path)) {
  967. $nomask = array_merge(drush_filename_blacklist(), drush_get_option_list('ignored-modules'));
  968. $dmv = DRUSH_MAJOR_VERSION;
  969. $files = drush_scan_directory($path, "/\.drush($dmv|)\.inc$/", $nomask);
  970. foreach ($files as $filename => $info) {
  971. $module = basename($filename);
  972. $module = str_replace(array('.drush.inc', ".drush$dmv.inc"), '', $module);
  973. // Only try to bootstrap modules that we have never seen before, or that we
  974. // have tried to load but did not due to an unmet _drush_load() requirement.
  975. if (!array_key_exists($module, $evaluated) && file_exists($filename)) {
  976. $evaluated[$module] = TRUE;
  977. $list[$module] = $filename;
  978. }
  979. }
  980. }
  981. }
  982. // Check each file in the consideration list; if there is
  983. // a modulename_drush_load() function in modulename.drush.load.inc,
  984. // then call it to determine if this file should be loaded.
  985. foreach ($list as $module => $filename) {
  986. $load_command = TRUE;
  987. $load_test_inc = dirname($filename) . "/" . $module . ".drush.load.inc";
  988. if (file_exists($load_test_inc)) {
  989. include_once($load_test_inc);
  990. $load_test_func = $module . "_drush_load";
  991. if (function_exists($load_test_func)) {
  992. $load_command = $load_test_func($phase);
  993. }
  994. }
  995. if ($load_command) {
  996. require_once realpath($filename);
  997. unset($deferred[$module]);
  998. }
  999. else {
  1000. unset($list[$module]);
  1001. // Signal that we should try again on
  1002. // the next bootstrap phase. We set
  1003. // the flag to the filename of the first
  1004. // module we find so that only that one
  1005. // will be retried.
  1006. $deferred[$module] = $filename;
  1007. }
  1008. }
  1009. if (sizeof($list)) {
  1010. $cache = array_merge($cache, $list);
  1011. ksort($cache);
  1012. }
  1013. }
  1014. }
  1015. /**
  1016. * Substrings to ignore during commandfile searching.
  1017. */
  1018. function drush_filename_blacklist() {
  1019. return array_diff(array('.', '..', 'examples', 'tests', 'disabled', 'gitcache', 'drush4', 'drush5', 'drush6', 'drush7'), array('drush' . DRUSH_MAJOR_VERSION));
  1020. }
  1021. /**
  1022. * Conditionally include files based on the command used.
  1023. *
  1024. * Steps through each of the currently loaded commandfiles and
  1025. * loads an optional commandfile based on the key.
  1026. *
  1027. * When a command such as 'pm-enable' is called, this
  1028. * function will find all 'enable.pm.inc' files that
  1029. * are present in each of the commandfile directories.
  1030. */
  1031. function drush_command_include($command) {
  1032. $include_files = drush_command_get_includes($command);
  1033. foreach($include_files as $filename => $commandfile) {
  1034. drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap');
  1035. include_once($filename);
  1036. }
  1037. }
  1038. function drush_command_get_includes($command) {
  1039. $include_files = array();
  1040. $parts = explode('-', $command);
  1041. $command = implode(".", array_reverse($parts));
  1042. $commandfiles = drush_commandfile_list();
  1043. $options = array();
  1044. foreach ($commandfiles as $commandfile => $file) {
  1045. $filename = sprintf("%s/%s.inc", dirname($file), $command);
  1046. if (file_exists($filename)) {
  1047. $include_files[$filename] = $commandfile;
  1048. }
  1049. }
  1050. return $include_files;
  1051. }
  1052. /**
  1053. * Conditionally include default options based on the command used.
  1054. */
  1055. function drush_command_default_options($command = NULL) {
  1056. if (!$command) {
  1057. $command = drush_get_command();
  1058. }
  1059. if ($command) {
  1060. // Look for command-specific options for this command
  1061. // keyed both on the command's primary name, and on each
  1062. // of its aliases.
  1063. $options_were_set = _drush_command_set_default_options($command['command']);
  1064. if (isset($command['aliases']) && count($command['aliases'])) {
  1065. foreach ($command['aliases'] as $alias) {
  1066. if (_drush_command_set_default_options($alias) === TRUE) {
  1067. $options_were_set = TRUE;
  1068. }
  1069. }
  1070. }
  1071. // If we set or cleared any options, go back and re-bootstrap any global
  1072. // options such as -y and -v.
  1073. if ($options_were_set) {
  1074. _drush_bootstrap_global_options();
  1075. }
  1076. }
  1077. }
  1078. function _drush_command_set_default_options($command) {
  1079. $options_were_set = FALSE;
  1080. $command_default_options = drush_get_context('command-specific');
  1081. if (array_key_exists($command, $command_default_options)) {
  1082. foreach ($command_default_options[$command] as $key => $value) {
  1083. // We set command-specific options in their own context
  1084. // that is higher precedence than the various config file
  1085. // context, but lower than command-line options.
  1086. if (!drush_get_option('no-' . $key, FALSE)) {
  1087. drush_set_option($key, $value, 'specific');
  1088. $options_were_set = TRUE;
  1089. }
  1090. }
  1091. }
  1092. return $options_were_set;
  1093. }
  1094. /**
  1095. * Determine whether a command file implements a hook.
  1096. *
  1097. * @param $module
  1098. * The name of the module (without the .module extension).
  1099. * @param $hook
  1100. * The name of the hook (e.g. "help" or "menu").
  1101. * @return
  1102. * TRUE if the the hook is implemented.
  1103. */
  1104. function drush_command_hook($commandfile, $hook) {
  1105. return function_exists($commandfile .'_'. $hook);
  1106. }
  1107. /**
  1108. * Finds all files that match a given mask in a given directory.
  1109. * Directories and files beginning with a period are excluded; this
  1110. * prevents hidden files and directories (such as SVN working directories
  1111. * and GIT repositories) from being scanned.
  1112. *
  1113. * @param $dir
  1114. * The base directory for the scan, without trailing slash.
  1115. * @param $mask
  1116. * The regular expression of the files to find.
  1117. * @param $nomask
  1118. * An array of files/directories to ignore.
  1119. * @param $callback
  1120. * The callback function to call for each match.
  1121. * @param $recurse_max_depth
  1122. * When TRUE, the directory scan will recurse the entire tree
  1123. * starting at the provided directory. When FALSE, only files
  1124. * in the provided directory are returned. Integer values
  1125. * limit the depth of the traversal, with zero being treated
  1126. * identically to FALSE, and 1 limiting the traversal to the
  1127. * provided directory and its immediate children only, and so on.
  1128. * @param $key
  1129. * The key to be used for the returned array of files. Possible
  1130. * values are "filename", for the path starting with $dir,
  1131. * "basename", for the basename of the file, and "name" for the name
  1132. * of the file without an extension.
  1133. * @param $min_depth
  1134. * Minimum depth of directories to return files from.
  1135. * @param $include_dot_files
  1136. * If TRUE, files that begin with a '.' will be returned if they
  1137. * match the provided mask. If FALSE, files that begin with a '.'
  1138. * will not be returned, even if they match the provided mask.
  1139. * @param $depth
  1140. * Current depth of recursion. This parameter is only used internally and should not be passed.
  1141. *
  1142. * @return
  1143. * An associative array (keyed on the provided key) of objects with
  1144. * "path", "basename", and "name" members corresponding to the
  1145. * matching files.
  1146. */
  1147. function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) {
  1148. $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  1149. $files = array();
  1150. if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) {
  1151. while (FALSE !== ($file = readdir($handle))) {
  1152. if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
  1153. if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
  1154. // Give priority to files in this folder by merging them in after any subdirectory files.
  1155. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
  1156. }
  1157. elseif ($depth >= $min_depth && preg_match($mask, $file)) {
  1158. // Always use this match over anything already set in $files with the same $$key.
  1159. $filename = "$dir/$file";
  1160. $basename = basename($file);
  1161. $name = substr($basename, 0, strrpos($basename, '.'));
  1162. $files[$$key] = new stdClass();
  1163. $files[$$key]->filename = $filename;
  1164. $files[$$key]->basename = $basename;
  1165. $files[$$key]->name = $name;
  1166. if ($callback) {
  1167. drush_op($callback, $filename);
  1168. }
  1169. }
  1170. }
  1171. }
  1172. closedir($handle);
  1173. }
  1174. return $files;
  1175. }
  1176. /**
  1177. * Check that a command is valid for the current bootstrap phase.
  1178. *
  1179. * @param $command
  1180. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1181. *
  1182. * @return
  1183. * TRUE if command is valid.
  1184. */
  1185. function drush_enforce_requirement_bootstrap_phase(&$command) {
  1186. $valid = array();
  1187. $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  1188. if ($command['bootstrap'] <= $current_phase) {
  1189. return TRUE;
  1190. }
  1191. // TODO: provide description text for each bootstrap level so we can give
  1192. // the user something more helpful and specific here.
  1193. $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command']));
  1194. }
  1195. /**
  1196. * Check that a command has its declared dependencies available or have no
  1197. * dependencies.
  1198. *
  1199. * @param $command
  1200. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1201. *
  1202. * @return
  1203. * TRUE if command is valid.
  1204. */
  1205. function drush_enforce_requirement_drupal_dependencies(&$command) {
  1206. // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will
  1207. // allow the requirements to pass if we have not successfully
  1208. // bootstrapped Drupal. The combination of DRUSH_BOOTSTRAP_MAX
  1209. // and 'drupal dependencies' indicates that the drush command
  1210. // will use the dependent modules only if they are available.
  1211. if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) {
  1212. // If we have not bootstrapped, then let the dependencies pass;
  1213. // if we have bootstrapped, then enforce them.
  1214. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  1215. return TRUE;
  1216. }
  1217. }
  1218. // If there are no drupal dependencies, then do nothing
  1219. if (!empty($command['drupal dependencies'])) {
  1220. foreach ($command['drupal dependencies'] as $dependency) {
  1221. if(!function_exists('module_exists') || !module_exists($dependency)) {
  1222. $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following modules installed/enabled to run: !dependencies.', array('!command' => $command['command'], '!dependencies' => implode(', ', $command['drupal dependencies'])));
  1223. return FALSE;
  1224. }
  1225. }
  1226. }
  1227. return TRUE;
  1228. }
  1229. /**
  1230. * Check that a command has its declared drush dependencies available or have no
  1231. * dependencies. Drush dependencies are helpful when a command is invoking
  1232. * another command, or implementing its API.
  1233. *
  1234. * @param $command
  1235. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1236. * @return
  1237. * TRUE if dependencies are met.
  1238. */
  1239. function drush_enforce_requirement_drush_dependencies(&$command) {
  1240. // If there are no drush dependencies, then do nothing.
  1241. if (!empty($command['drush dependencies'])) {
  1242. $commandfiles = drush_commandfile_list();
  1243. foreach ($command['drush dependencies'] as $dependency) {
  1244. if (!isset($commandfiles[$dependency])) {
  1245. $dt_args = array(
  1246. '!command' => $command['command'],
  1247. '!dependency' => "$dependency.drush.inc",
  1248. );
  1249. $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args);
  1250. return FALSE;
  1251. }
  1252. }
  1253. }
  1254. return TRUE;
  1255. }
  1256. /**
  1257. * Check that a command is valid for the current major version of core. Handles
  1258. * explicit version numbers and 'plus' numbers like 6+ (compatible with 6, 7 ...).
  1259. *
  1260. * @param $command
  1261. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1262. *
  1263. * @return
  1264. * TRUE if command is valid.
  1265. */
  1266. function drush_enforce_requirement_core(&$command) {
  1267. $major = drush_drupal_major_version();
  1268. if (!$core = $command['core']) {
  1269. return TRUE;
  1270. }
  1271. foreach ($core as $compat) {
  1272. if ($compat == $major) {
  1273. return TRUE;
  1274. }
  1275. elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) {
  1276. return TRUE;
  1277. }
  1278. }
  1279. $versions = array_pop($core);
  1280. if (!empty($core)) {
  1281. $versions = implode(', ', $core) . dt(' or ') . $versions;
  1282. }
  1283. $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
  1284. }