2 && $opt{1} != "-") { // Each char becomes a key of its own. for ($j = 1; $j < strlen($opt); $j++) { $options[substr($opt, $j, 1)] = true; } } // Do we have a longopt (starting with '--')? elseif ($opt{1} == "-") { if ($pos = strpos($opt, '=')) { $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1); } else { $options[substr($opt, 2)] = true; } } else { $opt = substr($opt, 1); // Check if the current opt is in $arg_opts (= has to be followed by an argument). if ((in_array($opt, $arg_opts))) { if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) { drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument."); } $options[$opt] = $args[$i + 1]; $i++; } else { $options[$opt] = true; } } } // If it's not an option, it's a command. else { $arguments[] = $opt; } } // If arguments are specified, print the help screen. $arguments = sizeof($arguments) ? $arguments : array('help'); drush_set_arguments($arguments); drush_set_context('options', $options); } /** * Get a list of all implemented commands. * This invokes hook_drush_command(). * * @return * Associative array of currently active command descriptors. * */ function drush_get_commands() { $commands = $available_commands = array(); $list = drush_commandfile_list(); foreach ($list as $commandfile => $path) { if (drush_command_hook($commandfile, 'drush_command')) { $function = $commandfile . '_drush_command'; $result = $function(); foreach ((array)$result as $key => $command) { // Add some defaults and normalize the command descriptor $command += array( 'command' => $key, 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, 'commandfile' => $commandfile, 'path' => dirname($path), 'engines' => array(), // Helpful for drush_show_help(). 'callback' => 'drush_command', 'description' => NULL, 'arguments' => array(), 'options' => array(), 'extras' => array(), 'core' => array(), 'scope' => 'site', 'drupal dependencies' => array(), 'drush dependencies' => array(), 'bootstrap_errors' => array(), ); // Collect all the commands (without filtering) so we can match non-executable // commands, and later explain why they are not executable. drush_enforce_requirement_bootstrap_phase($command); drush_enforce_requirement_core($command); drush_enforce_requirement_drupal_dependencies($command); $commands[$key] = $command; } } } return drush_set_context('DRUSH_COMMANDS', $commands); } /** * Matches a commands array, as returned by drush_get_arguments, with the * current command table. * * Note that not all commands may be discoverable at the point-of-call, * since Drupal modules can ship commands as well, and they are * not available until after bootstrapping. * * drush_parse_command returns a normalized command descriptor, which * is an associative array with the following entries: * - callback: name of function to invoke for this command. * - callback arguments: an array of arguments to pass to the calback. * - description: description of the command. * - arguments: an array of arguments that are understood by the command. for help texts. * - options: an array of options that are understood by the command. for help texts. * - examples: an array of examples that are understood by the command. for help texts. * - scope: one of 'system', 'project', 'site'. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap. * - core: Drupal major version required. * - drupal dependencies: drupal modules required for this command. * - drush dependencies: other drush command files required for this command (not yet implemented) * * @example * drush_parse_command(); * */ function drush_parse_command() { $args = drush_get_arguments(); // Get a list of all implemented commands. $implemented = drush_get_commands(); $command = FALSE; $arguments = array(); // Try to determine the handler for the current command. while (!$command && count($args)) { $part = implode(" ", $args); if (isset($implemented[$part])) { $command = $implemented[$part]; } else { $arguments[] = array_pop($args); } } // We have found a command that matches. Set the appropriate values. if ($command) { $arguments = array_reverse($arguments); // Merge specified callback arguments, which precede the arguments passed on the command line. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) { $arguments = array_merge($command['callback arguments'], $arguments); } $command['arguments'] = $arguments; drush_set_command($command); } return $command; } /** * Invoke drush api calls. * * Call the correct hook for all the modules that implement it. * Additionally, the ability to rollback when an error has been encountered is also provided. * If at any point during execution, the drush_get_error() function returns anything but 0, * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it, * in reverse order from how they were executed. * * This function will also trigger pre_$hook and post_$hook variants of the hook * and it's rollbacks automatically. * * @param command * The drush command to execute. * @return * A boolean specifying whether or not the command was successfully completed. * */ function drush_invoke($command) { drush_command_include($command); $args = func_get_args(); array_shift($args); $hook = str_replace(" ", "_", $command); $list = drush_commandfile_list(); $functions = array(); // First we build a list of functions are about to execute $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook"); foreach ($variations as $var_hook) { foreach ($list as $commandfile => $filename) { $func = sprintf("drush_%s_%s", $commandfile, $var_hook); if (function_exists($func)) { $functions[] = $func; } } } $rollback = FALSE; $completed = array(); foreach ($functions as $func) { $completed[] = $func; if (function_exists($func)) { call_user_func_array($func, $args); _drush_log_drupal_messages(); if (drush_get_error()) { drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error'); # As soon as an error occurs, roll back $rollback = TRUE; break; } } } // something went wrong, we need to undo if ($rollback) { foreach (array_reverse($completed) as $func) { $rb_func = $func . '_rollback'; if (function_exists($rb_func)) { call_user_func_array($rb_func, $args); _drush_log_drupal_messages(); drush_log("Changes for $func module have been rolled back.", 'rollback'); } } } return !$rollback; } /** * Entry point for commands into the drush_invoke API * * If a command does not have a callback specified, this function will be called. * * This function will trigger $hook_drush_init, then if no errors occur, * it will call drush_invoke() with the command that was dispatch. * * If no errors have occured, it will run $hook_drush_exit. */ function drush_command() { $args = func_get_args(); $command = drush_get_command(); foreach (drush_command_implements("drush_init") as $name) { $func = $name . '_drush_init'; drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap'); call_user_func_array($func, $args); _drush_log_drupal_messages(); } if (!drush_get_error()) { call_user_func_array('drush_invoke', array_merge(array($command['command']), $args)); } if (!drush_get_error()) { foreach (drush_command_implements('drush_exit') as $name) { $func = $name . '_drush_exit'; call_user_func_array($func, $args); _drush_log_drupal_messages(); } } } /** * Invoke a hook in all available command files that implement it. * * @param $hook * The name of the hook to invoke. * @param ... * Arguments to pass to the hook. * @return * An array of return values of the hook implementations. If commands return * arrays from their implementations, those are merged into one array. */ function drush_command_invoke_all() { $args = func_get_args(); $hook = $args[0]; unset($args[0]); $return = array(); foreach (drush_command_implements($hook) as $module) { $function = $module .'_'. $hook; $result = call_user_func_array($function, $args); if (isset($result) && is_array($result)) { $return = array_merge_recursive($return, $result); } else if (isset($result)) { $return[] = $result; } } return $return; } /** * Determine which command files are implementing a hook. * * @param $hook * The name of the hook (e.g. "drush_help" or "drush_command"). * * @return * An array with the names of the command files which are implementing this hook. */ function drush_command_implements($hook) { $implementations[$hook] = array(); $list = drush_commandfile_list(); foreach ($list as $commandfile => $file) { if (drush_command_hook($commandfile, $hook)) { $implementations[$hook][] = $commandfile; } } return (array)$implementations[$hook]; } /** * @param string * name of command to check. * * @return boolean * TRUE if the given command has an implementation. */ function drush_is_command($command) { $commands = drush_get_commands(); return isset($commands[$command]); } /** * Collect a list of all available drush command files. * * Scans the following paths for drush command files: * * - The ".drush" folder in the users HOME folder. * - The "/path/to/drush/includes" folder. * - Folders listed in the 'include' option (see example.drushrc.php). * - Active modules in the current Drupal installation (if any). * * A drush command file is a file that matches "*.drush.inc". * * @see drush_scan_directory * * @return * An associative array whose keys and values are the names of all available * command files. */ function drush_commandfile_list() { return drush_get_context('DRUSH_COMMAND_FILES', array()); } function _drush_find_commandfiles($phase) { $cache =& drush_get_context('DRUSH_COMMAND_FILES', array()); $searchpath = array(); switch ($phase) { case DRUSH_BOOTSTRAP_DRUSH : // Core commands shipping with drush $searchpath[] = realpath(dirname(__FILE__) . '/../commands/'); // User commands, specified by 'include' option if ($include = drush_get_option(array('i', 'include'), FALSE)) { foreach (explode(":", $include) as $path) { $searchpath[] = $path; } } // User commands, residing i ~/.drush if (!empty($_SERVER['HOME'])) { $searchpath[] = $_SERVER['HOME'] . '/.drush'; } break; case DRUSH_BOOTSTRAP_DRUPAL_SITE: // Add all module paths, even disabled modules. Prefer speed over accuracy. $searchpath[] = 'sites/all/modules'; $searchpath[] = conf_path() . '/modules'; // Too early for variable_get('install_profile', 'default'); Just use default. $searchpath[] = "profiles/default/modules"; break; case DRUSH_BOOTSTRAP_DRUPAL_FULL : // Add enabled module paths. Since we are bootstrapped, // we can use the Drupal API. foreach (array_keys(module_rebuild_cache()) as $module) { $searchpath[] = drupal_get_path('module', $module); } break; } if (sizeof($searchpath)) { $list = array(); // Scan for drush command files, load if found foreach (array_unique($searchpath) as $path) { if (is_dir($path)) { $files = drush_scan_directory($path, '\.drush\.inc$'); foreach ($files as $filename => $info) { require_once($filename); $list[basename($filename, '.drush.inc')] = $filename; } } } if (sizeof($list)) { $cache = array_merge($cache, $list); } } } /** * Conditionally include files based on the command used. * * Steps through each of the currently loaded commandfiles and * loads an optional commandfile based on the key. * * When a command such as 'pm install' is called, this * function will find all 'install.pm.inc' files that * are present in each of the commandfile directories. */ function drush_command_include($command) { $parts = explode(' ', $command); $command = implode(".", array_reverse($parts)); $commandfiles = drush_commandfile_list(); $options = array(); foreach ($commandfiles as $commandfile => $file) { $filename = sprintf("%s/%s.inc", dirname($file), $command); if (file_exists($filename)) { drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap'); include_once($filename); } } } /** * Determine whether a command file implements a hook. * * @param $module * The name of the module (without the .module extension). * @param $hook * The name of the hook (e.g. "help" or "menu"). * @return * TRUE if the the hook is implemented. */ function drush_command_hook($commandfile, $hook) { return function_exists($commandfile .'_'. $hook); } /** * 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. * * @param $dir * The base directory for the scan, without trailing slash. * @param $mask * The regular expression of the files to find. * @param $nomask * An array of files/directories to ignore. * @param $callback * The callback function to call for each match. * @param $recurse * When TRUE, the directory scan will recurse the entire tree * starting at the provided directory. * @param $key * The key to be used for the returned array of files. Possible * values are "filename", for the path starting with $dir, * "basename", for the basename of the file, and "name" for the name * of the file without an extension. * @param $min_depth * Minimum depth of directories to return files from. * @param $depth * Current depth of recursion. This parameter is only used internally and should not be passed. * * @return * An associative array (keyed on the provided key) of objects with * "path", "basename", and "name" members corresponding to the * matching files. */ function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) { $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename'); $files = array(); if (is_dir($dir) && $handle = opendir($dir)) { while (FALSE !== ($file = readdir($handle))) { if (!in_array($file, $nomask) && $file[0] != '.') { if (is_dir("$dir/$file") && $recurse) { // Give priority to files in this folder by merging them in after any subdirectory files. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files); } elseif ($depth >= $min_depth && ereg($mask, $file)) { // Always use this match over anything already set in $files with the same $$key. $filename = "$dir/$file"; $basename = basename($file); $name = substr($basename, 0, strrpos($basename, '.')); $files[$$key] = new stdClass(); $files[$$key]->filename = $filename; $files[$$key]->basename = $basename; $files[$$key]->name = $name; if ($callback) { $callback($filename); } } } } closedir($handle); } return $files; } /** * Check that a command is valid for the current bootstrap phase. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_bootstrap_phase(&$command) { $valid = array(); $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); if ($command['bootstrap'] == $current_phase) { return TRUE; } // TODO: pprovide description text for each bootstrap level so we can give // the user something more helpful and specific here. $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'])); } /** * Check that a command has it's declared dependencies available or have no * dependencies. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_drupal_dependencies(&$command) { if (empty($command['drupal dependencies'])) { return TRUE; } else { foreach ($command['drupal dependencies'] as $dependency) { if (function_exists('module_exists') && module_exists($dependency)) { return TRUE; } } } $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']))); } /** * Check that a command is valid for the current major version of core. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_core(&$command) { $core = $command['core']; if (empty($core) || in_array(drush_drupal_major_version(), $core)) { return TRUE; } $versions = array_pop($core); if (!empty($core)) { $versions = implode(', ', $core) . dt(' or ') . $versions; } $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions)); }