Newer
Older
Moshe Weitzman
committed
<?php
Greg Anderson
committed
/**
* @defgroup dispatching Command dispatching functions.
* @{
*
* These functions handle command dispatching, and can
* be used to programatically invoke drush commands in
* different ways.
*/
/**
* Invokes a Drush API call, including all hooks.
Greg Anderson
committed
*
* Executes the specified command with the specified arguments on the currently
* bootstrapped site using the current option contexts. Note that it will not
* bootstrap any further than the current command has already bootstrapped;
* therefore, you should only invoke commands that have the same (or lower)
* bootstrap requirements.
Greg Anderson
committed
*
* Commands execute with the same options that the user provided on the
* commandline. If you need to invoke another Drush command with options you
* specify, use drush_invoke_process() instead.
*
* @param string $command
* The command to invoke.
* @param array $arguments
* An array of argument to pass into the command.
*
* @return mixed|false
* The return value from drush_dispatch() or FALSE on error.
*
* @see drush_invoke_process()
Greg Anderson
committed
*/
function drush_invoke($command, $arguments = array()) {
// Convert a standalone argument to a single-element array.
Greg Anderson
committed
if (!is_array($arguments)) {
$arguments = array($arguments);
}
$commands = drush_get_commands();
if (array_key_exists($command, $commands)) {
$command = $commands[$command];
// Drush overloads the 'arguments' element, which contains the help string
// for the allowed arguments for the command when fetched, and is fixed up
// by _drush_prepare_command() to contain the actual commandline arguments
// during dispatching.
Greg Anderson
committed
$command['arguments'] = array();
return drush_dispatch($command, $arguments);
}
else {
return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!command' could not be found.", array('!command' => $command)));
}
}
/**
* Invoke a command in a new process, targeting the site specified by
* the provided site alias record.
*
* Use this function instead of drush_backend_invoke_sitealias,
* drush_backend_invoke_args, or drush_backend_invoke_command
* (all obsolete in drush 5).
*
* @param array $site_alias_record
* The site record to execute the command on. Use '@self' to run on the current site.
* @param string $command_name
* The command to invoke.
* @param array $commandline_args
* The arguments to pass to the command.
* @param array $commandline_options
* The options (e.g. --select) to provide to the command.
* @param $backend_options
* TRUE - integrate errors
* FALSE - do not integrate errors
Greg Anderson
committed
* array - @see drush_backend_invoke_concurrent
* There are also several options that _only_ work when set in
Greg Anderson
committed
* this parameter. They include:
* 'invoke-multiple'
* If $site_alias_record represents a single site, then 'invoke-multiple'
* will cause the _same_ command with the _same_ arguments and options
* to be invoked concurrently (e.g. for running concurrent batch processes).
* 'concurrency'
* Limits the number of concurrent processes that will run at the same time.
* Defaults to '4'.
* 'override-simulated'
* Forces the command to run, even in 'simulated' mode. Useful for
* commands that do not change any state on the machine, e.g. to fetch
* database information for sql-sync via sql-conf.
* 'interactive'
* Overrides the backend invoke process to run commands interactively.
Greg Anderson
committed
* 'fork'
* Overrides the backend invoke process to run non blocking commands in
* the background. Forks a new process by adding a '&' at the end of the
* command. The calling process does not receive any output from the child
Greg Anderson
committed
* process. The fork option is used to spawn a process that outlives its
* parent.
Greg Anderson
committed
*
* @return
* If the command could not be completed successfully, FALSE.
* If the command was completed, this will return an associative
* array containing the results of the API call.
* @see drush_backend_get_result()
Greg Anderson
committed
*
* Do not change the signature of this function! drush_invoke_process
* is one of the key Drush APIs. See http://drupal.org/node/1152908
Greg Anderson
committed
*/
function drush_invoke_process($site_alias_record, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = TRUE) {
Greg Anderson
committed
if (is_array($site_alias_record) && array_key_exists('site-list', $site_alias_record)) {
$site_alias_records = drush_sitealias_resolve_sitespecs($site_alias_record['site-list']);
$site_alias_records = drush_sitealias_simplify_names($site_alias_records);
foreach ($site_alias_records as $alias_name => $alias_record) {
$invocations[] = array(
'site' => $alias_record,
'command' => $command_name,
'args' => $commandline_args,
);
}
Greg Anderson
committed
}
Greg Anderson
committed
else {
$invocations[] = array(
'site' => $site_alias_record,
'command' => $command_name,
'args' => $commandline_args);
$invoke_multiple = drush_get_option_override($backend_options, 'invoke-multiple', 0);
if ($invoke_multiple) {
Greg Anderson
committed
$invocations = array_fill(0, $invoke_multiple, $invocations[0]);
Greg Anderson
committed
}
Greg Anderson
committed
}
Greg Anderson
committed
return drush_backend_invoke_concurrent($invocations, $commandline_options, $backend_options);
Greg Anderson
committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
}
/**
* 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 bootstrapped; therefore, you should only invoke
* commands that have the same (or lower) bootstrap requirements.
*
* @param command
* A full $command such as returned by drush_get_commands(),
* or a string containing the name of the command record from
* drush_get_commands() to call.
* @param arguments
* An array of argument values.
*
* @see drush_topic_docs_topic().
*/
function drush_dispatch($command, $arguments = array()) {
drush_set_command($command);
$return = FALSE;
if ($command) {
// Add arguments, if this has not already been done.
// (If the command was fetched from drush_parse_command,
// then you cannot provide arguments to drush_dispatch.)
if (empty($command['arguments'])) {
_drush_prepare_command($command, $arguments);
}
Greg Anderson
committed
drush_command_default_options($command);
// Test to see if any of the options in the 'cli' context
// are not represented in the command structure.
if ((_drush_verify_cli_options($command) === FALSE) || (_drush_verify_cli_arguments($command) === FALSE)) {
return FALSE;
}
// Give command files an opportunity to alter the command record
drush_command_invoke_all_ref('drush_command_alter', $command);
if (drush_load_command_engines($command) === FALSE) {
Greg Anderson
committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Call the callback function of the active command.
$return = call_user_func_array($command['callback'], $command['arguments']);
}
// Add a final log entry, just so a timestamp appears.
drush_log(dt('Command dispatch complete'), 'notice');
return $return;
}
/**
* 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';
if (drush_get_option('show-invoke')) {
drush_log(dt("Calling global init hook: !func", array('!name' => $name, '!func' => $func . '()')), 'bootstrap');
}
call_user_func_array($func, $args);
_drush_log_drupal_messages();
}
if (!drush_get_error()) {
$result = _drush_invoke_hooks($command, $args);
Greg Anderson
committed
}
if (!drush_get_error()) {
foreach (drush_command_implements('drush_exit') as $name) {
$func = $name . '_drush_exit';
if (drush_get_option('show-invoke')) {
drush_log(dt("Calling global exit hook: !func", array('!name' => $name, '!func' => $func . '()')), 'bootstrap');
}
call_user_func_array($func, $args);
_drush_log_drupal_messages();
}
}
}
/**
Moshe Weitzman
committed
* Invoke Drush API calls, including all hooks.
Greg Anderson
committed
*
* This is an internal function; it is called from drush_dispatch via
* drush_command, but only if the command does not specify a 'callback'
* function. If a callback function is specified, it will be called
* instead of drush_command + _drush_invoke_hooks.
*
* Executes the specified command with the specified arguments on the
* currently bootstrapped site using the current option contexts.
* Note that _drush_invoke_hooks will not bootstrap any further than the
* current command has already bootstrapped; therefore, you should only invoke
* commands that have the same (or lower) bootstrap requirements.
*
Greg Anderson
committed
* 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,
Greg Anderson
committed
* in reverse order from how they were executed. Rollbacks are also triggered any
* time a hook function returns FALSE.
Greg Anderson
committed
*
* This function will also trigger pre_$hook and post_$hook variants of the hook
* and its rollbacks automatically.
*
* HOW DRUSH HOOK FUNCTIONS ARE NAMED:
*
* The name of the hook is composed from the name of the command and the name of
* the command file that the command definition is declared in. The general
* form for the hook filename is:
*
* drush_COMMANDFILE_COMMANDNAME
*
* In many cases, drush commands that are functionally part of a common collection
* of similar commands will all be declared in the same file, and every command
* defined in that file will start with the same command prefix. For example, the
* command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
* In the case of "pm-enable", the command file is "pm", and and command name is
* "pm-enable". When the command name starts with the same sequence of characters
* as the command file, then the repeated sequence is dropped; thus, the command
* hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
*
Greg Anderson
committed
* There is also a special Drupal-version-specific naming convention that may
* be used. To hide a commandfile from all versions of Drupal except for the
* specific one named, add a ".dVERSION" after the command prefix. For example,
* the file "views.d8.drush.inc" defines a "views" commandfile that will only
* load with Drupal 8. This feature is not necessary and should not be used
* in contrib modules (any extension with a ".module" file), since these modules
* are already version-specific.
*
Greg Anderson
committed
* @param command
* The drush command to execute.
Greg Anderson
committed
* @param args
* An array of arguments to the command OR a single non-array argument.
Greg Anderson
committed
* @return
Moshe Weitzman
committed
* The return value will be passed along to the caller if --backend option is
* present. A boolean FALSE indicates failure and rollback will be intitated.
Greg Anderson
committed
*
Greg Anderson
committed
* This function should not be called directly.
* @see drush_invoke() and @see drush_invoke_process()
Greg Anderson
committed
*/
function _drush_invoke_hooks($command, $args) {
Greg Anderson
committed
// If someone passed a standalone arg, convert it to a single-element array
if (!is_array($args)) {
$args = array($args);
}
// Include the external command file used by this command, if there is one.
drush_command_include($command['command-hook']);
Greg Anderson
committed
// Generate the base name for the hook by converting all
// dashes in the command name to underscores.
$hook = str_replace("-", "_", $command['command-hook']);
Greg Anderson
committed
// Call the hook init function, if it exists.
// If a command needs to bootstrap, it is advisable
// to do so in _init; otherwise, new commandfiles
// will miss out on participating in any stage that
// has passed or started at the time it was discovered.
$func = 'drush_' . $hook . '_init';
if (function_exists($func)) {
drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), 'bootstrap');
call_user_func_array($func, $args);
_drush_log_drupal_messages();
if (drush_get_error()) {
drush_log(dt('The command @command could not be initialized.', array('@command' => $command['command-hook'])), 'error');
Greg Anderson
committed
return FALSE;
}
}
$rollback = FALSE;
$completed = array();
$available_rollbacks = array();
$all_available_hooks = array();
// Iterate through the different hook variations
Moshe Weitzman
committed
$variations = array($hook . "_pre_validate", $hook . "_validate", "pre_$hook", $hook, "post_$hook");
Greg Anderson
committed
foreach ($variations as $var_hook) {
// Get the list of command files.
// We re-fetch the list every time through
// the loop in case one of the hook function
// does something that will add additional
// commandfiles to the list (i.e. bootstrapping
// to a higher phase will do this).
$list = drush_commandfile_list();
// Make a list of function callbacks to call. Make sure
// that the primary callback for this command (the one
// from the commandfile that defines this command) always
// appears first in the list
$primary_func = ($command['commandfile'] . "_" == substr($var_hook . "_",0,strlen($command['commandfile']) + 1)) ? sprintf("drush_%s", $var_hook) : sprintf("drush_%s_%s", $command['commandfile'], $var_hook);
$callback_list = array($primary_func => $list[$command['commandfile']]);
// We've got the callback for the primary function in the
// callback list; now add all of the other callback functions.
unset($list[$command['commandfile']]);
Greg Anderson
committed
foreach ($list as $commandfile => $filename) {
$func = sprintf("drush_%s_%s", $commandfile, $var_hook);
$callback_list[$func] = $filename;
}
// Run all of the functions available for this variation
$accumulated_result = NULL;
foreach ($callback_list as $func => $filename) {
if (function_exists($func)) {
Greg Anderson
committed
$all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
Greg Anderson
committed
$available_rollbacks[] = $func . '_rollback';
$completed[] = $func;
Greg Anderson
committed
$result = call_user_func_array($func, $args);
// If there is an error, break out of the foreach
// $variations and foreach $callback_list
Greg Anderson
committed
if (drush_get_error() || ($result === FALSE)) {
Greg Anderson
committed
$rollback = TRUE;
break 2;
}
// If result values are arrays, then combine them all together.
// Later results overwrite earlier results.
if (isset($result) && is_array($accumulated_result) && is_array($result)) {
$accumulated_result = array_merge($accumulated_result, $result);
}
else {
$accumulated_result = $result;
}
_drush_log_drupal_messages();
Greg Anderson
committed
}
else {
$all_available_hooks[] = $func;
}
}
// Process the result value from the 'main' callback hook only.
if ($var_hook == $hook) {
$return = $accumulated_result;
if (isset($return)) {
drush_handle_command_output($command, $return);
}
}
Greg Anderson
committed
}
// If no hook functions were found, print a warning.
Greg Anderson
committed
if (empty($completed)) {
$default_command_hook = sprintf("drush_%s_%s", $command['commandfile'], $hook);
if (($command['commandfile'] . "_" == substr($hook . "_",0,strlen($command['commandfile'])+ 1))) {
Greg Anderson
committed
$default_command_hook = sprintf("drush_%s", $hook);
}
James Sansbury
committed
$dt_args = array(
'!command' => $command['command-hook'],
James Sansbury
committed
'!default_func' => $default_command_hook,
);
$message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks.";
$return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args));
Greg Anderson
committed
}
Greg Anderson
committed
if (drush_get_option('show-invoke')) {
Greg Anderson
committed
// We show all available hooks up to and including the one that failed (or all, if there were no failures)
drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command['command-hook'], '!available' => "\n" . implode("\n", $all_available_hooks))), 'ok');
Greg Anderson
committed
}
Greg Anderson
committed
if (drush_get_option('show-invoke') && !empty($available_rollbacks)) {
drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command['command-hook'], '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'ok');
Greg Anderson
committed
}
Greg Anderson
committed
if ($rollback) {
if (drush_get_option('confirm-rollback', FALSE)) {
// Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process.
drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
drush_set_context('DRUSH_NEGATIVE', FALSE);
$rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)'));
}
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(dt("Changes made in !func have been rolled back.", array('!func' => $func)), 'rollback');
}
Greg Anderson
committed
}
}
Greg Anderson
committed
}
Jonathan
committed
if (isset($return)) {
return $return;
}
Greg Anderson
committed
}
* Convert the structured output array provided from the Drush
* command into formatted output. Output is only printed for commands
* that define 'default-format' &/or 'default-pipe-format'; all
* other commands are expected to do their own output.
function drush_handle_command_output($command, $structured_output) {
// If the hook already called drush_backend_set_result,
// then return that value. If it did not, then the return
// value from the hook will be the value returned from
// this routine.
$return = drush_backend_get_result();
if (empty($return)) {
drush_backend_set_result($structured_output);
// We skip empty strings and empty arrays, but note that 'empty'
// returns TRUE for the integer value '0', but we do wat to print that.
// Only handle output here if the command defined an output format
// engine. If no engine was declared, then we presume that the command
// handled its own output.
if ((!empty($structured_output) || ($structured_output === 0)) && !empty($command['engines']['outputformat'])) {
// If the command specifies a default pipe format and
// returned a result, then output the formatted output when
// in --pipe mode.
$formatter = drush_get_outputformat();
if ($formatter) {
if ($formatter === TRUE) {
return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
}
if (!in_array($formatter->selected_engine, $command['engines']['outputformat']['usable'])) {
return $formatter->format_error(dt("The command '!command' does not produce output in a structure usable by this output format.", array('!command' => $command['command'])));
}
// Add any user-specified options to the metadata passed to the formatter.
$metadata = array();
Greg Anderson
committed
$metadata['strict'] = drush_get_option('strict', FALSE);
if (isset($formatter->engine_config['options'])) {
if (drush_get_option('full', FALSE)) {
if (isset($formatter->engine_config['fields-full'])) {
$formatter->engine_config['fields-default'] = $formatter->engine_config['fields-full'];
}
else {
Greg Anderson
committed
$formatter->engine_config['fields-default'] = array_keys($formatter->engine_config['field-labels']);
}
}
elseif (drush_get_context('DRUSH_PIPE') && isset($formatter->engine_config['fields-pipe'])) {
$formatter->engine_config['fields-default'] = $formatter->engine_config['fields-pipe'];
}
foreach ($formatter->engine_config['options'] as $option => $option_info) {
$default_value = isset($formatter->engine_config[$option . '-default']) ? $formatter->engine_config[$option . '-default'] : FALSE;
$user_specified_value = drush_get_option_list($option, $default_value);
if ($user_specified_value !== FALSE) {
$metadata[$option] =$user_specified_value;
if (isset($metadata['fields']) && !empty($metadata['fields'])) {
if (isset($formatter->engine_config['field-labels'])) {
$formatter->engine_config['field-labels'] = drush_select_fields($formatter->engine_config['field-labels'], $metadata['fields'], $metadata['strict']);
}
}
$output = $formatter->process($structured_output, $metadata);
if (drush_get_context('DRUSH_PIPE')) {
drush_print_pipe($output);
}
else {
drush_print($output);
}
}
/**
* Fail with an error if the user specified options on the
* command line that are not documented in the current command
* record.
*/
function _drush_verify_cli_options($command) {
// Start out with just the options in the current command record.
$options = _drush_get_command_options($command);
// Skip all tests if the command is marked to allow anything.
Greg Anderson
committed
// Also skip backend commands, which may have options on the commandline
// that were inherited from the calling command.
if (($command['allow-additional-options'] === TRUE) || (drush_get_option(array('backend', 'invoke'), FALSE))) {
return TRUE;
}
// If 'allow-additional-options' contains a list of command names,
// then union together all of the options from all of the commands.
if (is_array($command['allow-additional-options'])) {
$implemented = drush_get_commands();
foreach ($command['allow-additional-options'] as $subcommand_name) {
if (array_key_exists($subcommand_name, $implemented)) {
$options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name]));
}
}
}
// Also add in global options
$options = array_merge($options, drush_get_global_options());
// Now we will figure out which options in the cli context
// are not represented in our options list.
$cli_options = array_keys(drush_get_context('cli'));
$allowed_options = _drush_flatten_options($options);
Moshe Weitzman
committed
$allowed_options = drush_append_negation_options($allowed_options);
$disallowed_options = array_diff($cli_options, $allowed_options);
if (!empty($disallowed_options)) {
$unknown = count($disallowed_options) > 1 ? dt('Unknown options') : dt('Unknown option');
$msg = dt("@unknown: --@options. See `drush help @command` for available options. To suppress this error, add the option --strict=0.", array('@unknown' => $unknown, '@options' => implode(', --', $disallowed_options), '@command' => $command['command']));
if (drush_get_option('strict', TRUE)) {
return drush_set_error('DRUSH_UNKNOWN_OPTION', $msg);
}
Greg Anderson
committed
// Next check to see if all required options were specified.
$missing_required_options = array();
foreach ($command['options'] as $key => $value) {
if (is_array($value) && array_key_exists('required', $value)) {
$option_value = drush_get_option($key, NULL);
if (!isset($option_value)) {
$missing_required_options[] = $key;
}
}
}
if (!empty($missing_required_options)) {
$missing = count($missing_required_options) > 1 ? dt('Missing required options') : dt('Missing required option');
return drush_set_error(dt("@missing: --@options. See `drush help @command` for information on usage.", array('@missing' => $missing, '@options' => implode(', --', $missing_required_options), '@command' => $command['command'])));
}
return TRUE;
}
Moshe Weitzman
committed
function drush_append_negation_options($allowed_options) {
$new_allowed = $allowed_options;
foreach ($allowed_options as $option) {
$new_allowed[] = 'no-' . $option;
}
return $new_allowed;
}
function _drush_verify_cli_arguments($command) {
// Check to see if all of the required arguments
// are specified.
if ($command['required-arguments']) {
$required_arg_count = $command['required-arguments'];
if ($required_arg_count === TRUE) {
$required_arg_count = count($command['argument-description']);
}
if ((count($command['arguments'])) < $required_arg_count) {
$missing = count($required_arg_count) > 1 ? dt('Missing required arguments') : dt('Missing required argument');
$required = implode("', '", array_keys($command['argument-description']));
return drush_set_error(dt("@missing: '@required'. See `drush help @command` for information on usage.", array('@missing' => $missing, '@required' => $required, '@command' => $command['command'])));
}
}
return TRUE;
}
/**
* Return the list of all of the options for the given
* command record by merging the 'options' and 'sub-options'
* records.
*/
function _drush_get_command_options($command) {
Greg Anderson
committed
drush_command_invoke_all_ref('drush_help_alter', $command);
$options = $command['options'];
foreach ($command['sub-options'] as $group => $suboptions) {
$options = array_merge($options, $suboptions);
}
return $options;
}
/**
* Return the array keys of $options, plus any 'short-form'
* representations that may appear in the option's value.
*/
function _drush_flatten_options($options) {
$flattened_options = array();
foreach($options as $key => $value) {
// engine sections start with 'package-handler=git_drupalorg',
// or something similar. Get rid of everything from the = onward.
if (($eq_pos = strpos($key, '=')) !== FALSE) {
$key = substr($key, 0, $eq_pos);
}
$flattened_options[] = $key;
if (is_array($value)) {
if (array_key_exists('short-form', $value)) {
$flattened_options[] = $value['short-form'];
}
}
}
return $flattened_options;
}
/**
* Get the options that were passed to the current command.
*
* This function returns an array that contains all of the options
Greg Anderson
committed
* that are appropriate for forwarding along to drush_invoke_process.
*
* @return
* An associative array of option key => value pairs.
*/
function drush_redispatch_get_options() {
// Start off by taking everything from the site alias and command line
// ('cli' context)
$cli_context = drush_get_context('cli');
// local php settings should not override sitealias settings
unset($cli_context['php']);
unset($cli_context['php-options']);
// cli overrides sitealias
$options = $cli_context + drush_get_context('alias');
$options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys()));
unset($options['command-specific']);
unset($options['path-aliases']);
// If we can parse the current command, then examine all contexts
// in order for any option that is directly related to the current command
$command = drush_parse_command();
if (is_array($command)) {
foreach ($command['options'] as $key => $value) {
// Strip leading --
$key = ltrim($key, '-');
$value = drush_get_option($key);
if (isset($value)) {
$options[$key] = $value;
}
}
}
// If --bootstrap-to-first-arg is specified, do not
// pass it along to remote commands.
unset($options['bootstrap-to-first-arg']);
Greg Anderson
committed
/**
* @} End of "defgroup dispatching".
*/
Moshe Weitzman
committed
/**
* @file
* 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
Moshe Weitzman
committed
* concepts and to make developing commands as familiar as possible
* to traditional Drupal module developers.
*/
Moshe Weitzman
committed
/**
* Parse console arguments.
*/
function drush_parse_args() {
$args = drush_get_context('argv');
$command_args = NULL;
$global_options = array();
$target_alias_name = NULL;
Greg Anderson
committed
// It would be nice if commandfiles could somehow extend this list,
// but it is not possible. We need to parse args before we find commandfiles,
// because the specified options may affect how commandfiles are located.
// Therefore, commandfiles are loaded too late to affect arg parsing.
// There are only a limited number of short options anyway; drush reserves
// all for use by drush core.
static $arg_opts = array('c', 'u', 'r', 'l', 'i');
Greg Anderson
committed
// Check to see if we were executed via a "#!/usr/bin/env drush" script
drush_adjust_args_if_shebang_script($args);
Greg Anderson
committed
// Now process the command line arguments. We will divide them
// into options (starting with a '-') and arguments.
$arguments = $options = array();
Moshe Weitzman
committed
for ($i = 1; $i < count($args); $i++) {
$opt = $args[$i];
// We set $command_args to NULL until the first argument that is not
// an alias is found (the command); we put everything that follows
// into $command_args.
if (is_array($command_args)) {
$command_args[] = $opt;
}
Moshe Weitzman
committed
// Is the arg an option (starting with '-')?
if (!empty($opt) && $opt{0} == "-" && strlen($opt) != 1) {
Moshe Weitzman
committed
// Do we have multiple options behind one '-'?
if (strlen($opt) > 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))) {
// Raising errors for missing option values should be handled by the
// bootstrap or specific command, so we no longer do this here.
Moshe Weitzman
committed
$options[$opt] = $args[$i + 1];
$i++;
}
else {
$options[$opt] = true;
}
}
}
// If it's not an option, it's a command.
else {
$arguments[] = $opt;
// Once we find the first argument, record the command args and global options
if (!is_array($command_args)) {
// Remember whether we set $target_alias_name on a previous iteration,
// then record the $target_alias_name iff this arguement references a valid site alias.
$already_set_target = is_string($target_alias_name);
if (!$already_set_target && drush_sitealias_valid_alias_format($opt)) {
$target_alias_name = $opt;
}
// If an alias record was set on a previous iteration, then this
// argument must be the command name. If we set the target alias
// record on this iteration, then this is not the command name.
// If we've found the command name, then save $options in $global_options
// (all options that came before the command name), and initialize
// $command_args to an array so that we will begin storing all args
// and options that follow the command name in $command_args.
if ($already_set_target || (!is_string($target_alias_name))) {
$command_args = array();
$global_options = $options;
}
}
Moshe Weitzman
committed
}
}
// If no arguments are specified, then the command will
// be either 'help' or 'version' (the later if --version is specified)
if (!sizeof($arguments)) {
if (array_key_exists('version', $options)) {
$arguments = array('version');
}
else {
$arguments = array('help');
}
}
if (is_array($command_args)) {
drush_set_context('DRUSH_COMMAND_ARGS', $command_args);
}
drush_set_context('DRUSH_GLOBAL_CLI_OPTIONS', $global_options);
Greg Anderson
committed
// Handle the "@shift" alias, if present
drush_process_bootstrap_to_first_arg($arguments);
Moshe Weitzman
committed
drush_set_arguments($arguments);
Greg Anderson
committed
drush_set_config_special_contexts($options);
drush_set_context('cli', $options);
return $arguments;
Moshe Weitzman
committed
}
Greg Anderson
committed
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
/**
* Pop an argument off of drush's argument list
*/
function drush_shift() {
$arguments = drush_get_arguments();
$result = NULL;
if (!empty($arguments)) {
// The php-script command uses the DRUSH_SHIFT_SKIP
// context to cause drush_shift to skip the 'php-script'
// command and the script path argument when it is
// called from the user script.
$skip_count = drush_get_context('DRUSH_SHIFT_SKIP');
if (is_numeric($skip_count)) {
for ($i = 0; $i < $skip_count; $i++) {
array_shift($arguments);
}
$skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0);
}
$result = array_shift($arguments);
drush_set_arguments($arguments);
}
return $result;
}
/**
* Special checking for "shebang" script handling.
*
* If there is a file 'script.php' that begins like so:
* #!/path/to/drush
* Then $args will be:
* /path/to/drush /path/to/script userArg1 userArg2 ...
* If it instead starts like this:
* #!/path/to/drush --flag php-script
* Then $args will be:
* /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ...
* (Note that execve does not split the parameters from
* the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29)
* When drush is called via one of the "shebang" lines above,
Greg Anderson
committed
* the first or second parameter will be the full path
* to the "shebang" script file -- and if the path to the
* script is in the second position, then we will expect that
* the argument in the first position must begin with a
* '@' (alias) or '-' (flag). Under ordinary circumstances,
* we do not expect that the drush command must come before
* any argument that is the full path to a file. We use
* this assumption to detect "shebang" script execution.
*/
function drush_adjust_args_if_shebang_script(&$args) {
Moshe Weitzman
committed
if (drush_has_bash()) {
if (_drush_is_drush_shebang_script($args[1])) {
// If $args[1] is a drush "shebang" script, we will insert
// the option "--bootstrap-to-first-arg" and the command
// "php-script" at the beginning of @args, so the command
// line args become:
// /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ...
drush_set_option('bootstrap-to-first-arg', TRUE);
array_splice($args, 1, 0, array('php-script'));
drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
Greg Anderson
committed
}
Moshe Weitzman
committed
elseif (((strpos($args[1], ' ') !== FALSE) || (!ctype_alnum($args[1][0]))) && (_drush_is_drush_shebang_script($args[2]))) {
// If $args[2] is a drush "shebang" script, we will insert
// the space-exploded $arg[1] in place of $arg[1], so the
// command line args become:
// /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ...
// If none of the script arguments look like a drush command,
// then we will insert "php-script" as the default command to
// execute.
$script_args = explode(' ', $args[1]);
$has_command = FALSE;
foreach ($script_args as $script_arg) {
if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) {
$has_command = TRUE;
}
}
if (!$has_command) {
$script_args[] = 'php-script';
}
array_splice($args, 1, 1, $script_args);
drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
Greg Anderson
committed
}
}
}
/**
* Process the --bootstrap-to-first-arg option, if it is present.
Greg Anderson
committed
*
* This option checks to see if the first user-provided argument is an alias
* or site specification; if it is, it will be shifted into the first argument
Greg Anderson
committed
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
* position, where it will specify the site to bootstrap. The result of this
* is that if your shebang line looks like this:
*
* #!/path/to/drush --bootstrap-to-first-arg php-script
*
* Then when you run that script, you can optionally provide an alias such
* as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1
* scriptarg2). Since this is the behavior that one would usually want,
* it is default behavior for a canonical script. That is, a script
* with a simple shebang line, like so:
*
* #!/path/to/drush
*
* will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore
* behave exactly like the first example. To write a script that does not
* use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly
* included, like so:
*
* #!/path/to/drush php-script
*/
function drush_process_bootstrap_to_first_arg(&$arguments) {
if (drush_get_option('bootstrap-to-first-arg', FALSE)) {
$shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE);
if (sizeof($arguments) >= $shift_alias_pos) {
$shifted_alias = $arguments[$shift_alias_pos];
$alias_record = drush_sitealias_get_record($shifted_alias);
if (!empty($alias_record)) {
Greg Anderson
committed
// Move the alias we shifted from its current position
// in the argument list to the front of the list
array_splice($arguments, $shift_alias_pos, 1);
array_unshift($arguments, $shifted_alias);
Greg Anderson
committed
}
}
}
}
Moshe Weitzman
committed
Moshe Weitzman
committed
/**
* 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.
Moshe Weitzman
committed
$command += drush_command_defaults($key, $commandfile, $path);
drush_merge_engine_data($command);
Jonathan
committed
// Translate command.
drush_command_translate($command);
Greg Anderson
committed
// If command callback function name begins with "drush_$commandfile_",
// then fix up the command entry so that drush_invoke will be
// called by way of drush_command. This will cause all
// of the applicable hook functions to be called for the
// command when it is invoked. If the callback function does
// -not- begin with its commandfile name, then it will be
// called directly by drush_dispatch, and no hook functions
// will be called (e.g. you cannot hook drush_print_file).
Moshe Weitzman
committed
if ($command['callback'] != 'drush_command') {
$required_command_prefix = 'drush_' . $commandfile . '_';
if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) {
Greg Anderson
committed
$command['command-hook'] = substr($command['callback'], strlen('drush_'));
Moshe Weitzman
committed
$command['callback'] = 'drush_command';
}
}
Moshe Weitzman
committed
$commands[$key] = $command;
Moshe Weitzman
committed
// For every alias, make a copy of the command and store it in the command list
// using the alias as a key
Moshe Weitzman
committed
if (isset($command['aliases']) && count($command['aliases'])) {
foreach ($command['aliases'] as $alias) {
$commands[$alias] = $command;
$commands[$alias]['is_alias'] = TRUE;
}
}
Moshe Weitzman
committed
return drush_set_context('DRUSH_COMMANDS', $commands);
Moshe Weitzman
committed
}
Moshe Weitzman
committed
function drush_command_defaults($key, $commandfile, $path) {
return array(
'command' => $key,
'command-hook' => $key,
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
Moshe Weitzman
committed
'commandfile' => $commandfile,
'path' => dirname($path),
'engines' => array(), // Helpful for drush_show_help().
'callback' => 'drush_command',
'description' => NULL,
'sections' => array(
'examples' => 'Examples',
'arguments' => 'Arguments',
'options' => 'Options',
),
'arguments' => array(),
'required-arguments' => FALSE,
Moshe Weitzman
committed
'options' => array(),
'sub-options' => array(),
'allow-additional-options' => FALSE,
Moshe Weitzman
committed
'examples' => array(),
'aliases' => array(),
'core' => array(),
'scope' => 'site',
'drupal dependencies' => array(),
'drush dependencies' => array(),
'handle-remote-commands' => FALSE,
Greg Anderson
committed
'strict-option-handling' => FALSE,
Moshe Weitzman
committed
'bootstrap_errors' => array(),
'topics' => array(),
'hidden' => FALSE,