? includes/table.inc
Index: commands/core/core.drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/core.drush.inc,v
retrieving revision 1.92
diff -u -p -r1.92 core.drush.inc
--- commands/core/core.drush.inc	13 Apr 2010 20:02:04 -0000	1.92
+++ commands/core/core.drush.inc	14 Apr 2010 18:55:43 -0000
@@ -159,13 +159,14 @@ function core_drush_command() {
   $items['core-directory'] = array(
     'description' => dt('Return path to a given module/theme directory. See --help for more details.'),
     'arguments' => array(
-      'target' => 'A module/theme name, or special names like root, files, private. Defaults to root.',
+      'target' => 'A module/theme name, or special names like root, files, private, or an alias : path alias stirng such as @alias:%files. Defaults to root.',
     ),
     'examples' => array(
       'cd `drush cd devel`' => 'Navigate into the devel module directory',
       'cd `drush cd` ' => 'Navigate to the root of your Drupal site',
       'cd `drush cd files`' => 'Navigate to the files directory.',
-      'edit `drush cd devel`' => "Open devel directory in your editor (customize 'edit' for your editor)",
+      'drush cd @alias:%files' => 'Print the path to the files directory on the site @alias.',
+      'edit `drush cd devel`/devel.module' => "Open devel module in your editor (customize 'edit' for your editor)",
       'cdd devel' => 'Navigate to devel directory. See required function above,',
     ),
     'aliases' => array('cd'),
@@ -323,8 +324,8 @@ function core_drush_help($section) {
 
 
 function cdd() {
-  DEST=`drush $2 core-directory $1`
-  if [ -z "\$DEST" ]
+  DEST=`drush core-directory $1`
+  if [ $? != 0 ] || [ -z "\$DEST" ]
   then
     echo $1 "was not found."
   else
@@ -358,7 +359,7 @@ function _core_site_credentials() {
 function _core_site_credential_table($status_table) {
   $credentials = '';
   foreach ($status_table as $key => $value) {
-      $credentials .= sprintf("  %-18s: %s\n", $key, $value);
+    $credentials .= sprintf("  %-18s: %s\n", $key, $value);
   }
   return $credentials;
 }
@@ -366,22 +367,53 @@ function _core_site_credential_table($st
 function _core_site_credential_list($status_table) {
   $credentials = '';
   foreach ($status_table as $key => $value) {
-    $credentials .= sprintf("%s=%s\n", strtolower(str_replace(' ', '_', $key)), $value);
+    if (isset($value)) {
+      $credentials .= sprintf("%s=%s\n", strtolower(str_replace(' ', '_', $key)), $value);
+    }
   }
   return $credentials;
 }
 
-function _core_site_status_table() {
+function _core_path_aliases($project = '') {
+  $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
+  if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
+    $paths['%root'] = $drupal_root;
+    if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
+      $paths['%site'] = $site_root;
+      $paths['%modules'] = $site_root . '/sites/all/modules';
+      $paths['%themes'] = $site_root . '/sites/all/themes';
+      if (drush_drupal_major_version() >= 7) {
+        $paths['%files'] = DrupalPublicStreamWrapper::getDirectoryPath();
+        $paths['%private'] = DrupalPrivateStreamWrapper::getDirectoryPath();
+      }
+      elseif (function_exists('file_directory_path')) {
+        $paths['%files'] = file_directory_path();
+      }
+      // If the 'project' parameter was specified, then search
+      // for a project (or a few) and add its path to the path list
+      if (!empty($project)) {
+        foreach(explode(',', $project) as $target) {
+          $path = drush_core_find_project_path($target);
+          if(isset($path)) {
+            $paths['%' . $target] = $path;
+          }
+        }
+      }
+    }
+  }
+
+  // Add in all of the global paths from $options['path-aliases']
+  $paths = array_merge($paths, drush_get_option('path-aliases', array()));
+  
+  return $paths;
+}
+
+function _core_site_status_table($project = '') {
   $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
   if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
-    $status_table['Drupal Root'] = $drupal_root;
     $status_table['Drupal Version'] = drush_drupal_version();
     if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
-      $status_table['Site Path'] = $site_root;
       $status_table['Site URI'] = drush_get_context('DRUSH_URI');
-      if (function_exists('file_directory_path')) {
-        $status_table['File Directory Path'] = file_directory_path();
-      }
       if ($creds = drush_get_context('DRUSH_DB_CREDENTIALS')) {
         $status_table['Database Driver'] = $creds['driver'];
         $status_table['Database Hostname'] = $creds['host'];
@@ -413,6 +445,35 @@ function _core_site_status_table() {
   }
   $status_table['Drush Version'] = DRUSH_VERSION;
   $status_table['Drush configuration'] = implode(' ', drush_get_context_options('context-path', TRUE));
+  
+  // None of the Status keys are in dt(); this helps with machine-parsing of status?
+  $path_names['root'] = 'Drupal Root';
+  $path_names['site'] = 'Site Path';
+  $path_names['modules'] = 'Modules Path';
+  $path_names['themes'] = 'Themes Path';
+  $path_names['files'] = 'File Directory Path';
+  $path_names['private'] = 'Private File Directory Path';
+  
+  $paths = _core_path_aliases($project);
+  if (!empty($paths)) {
+    $status_table['Paths'] = NULL;
+    foreach ($paths as $target => $one_path) {
+      $name = $target;
+      if (substr($name,0,1) == '%') {
+        $name = substr($name,1);
+      }
+      if (array_key_exists($name, $path_names)) {
+        $name = $path_names[$name];
+      }
+      $status_table[$name] = $one_path;
+    }
+  }
+    
+  // Store the paths into the '%paths' index; this will be
+  // used by other code, but will not be included in the output
+  // of the drush status command.
+  $status_table['%paths'] = $paths;
+  
   return $status_table;
 }
 
@@ -442,7 +503,7 @@ function drush_core_cron() {
  */
 function drush_core_status() {
   drush_bootstrap_max();
-  $status_table = _core_site_status_table();
+  $status_table = _core_site_status_table(drush_get_option('project',''));
   // If args are specified, filter out any entry that is not named
   // (in other words, only show lines named by one of the arg values)
   $args = func_get_args();
@@ -454,6 +515,7 @@ function drush_core_status() {
     }
   }
   drush_backend_set_result($status_table);
+  unset($status_table['%paths']);
   // Print either an ini-format list or a formatted ASCII table
   if (drush_get_option('pipe')) {
     if (count($status_table) == 1) {
@@ -562,30 +624,45 @@ function drush_core_updatedb_batch_proce
   _update_batch_command($id);
 }
 
-function drush_core_directory($target = 'root') {
+function _drush_core_directory($target = 'root') {
   // Normalize to a sitealias in the target.
-  if (substr($target, 0, 1) != '/' && substr($target, 0, 1) != '@') {
+  $normalized_target = $target;
+  if (strpos($target, ':') === FALSE) {
     // @self makes no sense before 'site' level.
-    if (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) {
-      $additional_options = array();
-      $path = drush_sitealias_evaluate_path('@self:%' . $target, $additional_options);
+    if(!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) {
+      return FALSE;
     }
-    else {
-      return drush_set_error(dt('!target not found.', array('!target' => $target)));
+    $normalized_target = '@self:';
+    if (substr($target,0,1) != '%') {
+      $normalized_target .= '%';
     }
+    $normalized_target .= $target;
   }
 
-  if (isset($path['path-aliases']['%' . $target])) {
-    // Hurray, we found the destination quickly.
-    drush_print($path['rsync-path']);
-    return;
+  $additional_options = array();
+  $values = drush_sitealias_evaluate_path($normalized_target, $additional_options);
+
+  if (isset($values['path'])) {
+    // Hurray, we found the destination
+    return $values['path'];
   }
+  return NULL;
+}
+
+function drush_core_directory($target = 'root') {
+  $path = _drush_core_directory($target);
 
-  // We need to know which site is active in order to add its masks.
-  if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) {
-    return drush_set_error(dt('!target not found.', array('!target' => $target)));
+  if (isset($path)) {
+    drush_print($path);
   }
+  else {
+    return drush_set_error(dt('Core directory path !target not found.', array('!target' => $target)));
+  }
+   
+  return TRUE;
+}
 
+function drush_core_find_project_path($target) {
   $theme_suffix = drush_drupal_major_version() >= 6 ? '.info' : '/style.css';
   $masks = array(
     conf_path() . '/modules' => "/$target.module/",
@@ -600,8 +677,9 @@ function drush_core_directory($target = 
     if ($files = drush_scan_directory("$key", $mask, array('..', '.', 'CVS', '.svn', '.bzr'), 0, TRUE, 'name')) {
       // Just use the first match.
       $file = reset($files);
-      drush_print(drush_get_context('DRUSH_DRUPAL_ROOT') . '/' . dirname($file->filename));
-      return;
+      return drush_get_context('DRUSH_DRUPAL_ROOT') . '/' . dirname($file->filename);
     }
   }
+  
+  return NULL;
 }
Index: commands/core/rsync.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/rsync.inc,v
retrieving revision 1.15
diff -u -p -r1.15 rsync.inc
--- commands/core/rsync.inc	13 Apr 2010 05:11:39 -0000	1.15
+++ commands/core/rsync.inc	14 Apr 2010 18:55:44 -0000
@@ -21,13 +21,21 @@ function drush_core_rsync($source, $dest
   // After preflight, evaluate file paths
   $source_settings = drush_sitealias_evaluate_path($source, $additional_options);
   $destination_settings = drush_sitealias_evaluate_path($destination, $additional_options);
-  $source_path = $source_settings['rsync-path'];
-  $destination_path = $destination_settings['rsync-path'];
+  $source_path = $source_settings['evaluated-path'];
+  $destination_path = $destination_settings['evaluated-path'];
 
   // Check to see if this is an rsync multiple command (multiple sources and multiple destinations)
   $is_multiple = drush_do_multiple_command('rsync', $source_settings, $destination_settings, TRUE);
 
   if ($is_multiple === FALSE) {
+    // If the user path is the same for the source and the destination, then
+    // always add a slash to the end of the source.  If the user path is not
+    // the same in the source and the destinaiton, then you need to know how
+    // rsync paths work, and put on the trailing '/' if you want it.
+    if ($source_settings['user-path'] == $destination_settings['user-path']) {
+      $source_path .= '/';
+    }
+    
     // Prompt for confirmation. This is destructive.
     if (!drush_get_context('DRUSH_SIMULATE')) {
       drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path)));
@@ -91,7 +99,7 @@ function drush_core_call_rsync($source, 
     $options .= ' --include="sites/all" --exclude="sites/*"';
   }
   if (_drush_rsync_option_exists('mode', $additional_options)) {
-    $mode = "-" . _drush_rsync_get_option($additional_options, 'mode');
+    $mode = "-" . drush_get_option_override($additional_options, 'mode');
   }
   if (drush_get_context('DRUSH_VERBOSE')) {
     // the drush_op() will be verbose about the command that gets executed.
@@ -190,7 +198,7 @@ function drush_core_call_rsync($source, 
     'protocol',
     );
   foreach ($rsync_available_options as $test_option) {
-    $value = _drush_rsync_get_option($additional_options, $test_option);
+    $value = drush_get_option_override($additional_options, $test_option);
     if (isset($value)) {
       if ($value === TRUE) {
         $options .= " --$test_option";
@@ -201,7 +209,7 @@ function drush_core_call_rsync($source, 
     }
   }
 
-  $ssh_options = _drush_rsync_get_option($additional_options, 'ssh-options', '');
+  $ssh_options = drush_get_option_override($additional_options, 'ssh-options', '');
   $exec = "rsync -e 'ssh $ssh_options' $mode$options $source $destination";
 
   $exec_result = drush_op('system', $exec) !== FALSE;
Index: includes/context.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/context.inc,v
retrieving revision 1.27
diff -u -p -r1.27 context.inc
--- includes/context.inc	8 Apr 2010 19:17:30 -0000	1.27
+++ includes/context.inc	14 Apr 2010 18:55:44 -0000
@@ -383,6 +383,31 @@ function drush_get_option($option, $defa
 }
 
 /**
+ * Get the value for an option, but first checks the provided option overrides.
+ *
+ * The feature of drush_get_option that allows a list of option names
+ * to be passed in an array is NOT supported.
+ *
+ * @param option_overrides
+ *   An array to check for values before calling drush_get_option.
+ * @param option
+ *   The name of the option to get.
+ * @param default
+ *   Optional. The value to return if the option has not been set.
+ * @param context
+ *   Optional. The context to check for the option. If this is set, only this context will be searched.
+ * 
+ */
+function drush_get_option_override($option_overrides, $option, $value = NULL, $context = NULL) {
+  if (array_key_exists($option, $option_overrides)) {
+    return $option_overrides[$option];
+  }
+  else {
+    return drush_get_option($option, $value, $context);
+  }
+}
+
+/**
  * Get all of the values for an option in every context.
  *
  * @param option
Index: includes/drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/drush.inc,v
retrieving revision 1.107
diff -u -p -r1.107 drush.inc
--- includes/drush.inc	12 Apr 2010 14:29:54 -0000	1.107
+++ includes/drush.inc	14 Apr 2010 18:55:45 -0000
@@ -989,7 +989,13 @@ function drush_print_table($rows, $heade
 function drush_key_value_to_array_table($keyvalue_table) {
   $table = array();
   foreach ($keyvalue_table as $key => $value) {
-    $table[] = array($key, ' :', $value);
+    if (isset($value)) {
+      $table[] = array($key, ' :', $value);
+    }
+    else {
+      $table[] = array('', '', '');
+      $table[] = array($key . ':', '', '');
+    }
   }
   return $table;
 }
Index: includes/sitealias.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/sitealias.inc,v
retrieving revision 1.39
diff -u -p -r1.39 sitealias.inc
--- includes/sitealias.inc	13 Apr 2010 05:11:39 -0000	1.39
+++ includes/sitealias.inc	14 Apr 2010 18:55:46 -0000
@@ -568,6 +568,24 @@ function drush_sitealias_add_db_settings
 }
 
 /**
+ * Check to see if we have already bootstrapped to a site.
+ */
+function drush_sitealias_is_bootstrapped_site($alias_record) {
+  if (!isset($alias_record['remote-host'])) {
+    $self_record = drush_sitealias_get_record("@self");
+    if (empty($self_record)) {
+      // TODO:  If we have not bootstrapped to a site yet, we could
+      // perhaps bootstrap to $alias_record here.
+      return FALSE;
+    }
+    elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) {
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+/**
  * Walk through the path aliases and find any paths that begin with a '@'.
  * If found, query the 'status' function for the local or remote
  * site and use the result returned to populate the value of the
@@ -587,6 +605,18 @@ function drush_sitealias_add_db_settings
  */
 function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') {
   $resolution_needed = FALSE;
+  
+  // Convert the test string into an array of items, and
+  // from this make a comma-separated list of projects
+  // that we can pass to 'drush status'.
+  $test_array = explode('/', $test_string);
+  $project_array = array();
+  foreach($test_array as $one_item) {
+    if (substr($one_item,0,1) == '%') {
+      $project_array[] = substr($one_item,1);
+    }    
+  }
+  
   // Check to see if we have any path values that begin with '@'
   // that also exist in the test string
   if (array_key_exists('path-aliases', $alias_record)) {
@@ -596,19 +626,48 @@ function drush_sitealias_resolve_path_re
           $resolution_needed = TRUE;
         }
       }
+      // If we already have a path in the path aliases, then
+      // there is no need to search for it remotely; we can remove
+      // it from the project array.
+      if (substr($key,0,1) == '%') {
+        unset($project_array['%' . substr($key,1)]);
+      }
     }
   }
-
-  if ($resolution_needed) {
-    $values = drush_do_site_command($alias_record, "status");
-    $status_values = $values['object'];
-    if (isset($status_values)) {
-      foreach ($alias_record['path-aliases'] as $key => $value) {
-        if (substr($value,0,1) == '@') {
-          $status_key = strtr(substr($value,1), '-_', '  ');
+  $project_list = implode(',', $project_array);
+  
+  if ($resolution_needed || !empty($project_array)) {
+    // Optimization:  if we're already bootstrapped to the
+    // site specified by $alias_record, then we can just
+    // call _core_site_status_table() rather than use backend invoke.
+    if (drush_sitealias_is_bootstrapped_site($alias_record)) {
+      // Make sure that we are bootstrapped at least to the 'site'
+      // level, and include file.inc to insure that we have access
+      // to the %file path.
+      if (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) {
+        include_once DRUPAL_ROOT . '/includes/file.inc';
+      }
+      $status_values = _core_site_status_table($project_list);
+    }
+    else {
+      $values = drush_do_site_command($alias_record, "status", array(), empty($project_list) ? array() : array('project' => $project_list));
+      $status_values = $values['object'];
+    }
+    if (isset($status_values['%paths'])) {
+      foreach ($status_values['%paths'] as $key => $path) {
+        $alias_record['path-aliases'][$key] = $path;
+      }
+    }
+    // TODO:  Remove this old code, which is only useful when talking to old (remote) versions of drush-3.0-rc3 and earlier
+    if (isset($status_values['paths'])) {
+      foreach ($alias_record['path-aliases'] as $key => $path_alias_key) {
+        if (substr($path_alias_key,0,1) == '@') {
+          $status_key = strtr(substr($path_alias_key,1), '-_', '  ');
           if (array_key_exists($status_key, $status_values)) {
             $alias_record['path-aliases'][$key] = $status_values[$status_key];
-            drush_log(dt('Resolved path !key to !value', array('!key' => $status_key, '!value' => $status_values[$status_key])));
+          }
+          elseif (array_key_exists('%paths',$status_values) && array_key_exists($path_alias_key, $status_values['%paths'])) {
+            $alias_record['path-aliases'][$key] = $status_values['%paths'][$path_alias_key];
           }
           else {
             unset($alias_record['path-aliases'][$key]);
@@ -1252,7 +1311,7 @@ function drush_sitealias_set_alias_conte
  * @return
  *   The site record for the machine specified in the path, if any,
  *   with the path to pass to rsync (including the machine specifier)
- *   in the 'rsync-path' item.
+ *   in the 'evaluated-path' item.
  */
 function drush_sitealias_evaluate_path($path, &$additional_options) {
   $site_alias_settings = array();
@@ -1278,7 +1337,9 @@ function drush_sitealias_evaluate_path($
       $path = '';
     }
   }
-
+  // Record the path minus the machine / alias part in $user_path
+  $user_path = $path;
+  
   if (!empty($site_alias_settings)) {
     // Apply any options from this alias that might affect our rsync
     drush_sitealias_set_alias_context($site_alias_settings);
@@ -1307,15 +1368,15 @@ function drush_sitealias_evaluate_path($
 
   // If the --exclude-other-sites option is specified, then
   // convert that into --include-path='%site' and --exclude-sites.
-  if (_drush_rsync_get_option($additional_options, 'exclude-other-sites', FALSE) && !_drush_rsync_get_option($additional_options, 'exclude-other-sites-processed', FALSE, 'process')) {
-    $additional_options['include-path'] = '%site,' . _drush_rsync_get_option($additional_options, 'include-path', '');
+  if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_option_override($additional_options, 'exclude-other-sites-processed', FALSE, 'process')) {
+    $additional_options['include-path'] = '%site,' . drush_get_option_override($additional_options, 'include-path', '');
     $additional_options['exclude-sites'] = TRUE;
     $additional_options['exclude-other-sites-processed'] = TRUE;
   }
   // If the --exclude-files option is specified, then
   // convert that into --exclude-path='%files'.
-  if (_drush_rsync_get_option($additional_options, 'exclude-files', FALSE) && !_drush_rsync_get_option($additional_options, 'exclude-files-processed', FALSE, 'process')) {
-    $additional_options['exclude-path'] = '%files,' . _drush_rsync_get_option($additional_options, 'exclude-path', '');
+  if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) {
+    $additional_options['exclude-path'] = '%files,' . drush_get_option_override($additional_options, 'exclude-path', '');
     $additional_options['exclude-files-processed'] = TRUE;
   }
 
@@ -1335,8 +1396,8 @@ function drush_sitealias_evaluate_path($
   // in it, so that resolution is only done if the path alias is referenced.
   // Therefore, we can concatenate without worrying too much about the structure of
   // this variable's contents.
-  $include_path = _drush_rsync_get_option($additional_options, 'include-path', '');
-  $exclude_path = _drush_rsync_get_option($additional_options, 'exclude-path', '');
+  $include_path = drush_get_option_override($additional_options, 'include-path', '');
+  $exclude_path = drush_get_option_override($additional_options, 'exclude-path', '');
   $resolve_path = $path . $include_path . $exclude_path;
   // Resolve path aliases such as %files, if any exist in the path
   if (!empty($resolve_path)) {
@@ -1369,14 +1430,9 @@ function drush_sitealias_evaluate_path($
     if (($value[0] != '/') && ($key != '%root')) {
       $full_path_aliases[$key] = $drupal_root . $value;
     }
-    // Rsync is very particular about the meaning of paths that
-    // end with a '/' compared to those that do not.  In order to
-    // copy from !path to !path without creating an extra spurrious
-    // directory, the path alias must end with a '/'.  Therefore
-    // we will add a slash to the end of any alias that does not
-    // already have one.
-    if ($value[strlen($value)-1] != '/') {
-      $full_path_aliases[$key] = $full_path_aliases[$key] . '/';
+    // We do not want slashes on the end of our path aliases.
+    if ($value[strlen($value)-1] == '/') {
+      $full_path_aliases[$key] = substr($full_path_aliases[$key], -1);
     }
   }
 
@@ -1402,22 +1458,28 @@ function drush_sitealias_evaluate_path($
 
   // If there is a $machine component, to the path, then
   // add it to the beginning
+  $evaluated_path = $path;
   if (!empty($machine)) {
-    $path = $machine . ':' . $path;
+    $evaluated_path = $machine . ':' . $path;
   }
 
-  // TODO: Rename this key.
-  $site_alias_settings['rsync-path'] = $path;
+  //
+  // Add our result paths:
+  //
+  //	evaluated-path:		machine:/path
+  //	server-component:	machine
+  //	path-component:		:/path
+  //	path:			/path
+  //	user-path:		path (as specified in input parameter)
+  //
+  $site_alias_settings['evaluated-path'] = $evaluated_path;
+  if (!empty($machine)) {
+    $site_alias_settings['server-component'] = $machine;
+  }
   $site_alias_settings['path-component'] = $path_component;
+  $site_alias_settings['path'] = $path;
+  $site_alias_settings['user-path'] = $user_path;
 
   return $site_alias_settings;
 }
 
-function _drush_rsync_get_option($additional_options, $option, $value = NULL, $context = NULL) {
-  if (array_key_exists($option, $additional_options)) {
-    return $additional_options[$option];
-  }
-  else {
-    return drush_get_option($option, $value, $context);
-  }
-}
