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_command Entry point for commands into the drush_invoke API
drush_commandfile_list Collect a list of all available drush command files.
drush_command_default_options Conditionally include default options based on the command used.
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
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.
drush_enforce_requirement_drupal_dependencies Check that a command has its declared dependencies available or have no dependencies.
drush_get_commands Get a list of all implemented commands. This invokes hook_drush_command().
drush_invoke Invoke drush api calls.
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_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_command_set_default_options
_drush_find_commandfiles

File

includes/command.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * The drush command engine.
  5. *
  6. * Since drush can be invoked independently of a proper Drupal
  7. * installation and commands may operate across sites, a distinct
  8. * command engine is needed.
  9. *
  10. * It mimics the Drupal module engine in order to economize on
  11. * concepts and to make developing commands as familiar as possible
  12. * to traditional Drupal module developers.
  13. */
  14. /**
  15. * Parse console arguments.
  16. */
  17. function drush_parse_args() {
  18. $args = drush_get_context('argv');
  19. static $arg_opts = array('c', 'h', 'u', 'r', 'l', 'i');
  20. $arguments = $options = array();
  21. for ($i = 1; $i < count($args); $i++) {
  22. $opt = $args[$i];
  23. // Is the arg an option (starting with '-')?
  24. if ($opt{0} == "-" && strlen($opt) != 1) {
  25. // Do we have multiple options behind one '-'?
  26. if (strlen($opt) > 2 && $opt{1} != "-") {
  27. // Each char becomes a key of its own.
  28. for ($j = 1; $j < strlen($opt); $j++) {
  29. $options[substr($opt, $j, 1)] = true;
  30. }
  31. }
  32. // Do we have a longopt (starting with '--')?
  33. elseif ($opt{1} == "-") {
  34. if ($pos = strpos($opt, '=')) {
  35. $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
  36. }
  37. else {
  38. $options[substr($opt, 2)] = true;
  39. }
  40. }
  41. else {
  42. $opt = substr($opt, 1);
  43. // Check if the current opt is in $arg_opts (= has to be followed by an argument).
  44. if ((in_array($opt, $arg_opts))) {
  45. if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
  46. drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
  47. }
  48. $options[$opt] = $args[$i + 1];
  49. $i++;
  50. }
  51. else {
  52. $options[$opt] = true;
  53. }
  54. }
  55. }
  56. // If it's not an option, it's a command.
  57. else {
  58. $arguments[] = $opt;
  59. }
  60. }
  61. // If arguments are specified, print the help screen.
  62. $arguments = sizeof($arguments) ? $arguments : array('help');
  63. drush_set_arguments($arguments);
  64. drush_set_context('options', $options);
  65. }
  66. /**
  67. * Get a list of all implemented commands.
  68. * This invokes hook_drush_command().
  69. *
  70. * @return
  71. * Associative array of currently active command descriptors.
  72. *
  73. */
  74. function drush_get_commands() {
  75. $commands = $available_commands = array();
  76. $list = drush_commandfile_list();
  77. foreach ($list as $commandfile => $path) {
  78. if (drush_command_hook($commandfile, 'drush_command')) {
  79. $function = $commandfile . '_drush_command';
  80. $result = $function();
  81. foreach ((array)$result as $key => $command) {
  82. // Add some defaults and normalize the command descriptor
  83. $command += array(
  84. 'command' => $key,
  85. 'command-hook' => $key,
  86. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
  87. 'commandfile' => $commandfile,
  88. 'path' => dirname($path),
  89. 'engines' => array(), // Helpful for drush_show_help().
  90. 'callback' => 'drush_command',
  91. 'description' => NULL,
  92. 'arguments' => array(),
  93. 'options' => array(),
  94. 'examples' => array(),
  95. 'aliases' => array(),
  96. 'deprecated-aliases' => array(),
  97. 'extras' => array(),
  98. 'core' => array(),
  99. 'scope' => 'site',
  100. 'drupal dependencies' => array(),
  101. 'drush dependencies' => array(),
  102. 'bootstrap_errors' => array(),
  103. 'hidden' => FALSE,
  104. );
  105. // If command callback is correctly named, then fix
  106. // up the command entry so that drush_invoke will be
  107. // called.
  108. if ($command['callback'] != 'drush_command') {
  109. $required_command_prefix = 'drush_' . $commandfile . '_';
  110. if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) {
  111. $command['command-hook'] = substr($command['callback'], strlen($required_command_prefix));
  112. $command['callback'] = 'drush_command';
  113. }
  114. else {
  115. $command['callback-required-prefix'] = $required_command_prefix;
  116. }
  117. }
  118. // Enforce the no-spaces in command names rule
  119. if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($key, ' ') !== FALSE)) {
  120. $command['must-replace-spaces'] = TRUE;
  121. }
  122. // Temporary: if there is a dash in the command name,
  123. // then make a deprecated alias that has a space in the name.
  124. if (strpos($key, '-') !== FALSE) {
  125. $command['deprecated-aliases'][] = str_replace('-', ' ', $key);
  126. }
  127. // Collect all the commands (without filtering) so we can match non-executable
  128. // commands, and later explain why they are not executable.
  129. drush_enforce_requirement_bootstrap_phase($command);
  130. drush_enforce_requirement_core($command);
  131. drush_enforce_requirement_drupal_dependencies($command);
  132. $commands[$key] = $command;
  133. // For every alias, make a copy of the command and store it in the command list
  134. // using the alias as a key
  135. if (isset($command['aliases']) && count($command['aliases'])) {
  136. foreach ($command['aliases'] as $alias) {
  137. $commands[$alias] = $command;
  138. $commands[$alias]['is_alias'] = TRUE;
  139. }
  140. }
  141. // Do the same operation on the deprecated aliases.
  142. if (isset($command['deprecated-aliases']) && count($command['deprecated-aliases'])) {
  143. foreach ($command['deprecated-aliases'] as $alias) {
  144. $commands[$alias] = $command;
  145. $commands[$alias]['is_alias'] = TRUE;
  146. $commands[$alias]['deprecated'] = TRUE;
  147. $commands[$alias]['deprecated-name'] = $alias;
  148. if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($alias, ' ') !== FALSE)) {
  149. $commands[$alias]['must-not-use-spaces'] = TRUE;
  150. }
  151. }
  152. }
  153. }
  154. }
  155. }
  156. return drush_set_context('DRUSH_COMMANDS', $commands);
  157. }
  158. /**
  159. * Matches a commands array, as returned by drush_get_arguments, with the
  160. * current command table.
  161. *
  162. * Note that not all commands may be discoverable at the point-of-call,
  163. * since Drupal modules can ship commands as well, and they are
  164. * not available until after bootstrapping.
  165. *
  166. * drush_parse_command returns a normalized command descriptor, which
  167. * is an associative array with the following entries:
  168. * - callback: name of function to invoke for this command. The callback
  169. * function name _must_ begin with "drush_commandfile_", where commandfile
  170. * is from the file "commandfile.drush.inc", which contains the
  171. * commandfile_drush_command() function that returned this command.
  172. * Note that the callback entry is optional; it is preferable to
  173. * omit it, in which case drush_invoke() will generate the hook function name.
  174. * - callback arguments: an array of arguments to pass to the calback.
  175. * - description: description of the command.
  176. * - arguments: an array of arguments that are understood by the command. for help texts.
  177. * - options: an array of options that are understood by the command. for help texts.
  178. * - examples: an array of examples that are understood by the command. for help texts.
  179. * - scope: one of 'system', 'project', 'site'.
  180. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
  181. * - core: Drupal major version required.
  182. * - drupal dependencies: drupal modules required for this command.
  183. * - drush dependencies: other drush command files required for this command (not yet implemented)
  184. *
  185. * @example
  186. * drush_parse_command();
  187. *
  188. */
  189. function drush_parse_command() {
  190. $args = drush_get_arguments();
  191. // Get a list of all implemented commands.
  192. $implemented = drush_get_commands();
  193. $command = FALSE;
  194. $arguments = array();
  195. // Try to determine the handler for the current command.
  196. while (!$command && count($args)) {
  197. $part = implode(" ", $args);
  198. if (isset($implemented[$part])) {
  199. $command = $implemented[$part];
  200. }
  201. else {
  202. $arguments[] = array_pop($args);
  203. }
  204. }
  205. // We have found a command that matches. Set the appropriate values.
  206. if ($command) {
  207. // Special case. Force help command if --help option was specified.
  208. if (drush_get_option(array('h', 'help'))) {
  209. $arguments = array($command['command']);
  210. $command = $implemented['help'];
  211. $command['arguments'] = $arguments;
  212. }
  213. else {
  214. $arguments = array_reverse($arguments);
  215. // Merge specified callback arguments, which precede the arguments passed on the command line.
  216. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
  217. $arguments = array_merge($command['callback arguments'], $arguments);
  218. }
  219. }
  220. $command['arguments'] = $arguments;
  221. drush_set_command($command);
  222. }
  223. return $command;
  224. }
  225. /**
  226. * Invoke drush api calls.
  227. *
  228. * Call the correct hook for all the modules that implement it.
  229. * Additionally, the ability to rollback when an error has been encountered is also provided.
  230. * If at any point during execution, the drush_get_error() function returns anything but 0,
  231. * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
  232. * in reverse order from how they were executed.
  233. *
  234. * This function will also trigger pre_$hook and post_$hook variants of the hook
  235. * and its rollbacks automatically.
  236. *
  237. * HOW DRUSH HOOK FUNCTIONS ARE NAMED:
  238. *
  239. * The name of the hook is composed from the name of the command and the name of
  240. * the command file that the command definition is declared in. The general
  241. * form for the hook filename is:
  242. *
  243. * drush_COMMANDFILE_COMMANDNAME
  244. *
  245. * In many cases, drush commands that are functionally part of a common collection
  246. * of similar commands will all be declared in the same file, and every command
  247. * defined in that file will start with the same command prefix. For example, the
  248. * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
  249. * In the case of "pm-enable", the command file is "pm", and and command name is
  250. * "pm-enable". When the command name starts with the same sequence of characters
  251. * as the command file, then the repeated sequence is dropped; thus, the command
  252. * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
  253. *
  254. * @param command
  255. * The drush command to execute.
  256. * @return
  257. * A boolean specifying whether or not the command was successfully completed.
  258. *
  259. */
  260. function drush_invoke($command) {
  261. drush_command_include($command);
  262. $args = func_get_args();
  263. array_shift($args);
  264. // Generate the base name for the hook by using the
  265. // php string translation function to convert all
  266. // dashes and spaces in the command name to underscores.
  267. // TODO: put this back to $hook = str_replace("-", "_", $command);
  268. // for better readability after the allow-spaces-in-commands
  269. // backwards-compatibility feature is removed.
  270. $hook = strtr($command, "- ", "__"); // n.b. str tr, not str str.
  271. $list = drush_commandfile_list();
  272. $functions = array();
  273. // First we build a list of functions that are about to be executed
  274. $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook");
  275. $all_available_hooks = array();
  276. foreach ($variations as $var_hook) {
  277. foreach ($list as $commandfile => $filename) {
  278. $oldfunc = sprintf("drush_%s_%s", $commandfile, $var_hook);
  279. $func = str_replace('drush_' . $commandfile . '_' . $commandfile, 'drush_' . $commandfile, $oldfunc);
  280. if (($oldfunc != $func) && (function_exists($oldfunc))) {
  281. drush_log(dt("The drush command hook naming conventions have changed; the function !oldfunc must be renamed to !func. The old function will be called, but this will be removed shortly.", array('!oldfunc' => $oldfunc, '!func' => $func)), "error");
  282. // TEMPORARY: Allow the function to be called by its old name.
  283. $functions[] = $oldfunc;
  284. }
  285. if (function_exists($func)) {
  286. $functions[] = $func;
  287. $all_available_hooks[] = $func . ' [*]';
  288. }
  289. else {
  290. $all_available_hooks[] = $func;
  291. }
  292. }
  293. }
  294. // If no hook functions were found, print a warning.
  295. if (empty($functions)) {
  296. drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning');
  297. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'warning');
  298. }
  299. elseif (drush_get_option('show-invoke')) {
  300. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'internals');
  301. }
  302. $rollback = FALSE;
  303. $completed = array();
  304. $available_rollbacks = array();
  305. foreach ($functions as $func) {
  306. $available_rollbacks[] = $func . '_rollback';
  307. if ($rollback === FALSE) {
  308. $completed[] = $func;
  309. if (function_exists($func)) {
  310. call_user_func_array($func, $args);
  311. _drush_log_drupal_messages();
  312. if (drush_get_error()) {
  313. drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error');
  314. $rollback = TRUE;
  315. }
  316. }
  317. }
  318. }
  319. if (drush_get_option('show-invoke')) {
  320. drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'internals');
  321. }
  322. // something went wrong, we need to undo
  323. if ($rollback) {
  324. foreach (array_reverse($completed) as $func) {
  325. $rb_func = $func . '_rollback';
  326. if (function_exists($rb_func)) {
  327. call_user_func_array($rb_func, $args);
  328. _drush_log_drupal_messages();
  329. drush_log("Changes for $func module have been rolled back.", 'rollback');
  330. }
  331. }
  332. }
  333. return !$rollback;
  334. }
  335. /**
  336. * Entry point for commands into the drush_invoke API
  337. *
  338. * If a command does not have a callback specified, this function will be called.
  339. *
  340. * This function will trigger $hook_drush_init, then if no errors occur,
  341. * it will call drush_invoke() with the command that was dispatch.
  342. *
  343. * If no errors have occured, it will run $hook_drush_exit.
  344. */
  345. function drush_command() {
  346. $args = func_get_args();
  347. $command = drush_get_command();
  348. foreach (drush_command_implements("drush_init") as $name) {
  349. $func = $name . '_drush_init';
  350. drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap');
  351. call_user_func_array($func, $args);
  352. _drush_log_drupal_messages();
  353. }
  354. if (!drush_get_error()) {
  355. call_user_func_array('drush_invoke', array_merge(array($command['command-hook']), $args));
  356. }
  357. if (!drush_get_error()) {
  358. foreach (drush_command_implements('drush_exit') as $name) {
  359. $func = $name . '_drush_exit';
  360. call_user_func_array($func, $args);
  361. _drush_log_drupal_messages();
  362. }
  363. }
  364. }
  365. /**
  366. * Invoke a hook in all available command files that implement it.
  367. *
  368. * @param $hook
  369. * The name of the hook to invoke.
  370. * @param ...
  371. * Arguments to pass to the hook.
  372. * @return
  373. * An array of return values of the hook implementations. If commands return
  374. * arrays from their implementations, those are merged into one array.
  375. */
  376. function drush_command_invoke_all() {
  377. $args = func_get_args();
  378. if (count($args) == 1) {
  379. $args[] = NULL;
  380. }
  381. $reference_value = $args[1];
  382. $args[1] = &$reference_value;
  383. return call_user_func_array('drush_command_invoke_all_ref', $args);
  384. }
  385. function drush_command_invoke_all_ref($hook, &$reference_parameter) {
  386. $args = func_get_args();
  387. array_shift($args);
  388. // Insure that call_user_func_array can alter first parameter
  389. $args[0] = &$reference_parameter;
  390. $return = array();
  391. foreach (drush_command_implements($hook) as $module) {
  392. $function = $module .'_'. $hook;
  393. $result = call_user_func_array($function, $args);
  394. if (isset($result) && is_array($result)) {
  395. $return = array_merge_recursive($return, $result);
  396. }
  397. else if (isset($result)) {
  398. $return[] = $result;
  399. }
  400. }
  401. return $return;
  402. }
  403. /**
  404. * Determine which command files are implementing a hook.
  405. *
  406. * @param $hook
  407. * The name of the hook (e.g. "drush_help" or "drush_command").
  408. *
  409. * @return
  410. * An array with the names of the command files which are implementing this hook.
  411. */
  412. function drush_command_implements($hook) {
  413. $implementations[$hook] = array();
  414. $list = drush_commandfile_list();
  415. foreach ($list as $commandfile => $file) {
  416. if (drush_command_hook($commandfile, $hook)) {
  417. $implementations[$hook][] = $commandfile;
  418. }
  419. }
  420. return (array)$implementations[$hook];
  421. }
  422. /**
  423. * @param string
  424. * name of command to check.
  425. *
  426. * @return boolean
  427. * TRUE if the given command has an implementation.
  428. */
  429. function drush_is_command($command) {
  430. $commands = drush_get_commands();
  431. return isset($commands[$command]);
  432. }
  433. /**
  434. * Collect a list of all available drush command files.
  435. *
  436. * Scans the following paths for drush command files:
  437. *
  438. * - The "/path/to/drush/commands" folder.
  439. * - Folders listed in the 'include' option (see example.drushrc.php).
  440. * - The system-wide drush commands folder, e.g. /usr/share/drush/commands
  441. * - The ".drush" folder in the user's HOME folder.
  442. * - All modules in the current Drupal installation whether they are enabled or
  443. * not. Commands implementing hook_drush_load() in MODULE.drush.load.inc with
  444. * a return value FALSE will not be loaded.
  445. *
  446. * A drush command file is a file that matches "*.drush.inc".
  447. *
  448. * @see drush_scan_directory()
  449. *
  450. * @return
  451. * An associative array whose keys and values are the names of all available
  452. * command files.
  453. */
  454. function drush_commandfile_list() {
  455. return drush_get_context('DRUSH_COMMAND_FILES', array());
  456. }
  457. function _drush_find_commandfiles($phase) {
  458. $cache =& drush_get_context('DRUSH_COMMAND_FILES', array());
  459. static $evaluated = array();
  460. static $deferred = array();
  461. $searchpath = array();
  462. switch ($phase) {
  463. case DRUSH_BOOTSTRAP_DRUSH:
  464. // Core commands shipping with drush
  465. $searchpath[] = realpath(dirname(__FILE__) . '/../commands/');
  466. // User commands, specified by 'include' option
  467. if ($include = drush_get_option(array('i', 'include'), FALSE)) {
  468. foreach (explode(":", $include) as $path) {
  469. $searchpath[] = $path;
  470. }
  471. }
  472. // System commands, residing in $SHARE_PREFIX/share/drush/commands
  473. $share_path = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands';
  474. if (is_dir($share_path)) {
  475. $searchpath[] = $share_path;
  476. }
  477. // User commands, residing in ~/.drush
  478. if (!is_null(drush_server_home())) {
  479. $searchpath[] = drush_server_home() . '/.drush';
  480. }
  481. break;
  482. case DRUSH_BOOTSTRAP_DRUPAL_SITE:
  483. $searchpath[] = conf_path() . '/modules';
  484. // Too early for variable_get('install_profile', 'default'); Just use default.
  485. $searchpath[] = "profiles/default/modules";
  486. // Add all module paths, even disabled modules. Prefer speed over accuracy.
  487. $searchpath[] = 'sites/all/modules';
  488. break;
  489. case DRUSH_BOOTSTRAP_DRUPAL_FULL:
  490. // Add enabled module paths. Since we are bootstrapped,
  491. // we can use the Drupal API.
  492. foreach (module_list() as $module) {
  493. $filename = drupal_get_filename('module', $module);
  494. $searchpath[] = dirname($filename);
  495. }
  496. break;
  497. }
  498. if (sizeof($searchpath)) {
  499. // Build a list of all of the modules to attempt to load.
  500. // Start with any modules deferred from a previous phase.
  501. $list = $deferred;
  502. // Scan for drush command files; add to list for consideration if found.
  503. foreach (array_unique($searchpath) as $path) {
  504. if (is_dir($path)) {
  505. $files = drush_scan_directory($path, '/\.drush\.inc$/');
  506. foreach ($files as $filename => $info) {
  507. $module = basename($filename, '.drush.inc');
  508. // Only try to bootstrap modules that we have never seen before, or that we
  509. // have tried to load but did not due to an unmet _drush_load() requirement.
  510. if (!array_key_exists($module, $evaluated) && file_exists($filename)) {
  511. $evaluated[$module] = TRUE;
  512. $list[$module] = $filename;
  513. }
  514. }
  515. }
  516. }
  517. // Check each file in the consideration list; if there is
  518. // a modulename_drush_load() function in modulename.drush.load.inc,
  519. // then call it to determine if this file should be loaded.
  520. foreach ($list as $module => $filename) {
  521. $load_command = TRUE;
  522. $load_test_inc = dirname($filename) . "/" . $module . ".drush.load.inc";
  523. if (file_exists($load_test_inc)) {
  524. require_once($load_test_inc);
  525. $load_test_func = $module . "_drush_load";
  526. if (function_exists($load_test_func)) {
  527. $load_command = $load_test_func($phase);
  528. }
  529. }
  530. if ($load_command) {
  531. require_once(realpath($filename));
  532. unset($deferred[$module]);
  533. }
  534. else {
  535. unset($list[$module]);
  536. // Signal that we should try again on
  537. // the next bootstrap phase. We set
  538. // the flag to the filename of the first
  539. // module we find so that only that one
  540. // will be retried.
  541. $deferred[$module] = $filename;
  542. }
  543. }
  544. if (sizeof($list)) {
  545. $cache = array_merge($cache, $list);
  546. ksort($cache);
  547. }
  548. }
  549. }
  550. /**
  551. * Conditionally include files based on the command used.
  552. *
  553. * Steps through each of the currently loaded commandfiles and
  554. * loads an optional commandfile based on the key.
  555. *
  556. * When a command such as 'pm-enable' is called, this
  557. * function will find all 'enable.pm.inc' files that
  558. * are present in each of the commandfile directories.
  559. */
  560. function drush_command_include($command) {
  561. $parts = explode('-', $command);
  562. $command = implode(".", array_reverse($parts));
  563. $commandfiles = drush_commandfile_list();
  564. $options = array();
  565. foreach ($commandfiles as $commandfile => $file) {
  566. $filename = sprintf("%s/%s.inc", dirname($file), $command);
  567. if (file_exists($filename)) {
  568. drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap');
  569. include_once($filename);
  570. }
  571. }
  572. }
  573. /**
  574. * Conditionally include default options based on the command used.
  575. */
  576. function drush_command_default_options($command = NULL) {
  577. if (!$command) {
  578. $command = drush_get_command();
  579. }
  580. if ($command) {
  581. // Look for command-specific options for this command
  582. // keyed both on the command's primary name, and on each
  583. // of its aliases.
  584. $options_were_set = _drush_command_set_default_options($command['command']);
  585. if (isset($command['aliases']) && count($command['aliases'])) {
  586. foreach ($command['aliases'] as $alias) {
  587. if (_drush_command_set_default_options($alias) === TRUE) {
  588. $options_were_set = TRUE;
  589. }
  590. }
  591. }
  592. // Take the time here to clear out any options that may
  593. // have "--no-xxx" overrides on the command line.
  594. $commandline_options = drush_get_context('options');
  595. foreach ($commandline_options as $key => $value) {
  596. if (substr($key, 0, strlen("no-") ) == "no-") {
  597. drush_unset_option(substr($key, strlen("no-")));
  598. $options_were_set = TRUE;
  599. }
  600. }
  601. // If we set or cleared any options, go back and re-bootstrap any global
  602. // options such as -y and -v.
  603. if ($options_were_set) {
  604. _drush_bootstrap_global_options();
  605. }
  606. }
  607. }
  608. function _drush_command_set_default_options($command) {
  609. $options_were_set = FALSE;
  610. $command_default_options = drush_get_context('command-specific');
  611. if (array_key_exists($command, $command_default_options)) {
  612. foreach ($command_default_options[$command] as $key => $value) {
  613. // We set command-specific options in their own context
  614. // that is higher precidence than the various config file
  615. // context, but lower than command-line options.
  616. drush_set_option($key, $value, 'specific');
  617. $options_were_set = TRUE;
  618. }
  619. }
  620. return $options_were_set;
  621. }
  622. /**
  623. * Determine whether a command file implements a hook.
  624. *
  625. * @param $module
  626. * The name of the module (without the .module extension).
  627. * @param $hook
  628. * The name of the hook (e.g. "help" or "menu").
  629. * @return
  630. * TRUE if the the hook is implemented.
  631. */
  632. function drush_command_hook($commandfile, $hook) {
  633. return function_exists($commandfile .'_'. $hook);
  634. }
  635. /**
  636. * Finds all files that match a given mask in a given directory.
  637. * Directories and files beginning with a period are excluded; this
  638. * prevents hidden files and directories (such as SVN working directories
  639. * and GIT repositories) from being scanned.
  640. *
  641. * @param $dir
  642. * The base directory for the scan, without trailing slash.
  643. * @param $mask
  644. * The regular expression of the files to find.
  645. * @param $nomask
  646. * An array of files/directories to ignore.
  647. * @param $callback
  648. * The callback function to call for each match.
  649. * @param $recurse_max_depth
  650. * When TRUE, the directory scan will recurse the entire tree
  651. * starting at the provided directory. When FALSE, only files
  652. * in the provided directory are returned. Integer values
  653. * limit the depth of the traversal, with zero being treated
  654. * identically to FALSE, and 1 limiting the traversal to the
  655. * provided directory and its immediate children only, and so on.
  656. * @param $key
  657. * The key to be used for the returned array of files. Possible
  658. * values are "filename", for the path starting with $dir,
  659. * "basename", for the basename of the file, and "name" for the name
  660. * of the file without an extension.
  661. * @param $min_depth
  662. * Minimum depth of directories to return files from.
  663. * @param $include_dot_files
  664. * If TRUE, files that begin with a '.' will be returned if they
  665. * match the provided mask. If FALSE, files that begin with a '.'
  666. * will not be returned, even if they match the provided mask.
  667. * @param $depth
  668. * Current depth of recursion. This parameter is only used internally and should not be passed.
  669. *
  670. * @return
  671. * An associative array (keyed on the provided key) of objects with
  672. * "path", "basename", and "name" members corresponding to the
  673. * matching files.
  674. */
  675. 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) {
  676. $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  677. $files = array();
  678. if (is_dir($dir) && $handle = opendir($dir)) {
  679. while (FALSE !== ($file = readdir($handle))) {
  680. if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
  681. if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
  682. // Give priority to files in this folder by merging them in after any subdirectory files.
  683. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
  684. }
  685. elseif ($depth >= $min_depth && preg_match($mask, $file)) {
  686. // Always use this match over anything already set in $files with the same $$key.
  687. $filename = "$dir/$file";
  688. $basename = basename($file);
  689. $name = substr($basename, 0, strrpos($basename, '.'));
  690. $files[$$key] = new stdClass();
  691. $files[$$key]->filename = $filename;
  692. $files[$$key]->basename = $basename;
  693. $files[$$key]->name = $name;
  694. if ($callback) {
  695. $callback($filename);
  696. }
  697. }
  698. }
  699. }
  700. closedir($handle);
  701. }
  702. return $files;
  703. }
  704. /**
  705. * Check that a command is valid for the current bootstrap phase.
  706. *
  707. * @param $command
  708. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  709. *
  710. * @return
  711. * TRUE if command is valid.
  712. */
  713. function drush_enforce_requirement_bootstrap_phase(&$command) {
  714. $valid = array();
  715. $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  716. if ($command['bootstrap'] <= $current_phase) {
  717. return TRUE;
  718. }
  719. // TODO: provide description text for each bootstrap level so we can give
  720. // the user something more helpful and specific here.
  721. $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']));
  722. }
  723. /**
  724. * Check that a command has its declared dependencies available or have no
  725. * dependencies.
  726. *
  727. * @param $command
  728. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  729. *
  730. * @return
  731. * TRUE if command is valid.
  732. */
  733. function drush_enforce_requirement_drupal_dependencies(&$command) {
  734. if (empty($command['drupal dependencies'])) {
  735. return TRUE;
  736. }
  737. else {
  738. foreach ($command['drupal dependencies'] as $dependency) {
  739. if (function_exists('module_exists') && module_exists($dependency)) {
  740. return TRUE;
  741. }
  742. }
  743. }
  744. $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'])));
  745. }
  746. /**
  747. * Check that a command is valid for the current major version of core.
  748. *
  749. * @param $command
  750. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  751. *
  752. * @return
  753. * TRUE if command is valid.
  754. */
  755. function drush_enforce_requirement_core(&$command) {
  756. $core = $command['core'];
  757. if (empty($core) || in_array(drush_drupal_major_version(), $core)) {
  758. return TRUE;
  759. }
  760. $versions = array_pop($core);
  761. if (!empty($core)) {
  762. $versions = implode(', ', $core) . dt(' or ') . $versions;
  763. }
  764. $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
  765. }