/[drupal]/contributions/modules/drush/includes/command.inc
ViewVC logotype

Contents of /contributions/modules/drush/includes/command.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.45 - (show annotations) (download) (as text)
Mon Oct 26 02:26:37 2009 UTC (4 weeks, 6 days ago) by weitzman
Branch: MAIN
CVS Tags: DRUPAL-6--2-1
Changes since 1.44: +3 -3 lines
File MIME type: text/x-php
#613348 by mig5. Various spelling/grammar corrections.
1 <?php
2 // $Id: command.inc,v 1.44 2009/10/22 14:57:06 adrian Exp $
3
4 /**
5 * @file
6 * The drush command engine.
7 *
8 * Since drush can be invoked independently of a proper Drupal
9 * installation and commands may operate across sites, a distinct
10 * command engine is needed.
11 *
12 * It mimics the Drupal module engine in order to economize on
13 * concepts and to make developing commands as familiar as possible
14 * to traditional Drupal module developers.
15 */
16
17 /**
18 * Parse console arguments.
19 */
20 function drush_parse_args() {
21 $args = drush_get_context('argv');
22
23 static $arg_opts = array('c', 'h', 'u', 'r', 'l', 'i');
24
25 $arguments = $options = array();
26
27 for ($i = 1; $i < count($args); $i++) {
28 $opt = $args[$i];
29 // Is the arg an option (starting with '-')?
30 if ($opt{0} == "-" && strlen($opt) != 1) {
31 // Do we have multiple options behind one '-'?
32 if (strlen($opt) > 2 && $opt{1} != "-") {
33 // Each char becomes a key of its own.
34 for ($j = 1; $j < strlen($opt); $j++) {
35 $options[substr($opt, $j, 1)] = true;
36 }
37 }
38 // Do we have a longopt (starting with '--')?
39 elseif ($opt{1} == "-") {
40 if ($pos = strpos($opt, '=')) {
41 $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
42 }
43 else {
44 $options[substr($opt, 2)] = true;
45 }
46 }
47 else {
48 $opt = substr($opt, 1);
49 // Check if the current opt is in $arg_opts (= has to be followed by an argument).
50 if ((in_array($opt, $arg_opts))) {
51 if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
52 drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
53 }
54 $options[$opt] = $args[$i + 1];
55 $i++;
56 }
57 else {
58 $options[$opt] = true;
59 }
60 }
61 }
62 // If it's not an option, it's a command.
63 else {
64 $arguments[] = $opt;
65 }
66 }
67 // If arguments are specified, print the help screen.
68 $arguments = sizeof($arguments) ? $arguments : array('help');
69
70 drush_set_arguments($arguments);
71 drush_set_context('options', $options);
72 }
73
74
75 /**
76 * Get a list of all implemented commands.
77 * This invokes hook_drush_command().
78 *
79 * @return
80 * Associative array of currently active command descriptors.
81 *
82 */
83 function drush_get_commands() {
84 $commands = $available_commands = array();
85 $list = drush_commandfile_list();
86 foreach ($list as $commandfile => $path) {
87 if (drush_command_hook($commandfile, 'drush_command')) {
88 $function = $commandfile . '_drush_command';
89 $result = $function();
90 foreach ((array)$result as $key => $command) {
91 // Add some defaults and normalize the command descriptor
92 $command += array(
93 'command' => $key,
94 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
95 'commandfile' => $commandfile,
96 'path' => dirname($path),
97 'engines' => array(), // Helpful for drush_show_help().
98 'callback' => 'drush_command',
99 'description' => NULL,
100 'arguments' => array(),
101 'options' => array(),
102 'examples' => array(),
103 'aliases' => array(),
104 'extras' => array(),
105 'core' => array(),
106 'scope' => 'site',
107 'drupal dependencies' => array(),
108 'drush dependencies' => array(),
109 'bootstrap_errors' => array(),
110 );
111 // Collect all the commands (without filtering) so we can match non-executable
112 // commands, and later explain why they are not executable.
113 drush_enforce_requirement_bootstrap_phase($command);
114 drush_enforce_requirement_core($command);
115 drush_enforce_requirement_drupal_dependencies($command);
116 $commands[$key] = $command;
117 if (isset($command['aliases']) && count($command['aliases'])) {
118 foreach ($command['aliases'] as $alias) {
119 $commands[$alias] = $command;
120 $commands[$alias]['is_alias'] = TRUE;
121 }
122 }
123 }
124 }
125 }
126
127 return drush_set_context('DRUSH_COMMANDS', $commands);
128 }
129
130 /**
131 * Matches a commands array, as returned by drush_get_arguments, with the
132 * current command table.
133 *
134 * Note that not all commands may be discoverable at the point-of-call,
135 * since Drupal modules can ship commands as well, and they are
136 * not available until after bootstrapping.
137 *
138 * drush_parse_command returns a normalized command descriptor, which
139 * is an associative array with the following entries:
140 * - callback: name of function to invoke for this command.
141 * - callback arguments: an array of arguments to pass to the calback.
142 * - description: description of the command.
143 * - arguments: an array of arguments that are understood by the command. for help texts.
144 * - options: an array of options that are understood by the command. for help texts.
145 * - examples: an array of examples that are understood by the command. for help texts.
146 * - scope: one of 'system', 'project', 'site'.
147 * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
148 * - core: Drupal major version required.
149 * - drupal dependencies: drupal modules required for this command.
150 * - drush dependencies: other drush command files required for this command (not yet implemented)
151 *
152 * @example
153 * drush_parse_command();
154 *
155 */
156 function drush_parse_command() {
157 $args = drush_get_arguments();
158
159 // Get a list of all implemented commands.
160 $implemented = drush_get_commands();
161
162 $command = FALSE;
163 $arguments = array();
164 // Try to determine the handler for the current command.
165 while (!$command && count($args)) {
166 $part = implode(" ", $args);
167 if (isset($implemented[$part])) {
168 $command = $implemented[$part];
169 }
170 else {
171 $arguments[] = array_pop($args);
172 }
173 }
174
175 // We have found a command that matches. Set the appropriate values.
176 if ($command) {
177 // Special case. Force help command if --help option was specified.
178 if (drush_get_option(array('h', 'help'))) {
179 $arguments = array($command['command']);
180 $command = $implemented['help'];
181 $command['arguments'] = $arguments;
182 }
183 else {
184 $arguments = array_reverse($arguments);
185
186 // Merge specified callback arguments, which precede the arguments passed on the command line.
187 if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
188 $arguments = array_merge($command['callback arguments'], $arguments);
189 }
190 }
191 $command['arguments'] = $arguments;
192 drush_set_command($command);
193 }
194 return $command;
195 }
196
197 /**
198 * Invoke drush api calls.
199 *
200 * Call the correct hook for all the modules that implement it.
201 * Additionally, the ability to rollback when an error has been encountered is also provided.
202 * If at any point during execution, the drush_get_error() function returns anything but 0,
203 * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
204 * in reverse order from how they were executed.
205 *
206 * This function will also trigger pre_$hook and post_$hook variants of the hook
207 * and its rollbacks automatically.
208 *
209 * @param command
210 * The drush command to execute.
211 * @return
212 * A boolean specifying whether or not the command was successfully completed.
213 *
214 */
215 function drush_invoke($command) {
216 drush_command_include($command);
217 $args = func_get_args();
218 array_shift($args);
219
220
221 $hook = str_replace(" ", "_", $command);
222 $list = drush_commandfile_list();
223
224 $functions = array();
225 // First we build a list of functions are about to execute
226 $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook");
227 foreach ($variations as $var_hook) {
228 foreach ($list as $commandfile => $filename) {
229 $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
230 if (function_exists($func)) {
231 $functions[] = $func;
232 }
233 }
234 }
235 $rollback = FALSE;
236 $completed = array();
237 foreach ($functions as $func) {
238 $completed[] = $func;
239 if (function_exists($func)) {
240 call_user_func_array($func, $args);
241 _drush_log_drupal_messages();
242 if (drush_get_error()) {
243 drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error');
244 # As soon as an error occurs, roll back
245 $rollback = TRUE;
246 break;
247 }
248 }
249 }
250
251 // something went wrong, we need to undo
252 if ($rollback) {
253 foreach (array_reverse($completed) as $func) {
254 $rb_func = $func . '_rollback';
255 if (function_exists($rb_func)) {
256 call_user_func_array($rb_func, $args);
257 _drush_log_drupal_messages();
258 drush_log("Changes for $func module have been rolled back.", 'rollback');
259 }
260 }
261 }
262
263 return !$rollback;
264 }
265
266
267 /**
268 * Entry point for commands into the drush_invoke API
269 *
270 * If a command does not have a callback specified, this function will be called.
271 *
272 * This function will trigger $hook_drush_init, then if no errors occur,
273 * it will call drush_invoke() with the command that was dispatch.
274 *
275 * If no errors have occured, it will run $hook_drush_exit.
276 */
277 function drush_command() {
278 $args = func_get_args();
279 $command = drush_get_command();
280 foreach (drush_command_implements("drush_init") as $name) {
281 $func = $name . '_drush_init';
282 drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap');
283 call_user_func_array($func, $args);
284 _drush_log_drupal_messages();
285 }
286
287 if (!drush_get_error()) {
288 call_user_func_array('drush_invoke', array_merge(array($command['command']), $args));
289 }
290
291 if (!drush_get_error()) {
292 foreach (drush_command_implements('drush_exit') as $name) {
293 $func = $name . '_drush_exit';
294 call_user_func_array($func, $args);
295 _drush_log_drupal_messages();
296 }
297 }
298 }
299
300
301
302 /**
303 * Invoke a hook in all available command files that implement it.
304 *
305 * @param $hook
306 * The name of the hook to invoke.
307 * @param ...
308 * Arguments to pass to the hook.
309 * @return
310 * An array of return values of the hook implementations. If commands return
311 * arrays from their implementations, those are merged into one array.
312 */
313 function drush_command_invoke_all() {
314 $args = func_get_args();
315 $hook = $args[0];
316 unset($args[0]);
317 $return = array();
318 foreach (drush_command_implements($hook) as $module) {
319 $function = $module .'_'. $hook;
320 $result = call_user_func_array($function, $args);
321 if (isset($result) && is_array($result)) {
322 $return = array_merge_recursive($return, $result);
323 }
324 else if (isset($result)) {
325 $return[] = $result;
326 }
327 }
328 return $return;
329 }
330
331 /**
332 * Determine which command files are implementing a hook.
333 *
334 * @param $hook
335 * The name of the hook (e.g. "drush_help" or "drush_command").
336 *
337 * @return
338 * An array with the names of the command files which are implementing this hook.
339 */
340 function drush_command_implements($hook) {
341 $implementations[$hook] = array();
342 $list = drush_commandfile_list();
343 foreach ($list as $commandfile => $file) {
344 if (drush_command_hook($commandfile, $hook)) {
345 $implementations[$hook][] = $commandfile;
346 }
347 }
348 return (array)$implementations[$hook];
349 }
350
351 /**
352 * @param string
353 * name of command to check.
354 *
355 * @return boolean
356 * TRUE if the given command has an implementation.
357 */
358 function drush_is_command($command) {
359 $commands = drush_get_commands();
360 return isset($commands[$command]);
361 }
362
363 /**
364 * Collect a list of all available drush command files.
365 *
366 * Scans the following paths for drush command files:
367 *
368 * - The ".drush" folder in the users HOME folder.
369 * - The "/path/to/drush/includes" folder.
370 * - Folders listed in the 'include' option (see example.drushrc.php).
371 * - Active modules in the current Drupal installation (if any).
372 *
373 * A drush command file is a file that matches "*.drush.inc".
374 *
375 * @see drush_scan_directory
376 *
377 * @return
378 * An associative array whose keys and values are the names of all available
379 * command files.
380 */
381 function drush_commandfile_list() {
382 return drush_get_context('DRUSH_COMMAND_FILES', array());
383 }
384
385 function _drush_find_commandfiles($phase) {
386 $cache =& drush_get_context('DRUSH_COMMAND_FILES', array());
387
388 $searchpath = array();
389 switch ($phase) {
390 case DRUSH_BOOTSTRAP_DRUSH:
391 // Core commands shipping with drush
392 $searchpath[] = realpath(dirname(__FILE__) . '/../commands/');
393
394 // User commands, specified by 'include' option
395 if ($include = drush_get_option(array('i', 'include'), FALSE)) {
396 foreach (explode(":", $include) as $path) {
397 $searchpath[] = $path;
398 }
399 }
400
401 // System commands, residing in $SHARE_PREFIX/share/drush/commands
402 $share_path = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands';
403
404 if (is_dir($share_path)) {
405 $searchpath[] = $share_path;
406 }
407
408 // User commands, residing in ~/.drush
409 if (!empty($_SERVER['HOME'])) {
410 $searchpath[] = $_SERVER['HOME'] . '/.drush';
411 }
412 break;
413 case DRUSH_BOOTSTRAP_DRUPAL_SITE:
414 // Add all module paths, even disabled modules. Prefer speed over accuracy.
415 $searchpath[] = 'sites/all/modules';
416 $searchpath[] = conf_path() . '/modules';
417 // Too early for variable_get('install_profile', 'default'); Just use default.
418 $searchpath[] = "profiles/default/modules";
419 break;
420 case DRUSH_BOOTSTRAP_DRUPAL_FULL:
421 // Add enabled module paths. Since we are bootstrapped,
422 // we can use the Drupal API.
423 $files = drush_get_modules();
424 foreach ($files as $file) {
425 if (isset($file->status)) {
426 if ($file->status) {
427 $searchpath[] = dirname($file->filename);
428 }
429 }
430 }
431 break;
432
433 }
434
435 if (sizeof($searchpath)) {
436 $list = array();
437
438 // Scan for drush command files, load if found
439 foreach (array_unique($searchpath) as $path) {
440 if (is_dir($path)) {
441 $files = drush_scan_directory($path, '/\.drush\.inc$/');
442 foreach ($files as $filename => $info) {
443 require_once($filename);
444 $list[basename($filename, '.drush.inc')] = $filename;
445 }
446 }
447 }
448
449 if (sizeof($list)) {
450 $cache = array_merge($cache, $list);
451 ksort($cache);
452 }
453 }
454 }
455
456 /**
457 * Conditionally include files based on the command used.
458 *
459 * Steps through each of the currently loaded commandfiles and
460 * loads an optional commandfile based on the key.
461 *
462 * When a command such as 'pm install' is called, this
463 * function will find all 'install.pm.inc' files that
464 * are present in each of the commandfile directories.
465 */
466 function drush_command_include($command) {
467 $parts = explode(' ', $command);
468 $command = implode(".", array_reverse($parts));
469
470 $commandfiles = drush_commandfile_list();
471 $options = array();
472 foreach ($commandfiles as $commandfile => $file) {
473 $filename = sprintf("%s/%s.inc", dirname($file), $command);
474 if (file_exists($filename)) {
475 drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap');
476 include_once($filename);
477 }
478 }
479 }
480
481 /**
482 * Determine whether a command file implements a hook.
483 *
484 * @param $module
485 * The name of the module (without the .module extension).
486 * @param $hook
487 * The name of the hook (e.g. "help" or "menu").
488 * @return
489 * TRUE if the the hook is implemented.
490 */
491 function drush_command_hook($commandfile, $hook) {
492 return function_exists($commandfile .'_'. $hook);
493 }
494
495
496 /**
497 * Finds all files that match a given mask in a given directory.
498 * Directories and files beginning with a period are excluded; this
499 * prevents hidden files and directories (such as SVN working directories
500 * and GIT repositories) from being scanned.
501 *
502 * @param $dir
503 * The base directory for the scan, without trailing slash.
504 * @param $mask
505 * The regular expression of the files to find.
506 * @param $nomask
507 * An array of files/directories to ignore.
508 * @param $callback
509 * The callback function to call for each match.
510 * @param $recurse
511 * When TRUE, the directory scan will recurse the entire tree
512 * starting at the provided directory.
513 * @param $key
514 * The key to be used for the returned array of files. Possible
515 * values are "filename", for the path starting with $dir,
516 * "basename", for the basename of the file, and "name" for the name
517 * of the file without an extension.
518 * @param $min_depth
519 * Minimum depth of directories to return files from.
520 * @param $depth
521 * Current depth of recursion. This parameter is only used internally and should not be passed.
522 *
523 * @return
524 * An associative array (keyed on the provided key) of objects with
525 * "path", "basename", and "name" members corresponding to the
526 * matching files.
527 */
528 function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) {
529 $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
530 $files = array();
531
532 if (is_dir($dir) && $handle = opendir($dir)) {
533 while (FALSE !== ($file = readdir($handle))) {
534 if (!in_array($file, $nomask) && $file[0] != '.') {
535 if (is_dir("$dir/$file") && $recurse) {
536 // Give priority to files in this folder by merging them in after any subdirectory files.
537 $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
538 }
539 elseif ($depth >= $min_depth && preg_match($mask, $file)) {
540 // Always use this match over anything already set in $files with the same $$key.
541 $filename = "$dir/$file";
542 $basename = basename($file);
543 $name = substr($basename, 0, strrpos($basename, '.'));
544 $files[$$key] = new stdClass();
545 $files[$$key]->filename = $filename;
546 $files[$$key]->basename = $basename;
547 $files[$$key]->name = $name;
548 if ($callback) {
549 $callback($filename);
550 }
551 }
552 }
553 }
554
555 closedir($handle);
556 }
557
558 return $files;
559 }
560
561 /**
562 * Check that a command is valid for the current bootstrap phase.
563 *
564 * @param $command
565 * Command to check. Any errors will be added to the 'bootstrap_errors' element.
566 *
567 * @return
568 * TRUE if command is valid.
569 */
570 function drush_enforce_requirement_bootstrap_phase(&$command) {
571 $valid = array();
572 $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
573 if ($command['bootstrap'] <= $current_phase) {
574 return TRUE;
575 }
576 // TODO: provide description text for each bootstrap level so we can give
577 // the user something more helpful and specific here.
578 $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']));
579 }
580
581 /**
582 * Check that a command has its declared dependencies available or have no
583 * dependencies.
584 *
585 * @param $command
586 * Command to check. Any errors will be added to the 'bootstrap_errors' element.
587 *
588 * @return
589 * TRUE if command is valid.
590 */
591 function drush_enforce_requirement_drupal_dependencies(&$command) {
592 if (empty($command['drupal dependencies'])) {
593 return TRUE;
594 }
595 else {
596 foreach ($command['drupal dependencies'] as $dependency) {
597 if (function_exists('module_exists') && module_exists($dependency)) {
598 return TRUE;
599 }
600 }
601 }
602 $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'])));
603 }
604
605 /**
606 * Check that a command is valid for the current major version of core.
607 *
608 * @param $command
609 * Command to check. Any errors will be added to the 'bootstrap_errors' element.
610 *
611 * @return
612 * TRUE if command is valid.
613 */
614 function drush_enforce_requirement_core(&$command) {
615 $core = $command['core'];
616 if (empty($core) || in_array(drush_drupal_major_version(), $core)) {
617 return TRUE;
618 }
619 $versions = array_pop($core);
620 if (!empty($core)) {
621 $versions = implode(', ', $core) . dt(' or ') . $versions;
622 }
623 $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
624 }

  ViewVC Help
Powered by ViewVC 1.1.2