| 1 |
<?php
|
| 2 |
// $Id: drush.inc,v 1.56 2009/10/26 02:26:37 weitzman Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* The drush API implementation and helpers.
|
| 7 |
*/
|
| 8 |
|
| 9 |
|
| 10 |
/**
|
| 11 |
* Dispatch a given set of commands.
|
| 12 |
* Modules can add commands by implementing hook_drush_command().
|
| 13 |
*
|
| 14 |
* @param
|
| 15 |
* Command whose callback you want to call, defaults to current command.
|
| 16 |
*/
|
| 17 |
function drush_dispatch($command = NULL) {
|
| 18 |
$command = ($command) ? $command : drush_get_command();
|
| 19 |
$return = FALSE;
|
| 20 |
|
| 21 |
if ($command) {
|
| 22 |
// Call the callback function of the active command.
|
| 23 |
$return = call_user_func_array($command['callback'], $command['arguments']);
|
| 24 |
}
|
| 25 |
|
| 26 |
// prevent a '1' at the end of the output
|
| 27 |
if ($return === TRUE) {
|
| 28 |
$return = '';
|
| 29 |
}
|
| 30 |
|
| 31 |
// Add a final log entry, just so a timestamp appears.
|
| 32 |
drush_log(dt('Command dispatch complete'), 'notice');
|
| 33 |
|
| 34 |
return $return;
|
| 35 |
}
|
| 36 |
|
| 37 |
/**
|
| 38 |
* Include a file, selecting a version specific file if available.
|
| 39 |
*
|
| 40 |
* For example, if you pass the path "/var/drush" and the name
|
| 41 |
* "update" when bootstrapped on a Drupal 6 site it will first check for
|
| 42 |
* the presence of "/var/drush/update_6.inc" in include it if exists. If this
|
| 43 |
* file does NOT exist it will proceed and check for "/var/drush/update.inc".
|
| 44 |
* If neither file exists, it will return FALSE.
|
| 45 |
*
|
| 46 |
* @param $path
|
| 47 |
* The path you want to search.
|
| 48 |
* @param $name
|
| 49 |
* The file base name you want to include (not including a version suffix
|
| 50 |
* or extension).
|
| 51 |
* @param $version
|
| 52 |
* The version suffix you want to include (could be specific to the software
|
| 53 |
* or platform your are connecting to) - defaults to the current Drupal core
|
| 54 |
* major version.
|
| 55 |
* @param $extension
|
| 56 |
* The extension - defaults to ".inc".
|
| 57 |
*
|
| 58 |
* @return
|
| 59 |
* TRUE if the file was found and included.
|
| 60 |
*/
|
| 61 |
function drush_include($path, $name, $version = NULL, $extension = 'inc') {
|
| 62 |
$version = ($version) ? $version : drush_drupal_major_version();
|
| 63 |
$file = sprintf("%s/%s_%s.%s", $path, $name, $version, $extension);
|
| 64 |
if (file_exists($file)) {
|
| 65 |
drush_log(dt('Including version specific file : @file', array('@file' => $file)));
|
| 66 |
include_once($file);
|
| 67 |
return TRUE;
|
| 68 |
}
|
| 69 |
$file = sprintf("%s/%s.%s", $path, $name, $extension);
|
| 70 |
if (file_exists($file)) {
|
| 71 |
drush_log(dt('Including non-version specific file : @file', array('@file' => $file)));
|
| 72 |
include_once($file);
|
| 73 |
return TRUE;
|
| 74 |
}
|
| 75 |
}
|
| 76 |
|
| 77 |
/**
|
| 78 |
* Return a structured array of engines of a specific type from commandfiles
|
| 79 |
* implementing hook_drush_engine_$type.
|
| 80 |
*
|
| 81 |
* Engines are pluggable subsystems. Each engine of a specific type will
|
| 82 |
* implement the same set of API functions and perform the same high-level
|
| 83 |
* task using a different backend or approach.
|
| 84 |
*
|
| 85 |
* This function/hook is useful when you have a selection of several mutually
|
| 86 |
* exclusive options to present to a user to select from.
|
| 87 |
*
|
| 88 |
* Other commands are able to extend this list and provide their own engines.
|
| 89 |
* The hook can return useful information to help users decide which engine
|
| 90 |
* they need, such as description or list of available engine options.
|
| 91 |
*
|
| 92 |
* The engine path element will automatically default to a subdirectory (within
|
| 93 |
* the directory of the commandfile that implemented the hook) with the name of
|
| 94 |
* the type of engine - e.g. an engine "wget" of type "handler" provided by
|
| 95 |
* the "pm" commandfile would automatically be found if the file
|
| 96 |
* "pm/handler/wget.inc" exists and a specific path is not provided.
|
| 97 |
*
|
| 98 |
* @param $type
|
| 99 |
* The type of engine.
|
| 100 |
*
|
| 101 |
* @return
|
| 102 |
* A structured array of engines.
|
| 103 |
*/
|
| 104 |
function drush_get_engines($type) {
|
| 105 |
$engines = array();
|
| 106 |
$list = drush_commandfile_list();
|
| 107 |
foreach ($list as $commandfile => $path) {
|
| 108 |
if (drush_command_hook($commandfile, 'drush_engine_' . $type)) {
|
| 109 |
$function = $commandfile . '_drush_engine_' . $type;
|
| 110 |
$result = $function();
|
| 111 |
foreach ((array)$result as $key => $engine) {
|
| 112 |
// Add some defaults
|
| 113 |
$engine += array(
|
| 114 |
'commandfile' => $commandfile,
|
| 115 |
// Engines by default live in a subdirectory of the commandfile that
|
| 116 |
// declared them, named as per the type of engine they are.
|
| 117 |
'path' => sprintf("%s/%s", dirname($path), $type),
|
| 118 |
);
|
| 119 |
$engines[$key] = $engine;
|
| 120 |
}
|
| 121 |
}
|
| 122 |
}
|
| 123 |
return $engines;
|
| 124 |
}
|
| 125 |
|
| 126 |
/**
|
| 127 |
* Include the engine code for a specific named engine of a certain type.
|
| 128 |
*
|
| 129 |
* If the engine type has implemented hook_drush_engine_$type the path to the
|
| 130 |
* engine specified in the array will be used.
|
| 131 |
*
|
| 132 |
* If you don't need to present any user options for selecting the engine
|
| 133 |
* (which is common if the selection is implied by the running environment)
|
| 134 |
* and you don't need to allow other modules to define their own engines you can
|
| 135 |
* simply pass the $path to the directory where the engines are, and the
|
| 136 |
* appropriate one will be included.
|
| 137 |
*
|
| 138 |
* Unlike drush_include this function will set errors if the requested engine
|
| 139 |
* cannot be found.
|
| 140 |
*
|
| 141 |
* @param $type
|
| 142 |
* The type of engine.
|
| 143 |
* @param $engine
|
| 144 |
* The key for the engine to be included.
|
| 145 |
* @param $version
|
| 146 |
* The version of the engine to be included - defaults to the current Drupal core
|
| 147 |
* major version.
|
| 148 |
* @param $path
|
| 149 |
* A path to include from, if the engine has no corresponding
|
| 150 |
* hook_drush_engine_$type item path.
|
| 151 |
* @return unknown_type
|
| 152 |
*/
|
| 153 |
function drush_include_engine($type, $engine, $version = NULL, $path = NULL) {
|
| 154 |
$engines = drush_get_engines($type);
|
| 155 |
if (!$path && isset($engines[$engine])) {
|
| 156 |
$path = $engines[$engine]['path'];
|
| 157 |
}
|
| 158 |
if (!$path) {
|
| 159 |
return drush_set_error('DRUSH_ENGINE INCLUDE_NO_PATH', dt('No !path was set for including the !type engine !engine.', array('!path' => $path, '!type' => $type, '!engine' => $engine)));
|
| 160 |
}
|
| 161 |
if (drush_include($path, $engine, $version)) {
|
| 162 |
return TRUE;
|
| 163 |
}
|
| 164 |
return drush_set_error('DRUSH_ENGINE INCLUDE_FAILED', dt('Unable to include the !type engine !engine from !path.' , array('!path' => $path, '!type' => $type, '!engine' => $engine)));
|
| 165 |
}
|
| 166 |
|
| 167 |
/**
|
| 168 |
* Detects the version number of the current Drupal installation,
|
| 169 |
* if any. Returns false if there is no current Drupal installation,
|
| 170 |
* or it is somehow broken.
|
| 171 |
*
|
| 172 |
* This function relies on the presence of DRUPAL_ROOT/modules/system/system.module
|
| 173 |
*
|
| 174 |
* @return
|
| 175 |
* A string containing the version number of the current
|
| 176 |
* Drupal installation, if any. Otherwise, return false.
|
| 177 |
*/
|
| 178 |
function drush_drupal_version() {
|
| 179 |
static $version = FALSE;
|
| 180 |
|
| 181 |
if (!$version) {
|
| 182 |
if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
|
| 183 |
if (file_exists($drupal_root . '/modules/system/system.module')) {
|
| 184 |
// We can safely include system.module as it contains defines and functions only.
|
| 185 |
require_once($drupal_root . '/modules/system/system.module');
|
| 186 |
// We just might be dealing with an early Drupal version (pre 4.7)
|
| 187 |
if (defined('VERSION')) {
|
| 188 |
$version = VERSION;
|
| 189 |
}
|
| 190 |
}
|
| 191 |
}
|
| 192 |
}
|
| 193 |
return $version;
|
| 194 |
}
|
| 195 |
|
| 196 |
/**
|
| 197 |
* Returns the Drupal major version number (5, 6, 7 ...)
|
| 198 |
*/
|
| 199 |
function drush_drupal_major_version() {
|
| 200 |
$major_version = FALSE;
|
| 201 |
if ($version = drush_drupal_version()) {
|
| 202 |
$version_parts = explode('.', $version);
|
| 203 |
if (is_numeric($version_parts[0])) {
|
| 204 |
$major_version = (integer)$version_parts[0];
|
| 205 |
}
|
| 206 |
}
|
| 207 |
return $major_version;
|
| 208 |
}
|
| 209 |
|
| 210 |
/**
|
| 211 |
* A db_result() that works for any version of Drupal.
|
| 212 |
*
|
| 213 |
* @param
|
| 214 |
* A Database result object.
|
| 215 |
*/
|
| 216 |
function drush_db_result($result) {
|
| 217 |
return drush_drupal_major_version() >= 7 ? $result->fetchField() : db_result($result);
|
| 218 |
}
|
| 219 |
|
| 220 |
/**
|
| 221 |
* A db_fetch_object() that works for any version of Drupal.
|
| 222 |
*
|
| 223 |
* @param
|
| 224 |
* A Database result object.
|
| 225 |
*/
|
| 226 |
function drush_db_fetch_object($result) {
|
| 227 |
return drush_drupal_major_version() >= 7 ? $result->fetchObject() : db_fetch_object($result);
|
| 228 |
}
|
| 229 |
|
| 230 |
/**
|
| 231 |
* Save a string to a temporary file. Does not depend on Drupal's API.
|
| 232 |
*
|
| 233 |
* @param string $data
|
| 234 |
* @return string
|
| 235 |
* A path to the file.
|
| 236 |
*/
|
| 237 |
function drush_save_data_to_temp_file($data) {
|
| 238 |
static $fp;
|
| 239 |
|
| 240 |
$fp = tmpfile();
|
| 241 |
fwrite($fp, $data);
|
| 242 |
$meta_data = stream_get_meta_data($fp);
|
| 243 |
return $meta_data['uri'];
|
| 244 |
}
|
| 245 |
|
| 246 |
/**
|
| 247 |
* Calls a given function, passing through all arguments unchanged.
|
| 248 |
*
|
| 249 |
* This should be used when calling possibly mutative or destructive functions
|
| 250 |
* (e.g. unlink() and other file system functions) so that can be suppressed
|
| 251 |
* if the simulation mode is enabled.
|
| 252 |
*
|
| 253 |
* @param $function
|
| 254 |
* The name of the function.
|
| 255 |
* @return
|
| 256 |
* The return value of the function, or TRUE if simulation mode is enabled.
|
| 257 |
*/
|
| 258 |
function drush_op($function) {
|
| 259 |
$args = func_get_args();
|
| 260 |
array_shift($args); // Skip function name
|
| 261 |
|
| 262 |
if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
|
| 263 |
drush_print("Calling $function(". implode(", ", $args) .')');
|
| 264 |
}
|
| 265 |
|
| 266 |
if (drush_get_context('DRUSH_SIMULATE')) {
|
| 267 |
return TRUE;
|
| 268 |
}
|
| 269 |
|
| 270 |
return call_user_func_array($function, $args);
|
| 271 |
}
|
| 272 |
|
| 273 |
/**
|
| 274 |
* Rudimentary replacement for Drupal API t() function.
|
| 275 |
*
|
| 276 |
* @param string
|
| 277 |
* String to process, possibly with replacement item.
|
| 278 |
* @param array
|
| 279 |
* An associative array of replacement items.
|
| 280 |
*
|
| 281 |
* @return
|
| 282 |
* The processed string.
|
| 283 |
*
|
| 284 |
* @see t()
|
| 285 |
*/
|
| 286 |
function dt($string, $args = array()) {
|
| 287 |
if (function_exists('t')) {
|
| 288 |
return t($string, $args);
|
| 289 |
}
|
| 290 |
else {
|
| 291 |
if (!empty($args)) {
|
| 292 |
return strtr($string, $args);
|
| 293 |
}
|
| 294 |
else {
|
| 295 |
return $string;
|
| 296 |
}
|
| 297 |
}
|
| 298 |
}
|
| 299 |
|
| 300 |
/**
|
| 301 |
* Get the available options for Drush for use by help page.
|
| 302 |
*
|
| 303 |
* @return
|
| 304 |
* An associative array containing the option definition as the key, and the description as the value,
|
| 305 |
* for each of the available options.
|
| 306 |
*/
|
| 307 |
function drush_get_option_help() {
|
| 308 |
// TODO: Add a hook for this, to allow other modules to add their options
|
| 309 |
$options['-r <path>, --root=<path>'] = dt("Drupal root directory to use (default: current directory)");
|
| 310 |
$options['-l <uri>, --uri=http://example.com'] = dt('URI of the drupal site to use (only needed in multisite environments)');
|
| 311 |
$options['-v, --verbose'] = dt('Display extra information about the command.');
|
| 312 |
$options['-d, --debug'] = dt('Display even more information, including internal messages.');
|
| 313 |
$options['-q, --quiet'] = dt('Hide all output');
|
| 314 |
$options['-y, --yes'] = dt("Assume 'yes' as answer to all prompts");
|
| 315 |
$options['-s, --simulate'] = dt("Simulate all relevant actions (don't actually change the system)");
|
| 316 |
$options['-i, --include'] = dt("A list of paths to search for drush commands");
|
| 317 |
$options['-c, --config'] = dt("Specify a config file to use. See example.drushrc.php");
|
| 318 |
$options['-u, --user'] = dt("Specify a user to login with. May be a name or a number.");
|
| 319 |
$options['-b, --backend'] = dt("Hide all output and return structured data (internal use only).");
|
| 320 |
$options['-p, --pipe'] = dt("Emit a compact representation of the command for scripting.");
|
| 321 |
$options['-n, --nocolor'] = dt("Suppress color highlighting on log messages.");
|
| 322 |
$options['-h, --help'] = dt("The absolute path to your PHP intepreter.");
|
| 323 |
return $options;
|
| 324 |
}
|
| 325 |
|
| 326 |
/**
|
| 327 |
* Prints out help for a given command.
|
| 328 |
*/
|
| 329 |
function drush_show_help($commands) {
|
| 330 |
$phases = _drush_bootstrap_phases();
|
| 331 |
|
| 332 |
$commandstring = implode(" ", $commands);
|
| 333 |
|
| 334 |
foreach ($phases as $phase_index) {
|
| 335 |
if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) {
|
| 336 |
drush_bootstrap($phase_index);
|
| 337 |
}
|
| 338 |
if (!drush_get_error()) {
|
| 339 |
$commands = drush_get_commands();
|
| 340 |
if (array_key_exists($commandstring, $commands)) {
|
| 341 |
$command = $commands[$commandstring];
|
| 342 |
|
| 343 |
// Merge in engine specific help.
|
| 344 |
foreach ($command['engines'] as $type => $description) {
|
| 345 |
$all_engines = drush_get_engines($type);
|
| 346 |
foreach ($all_engines as $name => $engine) {
|
| 347 |
$command = array_merge_recursive($command, $engine);
|
| 348 |
}
|
| 349 |
}
|
| 350 |
|
| 351 |
$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command']);
|
| 352 |
if (!empty($help)) {
|
| 353 |
drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80)));
|
| 354 |
drush_print();
|
| 355 |
|
| 356 |
// TODO: Let commands define additional sections.
|
| 357 |
$sections = array(
|
| 358 |
'examples' => 'Examples',
|
| 359 |
'arguments' => 'Arguments',
|
| 360 |
'options' => 'Options',
|
| 361 |
);
|
| 362 |
|
| 363 |
foreach ($sections as $key => $value) {
|
| 364 |
if (!empty($command[$key])) {
|
| 365 |
drush_print(dt($value) . ':');
|
| 366 |
foreach ($command[$key] as $name => $description) {
|
| 367 |
// '[command] is a token representing the current command. @see pm_drush_engine_version_control().
|
| 368 |
$rows[] = array(str_replace('[command]', $commandstring, $name), dt($description));
|
| 369 |
}
|
| 370 |
drush_print_table($rows, false, array(40));
|
| 371 |
unset($rows);
|
| 372 |
drush_print();
|
| 373 |
}
|
| 374 |
}
|
| 375 |
|
| 376 |
// Append aliases if any.
|
| 377 |
if ($command['aliases']) {
|
| 378 |
drush_print(dt("Aliases: ") . implode(', ', $command['aliases']));
|
| 379 |
}
|
| 380 |
|
| 381 |
return TRUE;
|
| 382 |
}
|
| 383 |
else {
|
| 384 |
drush_print("No help available for command '$commandstring'.");
|
| 385 |
return TRUE;
|
| 386 |
}
|
| 387 |
}
|
| 388 |
}
|
| 389 |
else {
|
| 390 |
break;
|
| 391 |
}
|
| 392 |
}
|
| 393 |
return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring)));
|
| 394 |
}
|
| 395 |
|
| 396 |
/**
|
| 397 |
* Executes a shell command.
|
| 398 |
* Output is only printed if in verbose mode.
|
| 399 |
* Output is stored and can be retrieved using drush_shell_exec_output().
|
| 400 |
* If in simulation mode, no action is taken.
|
| 401 |
*
|
| 402 |
* @param $cmd
|
| 403 |
* The command to execute. May include placeholders used for sprintf.
|
| 404 |
* @param ...
|
| 405 |
* Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line.
|
| 406 |
* @return
|
| 407 |
* 0 if success.
|
| 408 |
*/
|
| 409 |
function drush_shell_exec($cmd) {
|
| 410 |
if (drush_get_context('DRUSH_SIMULATE')) {
|
| 411 |
return true;
|
| 412 |
}
|
| 413 |
$args = func_get_args();
|
| 414 |
|
| 415 |
//do not change the command itself, just the parameters.
|
| 416 |
for ($x = 1; $x < sizeof($args); $x++) {
|
| 417 |
$args[$x] = escapeshellarg($args[$x]);
|
| 418 |
}
|
| 419 |
$command = call_user_func_array('sprintf', $args);
|
| 420 |
|
| 421 |
if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
|
| 422 |
drush_log('Executing: ' . $command);
|
| 423 |
}
|
| 424 |
|
| 425 |
exec($command . ' 2>&1', $output, $result);
|
| 426 |
|
| 427 |
_drush_shell_exec_output_set($output);
|
| 428 |
|
| 429 |
if (drush_get_context('DRUSH_VERBOSE')) {
|
| 430 |
foreach ($output as $line) {
|
| 431 |
drush_print($line, 2);
|
| 432 |
}
|
| 433 |
}
|
| 434 |
|
| 435 |
// Exit code 0 means success.
|
| 436 |
return ($result == 0);
|
| 437 |
}
|
| 438 |
|
| 439 |
/**
|
| 440 |
* Stores output for the most recent shell command.
|
| 441 |
* This should only be run from drush_shell_exec().
|
| 442 |
*
|
| 443 |
* @param $output
|
| 444 |
* The output of the most recent shell command.
|
| 445 |
* If this is not set the stored value will be returned.
|
| 446 |
*/
|
| 447 |
function _drush_shell_exec_output_set($output = FALSE) {
|
| 448 |
static $stored_output;
|
| 449 |
if (!$output) return $stored_output;
|
| 450 |
$stored_output = $output;
|
| 451 |
}
|
| 452 |
|
| 453 |
/**
|
| 454 |
* Returns the output of the most recent shell command as an array of lines.
|
| 455 |
*/
|
| 456 |
function drush_shell_exec_output() {
|
| 457 |
return _drush_shell_exec_output_set();
|
| 458 |
}
|
| 459 |
|
| 460 |
/**
|
| 461 |
* Exits with a message. In general, you should use drush_set_error() instead of
|
| 462 |
* this function. That lets drush proceed with other tasks.
|
| 463 |
* TODO: Exit with a correct status code.
|
| 464 |
*/
|
| 465 |
function drush_die($msg = NULL, $status = NULL) {
|
| 466 |
die($msg ? "drush: $msg\n" : '');
|
| 467 |
}
|
| 468 |
|
| 469 |
/**
|
| 470 |
* Prints a message with optional indentation. In general,
|
| 471 |
* drush_log($message, 'ok') is often a better choice than this function.
|
| 472 |
* That gets your confirmation message (for example) into the logs for this
|
| 473 |
* drush request. Consider that drush requests may be executed remotely and
|
| 474 |
* non interactively.
|
| 475 |
*
|
| 476 |
* @param $message
|
| 477 |
* The message to print.
|
| 478 |
* @param $indent
|
| 479 |
* The indentation (space chars)
|
| 480 |
*/
|
| 481 |
function drush_print($message = '', $indent = 0) {
|
| 482 |
print str_repeat(' ', $indent) . (string)$message . "\n";
|
| 483 |
}
|
| 484 |
|
| 485 |
/**
|
| 486 |
* Stores a message which is printed during drush_shutdown() if in compact mode.
|
| 487 |
* @param $message
|
| 488 |
* The message to print.
|
| 489 |
*/
|
| 490 |
function drush_print_pipe($message = '') {
|
| 491 |
$buffer = &drush_get_context('DRUSH_PIPE_BUFFER' , '');
|
| 492 |
$buffer .= $message;
|
| 493 |
}
|
| 494 |
|
| 495 |
/**
|
| 496 |
* Prints an array or string.
|
| 497 |
* @param $array
|
| 498 |
* The array to print.
|
| 499 |
*/
|
| 500 |
function drush_print_r($array) {
|
| 501 |
print_r($array);
|
| 502 |
}
|
| 503 |
|
| 504 |
/**
|
| 505 |
* Ask the user a basic yes/no question.
|
| 506 |
*
|
| 507 |
* @param $msg The question to ask
|
| 508 |
* @return TRUE if the user entered 'y', FALSE if he entered 'n'
|
| 509 |
*/
|
| 510 |
function drush_confirm($msg, $indent = 0) {
|
| 511 |
print str_repeat(' ', $indent) . (string)$msg . " (y/n): ";
|
| 512 |
|
| 513 |
if (drush_get_context('DRUSH_AFFIRMATIVE')) {
|
| 514 |
print "y\n";
|
| 515 |
return TRUE;
|
| 516 |
}
|
| 517 |
// See http://drupal.org/node/499758 before changing this.
|
| 518 |
$stdin = fopen("php://stdin","r");
|
| 519 |
|
| 520 |
while ($line = fgets($stdin)) {
|
| 521 |
$line = trim($line);
|
| 522 |
if ($line == 'y') {
|
| 523 |
return TRUE;
|
| 524 |
}
|
| 525 |
if ($line == 'n') {
|
| 526 |
return FALSE;
|
| 527 |
}
|
| 528 |
print str_repeat(' ', $indent) . (string)$msg . " (y/n): ";
|
| 529 |
}
|
| 530 |
}
|
| 531 |
|
| 532 |
/**
|
| 533 |
* Print a formatted table.
|
| 534 |
*
|
| 535 |
* @param $rows
|
| 536 |
* The rows to print.
|
| 537 |
* @param $header
|
| 538 |
* If TRUE, the first line will be treated as table header and therefore be
|
| 539 |
* underlined.
|
| 540 |
* @param $widths
|
| 541 |
* The widths of each column (in characters) to use - if not specified this
|
| 542 |
* will be determined automatically, based on a "best fit" algorithm.
|
| 543 |
*/
|
| 544 |
function drush_print_table($rows, $header = FALSE, $widths = array()) {
|
| 545 |
$tbl = new Console_Table(CONSOLE_TABLE_ALIGN_LEFT , '');
|
| 546 |
|
| 547 |
$auto_widths = drush_table_column_autowidth($rows, $widths);
|
| 548 |
|
| 549 |
// Do wordwrap on all cells.
|
| 550 |
$newrows = array();
|
| 551 |
foreach ($rows as $rowkey => $row) {
|
| 552 |
foreach ($row as $col_num => $cell) {
|
| 553 |
$newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE);
|
| 554 |
if (isset($widths[$col_num])) {
|
| 555 |
$newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]);
|
| 556 |
}
|
| 557 |
}
|
| 558 |
}
|
| 559 |
if ($header) {
|
| 560 |
$headers = array_shift($newrows);
|
| 561 |
$tbl->setHeaders($headers);
|
| 562 |
}
|
| 563 |
|
| 564 |
$tbl->addData($newrows);
|
| 565 |
print $tbl->getTable();
|
| 566 |
}
|
| 567 |
|
| 568 |
/**
|
| 569 |
* Determine the best fit for column widths.
|
| 570 |
*
|
| 571 |
* @param $rows
|
| 572 |
* The rows to use for calculations.
|
| 573 |
* @param $widths
|
| 574 |
* Manually specified widths of each column (in characters) - these will be
|
| 575 |
* left as is.
|
| 576 |
*/
|
| 577 |
function drush_table_column_autowidth($rows, $widths) {
|
| 578 |
$auto_widths = $widths;
|
| 579 |
|
| 580 |
// First we determine the distribution of row lengths in each column.
|
| 581 |
// This is an array of descending character length keys (i.e. starting at
|
| 582 |
// the rightmost character column), with the value indicating the number
|
| 583 |
// of rows where that character column is present.
|
| 584 |
$col_dist = array();
|
| 585 |
foreach ($rows as $rowkey => $row) {
|
| 586 |
foreach ($row as $col_num => $cell) {
|
| 587 |
if (empty($widths[$col_num])) {
|
| 588 |
$length = strlen($cell);
|
| 589 |
while ($length > 0) {
|
| 590 |
if (!isset($col_dist[$col_num][$length])) {
|
| 591 |
$col_dist[$col_num][$length] = 0;
|
| 592 |
}
|
| 593 |
$col_dist[$col_num][$length]++;
|
| 594 |
$length--;
|
| 595 |
}
|
| 596 |
}
|
| 597 |
}
|
| 598 |
}
|
| 599 |
foreach ($col_dist as $col_num => $count) {
|
| 600 |
// Sort the distribution in decending key order.
|
| 601 |
krsort($col_dist[$col_num]);
|
| 602 |
// Initially we set all columns to their "ideal" longest width
|
| 603 |
// - i.e. the width of their longest column.
|
| 604 |
$auto_widths[$col_num] = max(array_keys($col_dist[$col_num]));
|
| 605 |
}
|
| 606 |
|
| 607 |
// We determine what width we have available to use, and what width the
|
| 608 |
// above "ideal" columns take up.
|
| 609 |
$available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2);
|
| 610 |
$auto_width_current = array_sum($auto_widths);
|
| 611 |
|
| 612 |
// If we need to reduce a column so that we can fit the space we use this
|
| 613 |
// loop to figure out which column will cause the "least wrapping",
|
| 614 |
// (relative to the other columns) and reduce the width of that column.
|
| 615 |
while ($auto_width_current > $available_width) {
|
| 616 |
$count = 0;
|
| 617 |
$width = 0;
|
| 618 |
foreach ($col_dist as $col_num => $counts) {
|
| 619 |
// If we are just starting out, select the first column.
|
| 620 |
if ($count == 0 ||
|
| 621 |
// OR: if this column would cause less wrapping than the currently
|
| 622 |
// selected column, then select it.
|
| 623 |
(current($counts) < $count) ||
|
| 624 |
// OR: if this column would cause the same amount of wrapping, but is
|
| 625 |
// longer, then we choose to wrap the longer column (proportionally
|
| 626 |
// less wrapping, and helps avoid triple line wraps).
|
| 627 |
(current($counts) == $count && key($counts) > $width)) {
|
| 628 |
// Select the column number, and record the count and current width
|
| 629 |
// for later comparisons.
|
| 630 |
$column = $col_num;
|
| 631 |
$count = current($counts);
|
| 632 |
$width = key($counts);
|
| 633 |
}
|
| 634 |
}
|
| 635 |
if ($width <= 1) {
|
| 636 |
// If we have reached a width of 1 then give up, so wordwrap can still progress.
|
| 637 |
break;
|
| 638 |
}
|
| 639 |
// Reduce the width of the selected column.
|
| 640 |
$auto_widths[$column]--;
|
| 641 |
// Reduce our overall table width counter.
|
| 642 |
$auto_width_current--;
|
| 643 |
// Remove the corresponding data from the disctribution, so next time
|
| 644 |
// around we use the data for the row to the left.
|
| 645 |
unset($col_dist[$column][$width]);
|
| 646 |
}
|
| 647 |
return $auto_widths;
|
| 648 |
}
|
| 649 |
|
| 650 |
/**
|
| 651 |
* @defgroup logging Logging information to be provided as output.
|
| 652 |
* @{
|
| 653 |
*
|
| 654 |
* These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken
|
| 655 |
* by drush.
|
| 656 |
*/
|
| 657 |
|
| 658 |
/**
|
| 659 |
* Add a log message to the log history.
|
| 660 |
*
|
| 661 |
* This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with
|
| 662 |
* the resulting entry at the end of execution.
|
| 663 |
*
|
| 664 |
* This allows you to replace it with custom logging implementations if needed,
|
| 665 |
* such as logging to a file or logging to a database (drupal or otherwise).
|
| 666 |
*
|
| 667 |
* The default callback is the _drush_print_log() function with prints the messages
|
| 668 |
* to the shell.
|
| 669 |
*
|
| 670 |
* @param message
|
| 671 |
* String containing the message to be logged.
|
| 672 |
* @param type
|
| 673 |
* The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'.
|
| 674 |
* A type of 'failed' can also be supplied to flag as an 'error'.
|
| 675 |
* A type of 'ok' or 'completed' can also be supplied to flag as a 'success'
|
| 676 |
* All other types of messages will be assumed to be notices.
|
| 677 |
*/
|
| 678 |
function drush_log($message, $type = 'notice', $error = null) {
|
| 679 |
$log =& drush_get_context('DRUSH_LOG', array());
|
| 680 |
$callback = drush_get_context('DRUSH_LOG_CALLBACK', '_drush_print_log');
|
| 681 |
$entry = array(
|
| 682 |
'type' => $type,
|
| 683 |
'message' => $message,
|
| 684 |
'timestamp' => microtime(TRUE),
|
| 685 |
);
|
| 686 |
$entry['error'] = $error;
|
| 687 |
$log[] = $entry;
|
| 688 |
return $callback($entry);
|
| 689 |
}
|
| 690 |
|
| 691 |
/**
|
| 692 |
* Retrieve the log messages from the log history
|
| 693 |
*
|
| 694 |
* @return
|
| 695 |
* Entire log history
|
| 696 |
*/
|
| 697 |
function drush_get_log() {
|
| 698 |
return drush_get_context('DRUSH_LOG', array());
|
| 699 |
}
|
| 700 |
|
| 701 |
/**
|
| 702 |
* Run print_r on a variable and log the output.
|
| 703 |
*/
|
| 704 |
function dlm($object) {
|
| 705 |
ob_start();
|
| 706 |
print_r($object);
|
| 707 |
$contents = ob_get_contents();
|
| 708 |
ob_end_clean();
|
| 709 |
|
| 710 |
drush_log($contents);
|
| 711 |
}
|
| 712 |
|
| 713 |
/*
|
| 714 |
* Display the pipe output for the current request.
|
| 715 |
*/
|
| 716 |
function drush_pipe_output() {
|
| 717 |
$pipe = drush_get_context('DRUSH_PIPE_BUFFER');
|
| 718 |
drush_print_r($pipe);
|
| 719 |
}
|
| 720 |
|
| 721 |
/**
|
| 722 |
* Display the log message
|
| 723 |
*
|
| 724 |
* By default, only warnings and errors will be displayed, if 'verbose' is specified, it will also display notices.
|
| 725 |
*
|
| 726 |
* @param
|
| 727 |
* The associative array for the entry.
|
| 728 |
*
|
| 729 |
* @return
|
| 730 |
* False in case of an error or failed type, True in all other cases.
|
| 731 |
*/
|
| 732 |
function _drush_print_log($entry) {
|
| 733 |
if (drush_get_context('DRUSH_NOCOLOR')) {
|
| 734 |
$red = "[%s]";
|
| 735 |
$yellow = "[%s]";
|
| 736 |
$green = "[%s]";
|
| 737 |
}
|
| 738 |
else {
|
| 739 |
$red = "\033[31;40m\033[1m[%s]\033[0m";
|
| 740 |
$yellow = "\033[1;33;40m\033[1m[%s]\033[0m";
|
| 741 |
$green = "\033[0;33;40m\033[1m[%s]\033[0m";
|
| 742 |
}
|
| 743 |
|
| 744 |
$verbose = drush_get_context('DRUSH_VERBOSE');
|
| 745 |
$debug = drush_get_context('DRUSH_DEBUG');
|
| 746 |
|
| 747 |
$return = TRUE;
|
| 748 |
switch ($entry['type']) {
|
| 749 |
case 'warning' :
|
| 750 |
$type_msg = sprintf($yellow, $entry['type']);
|
| 751 |
break;
|
| 752 |
case 'failed' :
|
| 753 |
case 'error' :
|
| 754 |
$type_msg = sprintf($red, $entry['type']);
|
| 755 |
$return = FALSE;
|
| 756 |
break;
|
| 757 |
case 'ok' :
|
| 758 |
case 'completed' :
|
| 759 |
case 'success' :
|
| 760 |
$type_msg = sprintf($green, $entry['type']);
|
| 761 |
break;
|
| 762 |
case 'notice' :
|
| 763 |
case 'message' :
|
| 764 |
if (!$verbose) {
|
| 765 |
// print nothing. exit cleanly.
|
| 766 |
return TRUE;
|
| 767 |
}
|
| 768 |
$type_msg = sprintf("[%s]", $entry['type']);
|
| 769 |
break;
|
| 770 |
default :
|
| 771 |
if (!$debug) {
|
| 772 |
// print nothing. exit cleanly.
|
| 773 |
return TRUE;
|
| 774 |
}
|
| 775 |
$type_msg = sprintf("[%s]", $entry['type']);
|
| 776 |
break;
|
| 777 |
}
|
| 778 |
|
| 779 |
// When running in backend mode, log messages are not displayed, as they will
|
| 780 |
// be returned in the JSON encoded associative array.
|
| 781 |
if (drush_get_context('DRUSH_BACKEND')) {
|
| 782 |
return $return;
|
| 783 |
}
|
| 784 |
|
| 785 |
$columns = drush_get_context('DRUSH_COLUMNS', 80);
|
| 786 |
|
| 787 |
$width[1] = 11;
|
| 788 |
// Append timer value.
|
| 789 |
if ($debug) {
|
| 790 |
$timer = sprintf('[%s sec]', round($entry['timestamp']-DRUSH_REQUEST_TIME, 3));
|
| 791 |
$entry['message'] = $entry['message'] . ' ' . $timer;
|
| 792 |
}
|
| 793 |
|
| 794 |
$width[0] = ($columns - 11);
|
| 795 |
|
| 796 |
$format = sprintf("%%-%ds%%%ds", $width[0], $width[1]);
|
| 797 |
|
| 798 |
// Place the status message right aligned with the top line of the error message.
|
| 799 |
$message = wordwrap($entry['message'], $width[0]);
|
| 800 |
$lines = explode("\n", $message);
|
| 801 |
$lines[0] = sprintf($format, $lines[0], $type_msg);
|
| 802 |
$message = implode("\n", $lines);
|
| 803 |
drush_print($message);
|
| 804 |
return $return;
|
| 805 |
}
|
| 806 |
|
| 807 |
// Log all timers for the request except standard page timer.
|
| 808 |
// Useful for migrate commands. Lets see if others find it useful.
|
| 809 |
// Called at end of request. @see drush.php
|
| 810 |
function drush_log_timers() {
|
| 811 |
global $timers;
|
| 812 |
foreach ((array)$timers as $name => $timerec) {
|
| 813 |
drush_log("Timer '$name' is " . sprintf('%s sec.', round(timer_read($name)/1000, 3)), 'timer');
|
| 814 |
}
|
| 815 |
}
|
| 816 |
/**
|
| 817 |
* Turn drupal_set_message errors into drush_log errors
|
| 818 |
*/
|
| 819 |
function _drush_log_drupal_messages() {
|
| 820 |
if (function_exists('drupal_get_messages')) {
|
| 821 |
|
| 822 |
$messages = drupal_get_messages();
|
| 823 |
|
| 824 |
if (array_key_exists('error', $messages)) {
|
| 825 |
//Drupal message errors.
|
| 826 |
foreach ((array) $messages['error'] as $error) {
|
| 827 |
$error = strip_tags($error);
|
| 828 |
if (preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error)) {
|
| 829 |
//This is a special case for an unavoidable warning
|
| 830 |
//that is generated by generating output before Drupal is bootstrapped.
|
| 831 |
//Simply ignore it.
|
| 832 |
continue;
|
| 833 |
}
|
| 834 |
elseif (preg_match('/^warning:/i', $error)) {
|
| 835 |
drush_log(preg_replace('/^warning: /i', '', $error), 'warning');
|
| 836 |
}
|
| 837 |
elseif (preg_match('/^notice:/i', $error)) {
|
| 838 |
drush_log(preg_replace('/^notice: /i', '', $error), 'notice');
|
| 839 |
}
|
| 840 |
elseif (preg_match('/^user warning:/i', $error)) {
|
| 841 |
// This is a special case. PHP logs sql errors as 'User Warnings', not errors.
|
| 842 |
drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error));
|
| 843 |
}
|
| 844 |
else {
|
| 845 |
drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error);
|
| 846 |
}
|
| 847 |
}
|
| 848 |
}
|
| 849 |
}
|
| 850 |
}
|
| 851 |
|
| 852 |
/**
|
| 853 |
* Log the return value of Drupal hook_update_n functions.
|
| 854 |
*
|
| 855 |
* This is used during install and update to log the output
|
| 856 |
* of the update process to the logging system.
|
| 857 |
*/
|
| 858 |
function _drush_log_update_sql($ret) {
|
| 859 |
if (sizeof($ret)) {
|
| 860 |
foreach ($ret as $info) {
|
| 861 |
if (is_array($info)) {
|
| 862 |
if (!$info['success']) {
|
| 863 |
drush_set_error('DRUPAL_UPDATE_FAILED', $info['query']);
|
| 864 |
}
|
| 865 |
else {
|
| 866 |
drush_log($info['query'], ($info['success']) ? 'success' : 'error');
|
| 867 |
}
|
| 868 |
}
|
| 869 |
}
|
| 870 |
}
|
| 871 |
}
|
| 872 |
|
| 873 |
/**
|
| 874 |
* @} End of "defgroup logging".
|
| 875 |
*/
|
| 876 |
|
| 877 |
/**
|
| 878 |
* @name Error status definitions
|
| 879 |
* @{
|
| 880 |
* Error code definitions for interpreting the current error status.
|
| 881 |
* @see drush_set_error(), drush_get_error(), drush_get_error_log(), drush_cmp_error()
|
| 882 |
*/
|
| 883 |
|
| 884 |
/** The command completed successfully. */
|
| 885 |
define('DRUSH_SUCCESS', 0);
|
| 886 |
/** The command could not be completed because the framework has specified errors that have occured. */
|
| 887 |
define('DRUSH_FRAMEWORK_ERROR', 1);
|
| 888 |
/** The command that was executed resulted in an application error,
|
| 889 |
The most commom causes for this is invalid PHP or a broken SSH
|
| 890 |
pipe when using drush_backend_invoke in a distributed manner. */
|
| 891 |
define('DRUSH_APPLICATION_ERROR', 255);
|
| 892 |
|
| 893 |
/**
|
| 894 |
* @} End of "name Error status defintions".
|
| 895 |
*/
|
| 896 |
|
| 897 |
/**
|
| 898 |
* @defgroup errorhandling Managing errors that occur in the Drush framework.
|
| 899 |
* @{
|
| 900 |
* Functions that manage the current error status of the Drush framework.
|
| 901 |
*
|
| 902 |
* These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an
|
| 903 |
* error has occurred.
|
| 904 |
* This error code is returned at the end of program execution, and provide the shell or calling application with
|
| 905 |
* more information on how to diagnose any problems that may have occurred.
|
| 906 |
*/
|
| 907 |
|
| 908 |
/**
|
| 909 |
* Set an error code for the error handling system.
|
| 910 |
*
|
| 911 |
* @param error
|
| 912 |
* A text string identifying the type of error.
|
| 913 |
*
|
| 914 |
* @param message
|
| 915 |
* Optional. Error message to be logged. If no message is specified, hook_drush_help will be consulted,
|
| 916 |
* using a key of 'error:MY_ERROR_STRING'.
|
| 917 |
*
|
| 918 |
* @return
|
| 919 |
* Always returns FALSE, to allow you to return with false in the calling functions,
|
| 920 |
* such as <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code>
|
| 921 |
*/
|
| 922 |
function drush_set_error($error, $message = null) {
|
| 923 |
$error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
|
| 924 |
$error_code = DRUSH_FRAMEWORK_ERROR;
|
| 925 |
|
| 926 |
$error_log =& drush_get_context('DRUSH_ERROR_LOG', array());
|
| 927 |
|
| 928 |
if (is_numeric($error)) {
|
| 929 |
$error = 'DRUSH_FRAMEWORK_ERROR';
|
| 930 |
}
|
| 931 |
|
| 932 |
$message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error);
|
| 933 |
|
| 934 |
if (is_array($message)) {
|
| 935 |
$message = implode("\n", $message);
|
| 936 |
}
|
| 937 |
|
| 938 |
$error_log[$error][] = $message;
|
| 939 |
drush_log(($message) ? $message : $error, 'error', $error);
|
| 940 |
|
| 941 |
return FALSE;
|
| 942 |
}
|
| 943 |
|
| 944 |
/**
|
| 945 |
* Return the current error handling status
|
| 946 |
*
|
| 947 |
* @return
|
| 948 |
* The current aggregate error status
|
| 949 |
*/
|
| 950 |
function drush_get_error() {
|
| 951 |
return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
|
| 952 |
}
|
| 953 |
|
| 954 |
/**
|
| 955 |
* Return the current list of errors that have occurred.
|
| 956 |
*
|
| 957 |
* @return
|
| 958 |
* An associative array of error messages indexed by the type of message.
|
| 959 |
*/
|
| 960 |
function drush_get_error_log() {
|
| 961 |
return drush_get_context('DRUSH_ERROR_LOG', array());
|
| 962 |
}
|
| 963 |
|
| 964 |
/**
|
| 965 |
* Check if a specific error status has been set.
|
| 966 |
*
|
| 967 |
* @param error
|
| 968 |
* A text string identifying the error that has occurred.
|
| 969 |
* @return
|
| 970 |
* TRUE if the specified error has been set, FALSE if not
|
| 971 |
*/
|
| 972 |
function drush_cmp_error($error) {
|
| 973 |
$error_log = drush_get_error_log();
|
| 974 |
|
| 975 |
if (is_numeric($error)) {
|
| 976 |
$error = 'DRUSH_FRAMEWORK_ERROR';
|
| 977 |
}
|
| 978 |
|
| 979 |
return array_key_exists($error, $error_log);
|
| 980 |
}
|
| 981 |
|
| 982 |
/**
|
| 983 |
* Turn PHP error handling off.
|
| 984 |
*
|
| 985 |
* This is commonly used while bootstrapping Drupal for install
|
| 986 |
* or updates.
|
| 987 |
*/
|
| 988 |
function drush_errors_off() {
|
| 989 |
$errors =& drush_get_context('DRUSH_ERROR_REPORTING', 0);
|
| 990 |
$errors = error_reporting(0);
|
| 991 |
ini_set('display_errors', FALSE);
|
| 992 |
}
|
| 993 |
|
| 994 |
/**
|
| 995 |
* Turn PHP error handling on.
|
| 996 |
*/
|
| 997 |
function drush_errors_on() {
|
| 998 |
$errors =& drush_get_context('DRUSH_ERROR_REPORTING', E_ALL ^ E_NOTICE);
|
| 999 |
$errors = error_reporting($errors);
|
| 1000 |
ini_set('display_errors', TRUE);
|
| 1001 |
}
|
| 1002 |
|
| 1003 |
/**
|
| 1004 |
* @} End of "defgroup errorhandling".
|
| 1005 |
*/
|
| 1006 |
|