Index: includes/drush.inc
===================================================================
RCS file: includes/drush.inc
diff -N includes/drush.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/drush.inc	13 Jun 2007 13:35:41 -0000
@@ -0,0 +1,337 @@
+<?php
+// $Id: drush.inc,v 1.13 2007/06/13 11:21:14 frando Exp $
+
+/**
+ * @file
+ * The drush API implementation and helpers.
+ */
+
+/**
+ * @defgroup drush drush (the Drupal shell)
+ *
+ * The drush provides a command-line interface to Drupal.
+ * Modules can implement hook_drush_command() to add command-line commands.
+ * TODO: Document hook_drush_command()
+ * @{
+ */
+
+/**
+ * Dispatch a given set of commands.
+ * Modules can add commands by implementing hook_drush_command().
+ */
+function drush_dispatch($commands) {
+  // Get a list of all implemented commands.
+  $implemented = drush_get_commands();
+
+  $go = TRUE;
+  $result = FALSE;
+  $funcargs = array();
+  // Try to determine the handler for the current command.
+  while (!$result && count($commands)) {
+    $command = implode(" ", $commands);
+    if (isset($implemented[$command])) {
+      $result = $implemented[$command];
+    }
+    else {
+      $funcargs[] = array_pop($commands);
+    }
+  }
+  if ($result) {
+    // Call the callback function of the active command.
+    return call_user_func_array($result['callback'], array_reverse($funcargs));
+  }
+  else {
+    // Special case: If no command is found, display usage info.
+    return call_user_func_array('drush_usage', array_reverse($funcargs));
+  }
+
+  return FALSE;
+}
+
+/**
+ * Get a list of all implemented commands.
+ * This invokes hook_drush_command().
+ */
+function drush_get_commands($refresh = FALSE) {
+  static $commands;
+
+  if (!$refresh && !empty($commands)) {
+    return $commands;
+  }
+  
+  // Add the built-in, hard-coded help command.
+  $commands['help'] = array(
+    'callback' => 'drush_callback_help',
+    'description' => 'View help. Run "drush help command" to view command-specific help.'
+  );
+  
+  $commands = $commands + module_invoke_all('drush_command', TRUE);
+
+  return $commands;
+}
+
+function drush_is_command($command) {
+  $commands = drush_get_commands();
+  return (isset($commands[$command])) ? TRUE : FALSE;
+}
+
+/**
+ * Calls a given function, passing through all arguments unchanged.
+ *
+ * This should be used when calling possibly mutative or destructive functions
+ * (e.g. unlink() and other file system functions) so that can be suppressed
+ * if the simulation mode is enabled.
+ *
+ * @param $function
+ *   The name of the function.
+ * @return
+ *   The return value of the function, or TRUE if simulation mode is enabled.
+ */
+function drush_op($function) {
+  $args = func_get_args();
+  array_shift($args); // Skip function name
+
+  if (DRUSH_VERBOSE || DRUSH_SIMULATE) {
+    drush_print("Calling $function(". implode(", ", $args) .')');
+  }
+
+  if (DRUSH_SIMULATE) {
+    return true;
+  }
+
+  return call_user_func_array($function, $args);
+}
+
+/**
+ * Executes a shell command.
+ * Output is only printed if in verbose mode.
+ * If in simulation mode, no action is taken.
+ *
+ * @param $cmd
+ *   The command to execute.
+ * @param $indent
+ *   Indentation for the output (only for verbose mode).
+ */
+function drush_shell_exec($cmd, $indent = 0) {
+  if (DRUSH_VERBOSE || DRUSH_SIMULATE) {
+    drush_print('Executing: ' . $cmd, $indent);
+  }
+
+  if (DRUSH_SIMULATE) {
+    return true;
+  }
+
+  exec($cmd . ' 2>&1', $output, $result);
+
+  if (DRUSH_VERBOSE) {
+    foreach ($output as $line) {
+      drush_print($line, $indent + 2);
+    }
+  }
+
+  // Exit code 0 means success.
+  return ($result == 0);
+}
+
+/**
+ * Exits with a message.
+ * TODO: Exit with a correct status code.
+ */
+function drush_die($msg = NULL, $status = NULL) {
+  die($msg ? "drush: $msg\n" : '');
+}
+
+/**
+ * Prints an error message.
+ * Always returns FALSE. This allows to do e.g.
+ * if ($error) { return drush_error('A error occured); }
+ * to make your function return FALSE in case of an error.
+ */
+function drush_error($msg = '') {
+  // TODO: print to stderr if running in CLI mode.
+  drush_print("E: ". $msg);
+  return FALSE;
+}
+
+/**
+ * Prints a message.
+ * @param $message
+ *   The message to print.
+ * @param $indent
+ *    The indentation (space chars)
+ */
+function drush_print($message = '', $indent = 0) {
+  print str_repeat(' ', $indent) . (string)$message . "\n";
+}
+
+/**
+ * Prints a message, but only if verbose mode is activated.
+ * Returns TRUE if in verbose mode, otherwise FALSE.
+ */
+function drush_verbose($msg = FALSE, $indent = 0) {
+  if (!DRUSH_VERBOSE) {
+    return FALSE;
+  }
+  if (DRUSH_VERBOSE && $msg === FALSE) {
+    return TRUE;
+  }
+
+  print str_repeat(' ', $indent) . (string)$msg . "\n";
+  return TRUE;
+}
+
+/**
+ * Ask the user a basic yes/no question.
+ *
+ * @param $msg The question to ask
+ * @return TRUE if the user entered 'y', FALSE if he entered 'n'
+ */
+function drush_confirm($msg, $indent = 0) {
+  print str_repeat(' ', $indent) . (string)$msg . " (y/n): ";
+
+  if (DRUSH_AFFIRMATIVE) {
+    print "y\n";
+    return TRUE;
+  }
+  while ($line = trim(fgets(STDIN))) {
+    if ($line == 'y') {
+      return TRUE;
+    }
+    if ($line == 'n') {
+      return FALSE;
+    }
+    print str_repeat(' ', $indent) . (string)$msg . " (y/n): ";
+  }
+
+}
+
+/**
+ * Print a formatted table.
+ * @param $rows
+ *   The rows to print
+ * @param $indent
+ *   Indentation for the whole table
+ * @param $header
+ *   If TRUE, the first line will be treated as table
+ *   header and therefore be underlined.
+ */
+function drush_print_table($rows, $indent = 0, $header = FALSE) {
+  if (count($rows) == 0) {
+    // Nothing to output.
+    return;
+  }
+
+  $indent = str_repeat(' ', $indent);
+  $format = _drush_get_table_row_format($rows);
+
+  $header_printed = FALSE;
+  foreach ($rows as $cols) {
+    // Print the current line.
+    print $indent . vsprintf($format, $cols) . "\n";
+    // Underline the first row if $header is set to true.
+    if (!$header_printed && $header) {
+      $headers = array();
+      foreach ($cols as $col) {
+        $headers[] = str_repeat('-', strlen($col));
+      }
+      print $indent . trim(vsprintf($format, $headers)) . "\n";
+      $header_printed = TRUE;
+    }
+  }
+}
+
+/**
+ * @} End of "defgroup drush".
+ */
+
+/**
+ * Create the table row format string to be used in vsprintf().
+ */
+function _drush_get_table_row_format($table) {
+  $widths = _drush_get_table_column_widths($table);
+  foreach ($widths as $col_width) {
+    $col_formats[] = "%-{$col_width}s";
+  }
+  $format = implode("\t", $col_formats);
+  return $format;
+}
+
+/**
+ * Calculate table column widths.
+ */
+function _drush_get_table_column_widths($table) {
+  $widths = array();
+  foreach ($table as $row => $cols) {
+    foreach ($cols as $col => $value) {
+      $old_width = isset($widths[$col]) ? $widths[$col] : 0;
+      $widths[$col] = max($old_width, strlen((string)$value));
+    }
+  }
+  return $widths;
+}
+
+/**
+ * Command callback. Display help.
+ */
+function drush_callback_help() {
+  $commands = func_get_args();
+
+  // Display general help text if no command is specified.
+  if (empty($commands)) {
+    drush_print('drush ' . VERSION);
+    drush_print(t('Usage: drush [options] <command> <command> ...'));
+    drush_print();
+    drush_print(t('Options: '));
+
+    // TODO: (?) Add a hook for this, to allow other modules to add their options
+    $rows[] = array('-r <path>, --root=<path>', t('Drupal root directory to use (default: current directory)'));
+    $rows[] = array('-l <uri>, --uri=<uri>', t('URI of the drupal site to use (only needed in multisite environments)'));
+    $rows[] = array('-u <user>, --user=<user>', t('Drupal user name (or numeric ID) to execute actions under.'));
+    $rows[] = array('-v, --verbose', t('Display all available output'));
+    $rows[] = array('-y, --yes', t("Assume 'yes' as answer to all prompts"));
+    $rows[] = array('-s, --simulate', t("Simulate all relevant actions (don't actually change the system)"));
+
+    drush_print_table($rows, 2);
+    drush_print();
+    drush_print('Commands: ');
+
+    $commands = drush_get_commands();
+    $rows = array();
+    foreach ($commands as $key => $command) {
+      $rows[] = array($key, $commands[$key]['description']);
+    }
+    drush_print_table($rows, 2);
+  }
+  // Print command specific help.
+  else {
+    $commandstring = implode(" ", $commands);
+
+    if (!drush_is_command($commandstring)) {
+      return drush_error(t('Invalid command !command.', array('!command' => implode(" ", $commands))));
+    }
+
+    $help = module_invoke_all('help', 'drush:'. $commandstring);
+    if (!empty($help)) {
+      drush_print(implode("\n", $help));
+    }
+    else {
+      drush_print(t("No help available for command 'drush $commandstring'."));
+    }
+  }
+}
+
+/**
+ * This is called if no command or an unknown command is entered.
+ */
+function drush_usage() {
+  $commands = func_get_args();
+  if (drush_get_option('version')) {
+    return drush_print('drush ' . VERSION);
+  }
+
+  if (drush_get_option('help') || empty($commands)) {
+    return drush_callback_help();
+  }
+
+  return drush_error(t('Invalid command !command.', array('!command' => implode(" ", $commands))));
+}
\ No newline at end of file
Index: scripts/drush.bat
===================================================================
RCS file: scripts/drush.bat
diff -N scripts/drush.bat
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ scripts/drush.bat	13 Jun 2007 13:35:42 -0000
@@ -0,0 +1 @@
+@php.exe drush.php %1 %2 %3 %4 %5 %6 %7 %8 %9
Index: scripts/drush.php
===================================================================
RCS file: scripts/drush.php
diff -N scripts/drush.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ scripts/drush.php	13 Jun 2007 13:35:42 -0000
@@ -0,0 +1,252 @@
+#!/usr/bin/php
+<?php
+
+// $Id: drush.php,v 1.9 2007/06/13 11:21:14 frando Exp $
+
+/**
+ * @file
+ * drush is a PHP script implementing a command line shell for Drupal.
+ *
+ * @requires PHP CLI 4.3.0, PHP CLI 5.x, or newer.
+ */
+
+define('DRUSH_DRUPAL_BOOTSTRAP', 'includes/bootstrap.inc');
+
+// Terminate immediately unless invoked as a command line script
+if (!empty($_SERVER['REQUEST_METHOD'])) {
+  die();
+}
+
+exit(drush_bootstrap($GLOBALS['argc'], $GLOBALS['argv']));
+
+
+function drush_bootstrap($argc, $argv) {
+  global $args;
+  // Parse command line options and arguments.
+  $args = drush_parse_args($argv, array('h', 'u', 'r', 'l'));
+
+  // Define basic options as constants.
+  define('DRUSH_URI',         drush_get_option(array('l', 'uri'), FALSE));
+  define('DRUSH_VERBOSE',     drush_get_option(array('v', 'verbose'), FALSE));
+  define('DRUSH_AFFIRMATIVE', drush_get_option(array('y', 'yes'), FALSE));
+  define('DRUSH_SIMULATE',    drush_get_option(array('s', 'simulate'), FALSE));
+
+  // TODO: Make use of this as soon as the external
+  define('DRUSH_USER',        drush_get_option(array('u', 'user'), 0));
+
+  // If no root is defined, we try to guess from the current directory.
+  define('DRUSH_DRUPAL_ROOT',  drush_get_option(array('r', 'root'), _drush_locate_root()));
+
+  // If the Drupal directory can't be found, and no -r option was specified,
+  // or the path specified in -r does not point to a Drupal directory,
+  // we have no alternative but to give up the ghost at this point.
+  // (NOTE: t() is not available yet.)
+  if (!DRUSH_DRUPAL_ROOT || !is_dir(DRUSH_DRUPAL_ROOT) || !file_exists(DRUSH_DRUPAL_ROOT . '/' . DRUSH_DRUPAL_BOOTSTRAP))  {
+    exit("E: Could not locate the Drupal installation directory. Aborting.\n");
+  }
+
+  // Fake the necessary HTTP headers that Drupal needs:
+  $drupal_base_url = parse_url(DRUSH_URI);
+  $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
+  $_SERVER['PHP_SELF'] = $drupal_base_url['path'].'/index.php';
+  $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
+  $_SERVER['REMOTE_ADDR'] = NULL;
+  $_SERVER['REQUEST_METHOD'] = NULL;
+
+  // Change to Drupal root dir.
+  chdir(DRUSH_DRUPAL_ROOT);
+  // Bootstrap Drupal.
+  _drush_bootstrap_drupal();
+
+  // Login the specified user (if given).
+  if (DRUSH_USER) {
+    _drush_login(DRUSH_USER);
+  }
+
+  // Now we can use all of Drupal.
+
+  if (DRUSH_SIMULATE) {
+    drush_print(t('SIMULATION MODE IS ENABLED. NO ACTUAL ACTION WILL BE TAKEN. SYSTEM WILL REMAIN UNCHANGED.'));
+  }
+
+  // Dispatch the command.
+  $output = drush_dispatch($args['commands']);
+
+  // prevent a '1' at the end of the outputs
+  if ($output === true) {
+    $output = '';
+  }
+
+  // TODO: Terminate with the correct exit status.
+  return $output;
+}
+
+
+/**
+ * Exhaustive depth-first search to try and locate the Drupal root directory.
+ * This makes it possible to run drush from a subdirectory of the drupal root.
+ */
+function _drush_locate_root() {
+
+  $path = getcwd();
+  // Convert windows paths.
+  $path = drush_convert_path($path);
+  // Check the current path.
+  if (file_exists($path . '/' . DRUSH_DRUPAL_BOOTSTRAP)) {
+    return $path;
+  }
+  // Move up dir by dir and check each.
+  while ($path = _drush_locate_root_moveup($path)) {
+    if (file_exists($path . '/' . DRUSH_DRUPAL_BOOTSTRAP)) {
+      return $path;
+    }
+  }
+
+  return FALSE;
+}
+
+function _drush_locate_root_moveup($path) {
+  if (empty($path)) {
+    return FALSE;
+  }
+  $path = explode('/', $path);
+  // Move one directory up.
+  array_pop($path);
+  return implode($path, '/');
+}
+
+/**
+ * Bootstrap Drupal.
+ */
+function _drush_bootstrap_drupal() {
+  require_once DRUSH_DRUPAL_BOOTSTRAP;
+  require_once 'includes/drush.inc';
+
+  if (($conf_path = conf_path()) && !file_exists("./$conf_path/settings.php")) {
+    drush_die("Unable to load Drupal configuration from $conf_path/.");
+  }
+
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+
+  return TRUE;
+}
+
+/**
+ * Log a certain user in.
+ */
+function _drush_login($drush_user) {
+  global $user;
+  $user = user_load(is_numeric($drush_user) ? array('uid' => $drush_user) : array('name' => $drush_user));
+
+  if (empty($user)) {
+    if (is_numeric($drush_user)) {
+      drush_die(t('Could not login with user ID #%user.', array('%user' => $drush_user)));
+    }
+    else {
+      drush_die(t('Could not login with user account `%user\'.', array('%user' => $drush_user)));
+    }
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * Parse console arguments.
+ *
+ * @param $args
+ *   The console argument array (usually $argv)
+ * @param $arg_opts
+ *   An array of options that are followed by an argument.
+ *   e.g. shell.php -u admin -v --> $arg_opts = array('u')
+ * @param $default_options
+ * @return
+ *   A associative array:
+ *   $return['commands'] ia a numeric array of all commands,
+ *   $return['options'] contains the options. The option keys
+ *   are always noted without - or -- and are set to TRUE if they were
+ *   invoked, to the argument if followed by an argument, and if not present
+ *   to their default value or FALSE if no default value was specified.
+ */
+function drush_parse_args($args = array(), $arg_opts = array(), $default_options = array()) {
+  $options = $default_options;
+  $commands = array();
+
+  for ($i = 1; $i < count($args); $i++) {
+    $opt = $args[$i];
+    // Is the arg an option (starting with '-')?
+    if ($opt{0} == "-" && strlen($opt) != 1) {
+      // 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))) {
+          if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
+            exit("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 {
+      $commands[] = $opt;
+    }
+  }
+  return array('options' => $options, 'commands' => $commands);
+}
+
+/**
+ * Get the value for an option.
+ *
+ * If the first argument is an array, then it checks wether one of the options
+ * exists and return the value of the first one found. Useful for allowing both
+ * -h and --host-name
+ *
+ */
+function drush_get_option($option, $default = NULL) {
+  $options = $GLOBALS['args']['options'];
+  if (is_array($option)) {
+    foreach ($option as $current) {
+      if (array_key_exists($current, $options)) {
+        return $options[$current];
+      }
+    }
+    return $default;
+  }
+
+  if (!array_key_exists($option, $options)) {
+    return $default;
+  }
+  else {
+    return $options[$option];
+  }
+}
+
+/**
+ * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3).
+ */
+function drush_convert_path($path) {
+  return str_replace('\\','/', $path);
+}
+
+?>
