Index: drush_pm/drush_pm.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/drush_pm/drush_pm.module,v
retrieving revision 1.25.2.1
diff -u -p -r1.25.2.1 drush_pm.module
--- drush_pm/drush_pm.module	5 Aug 2008 16:58:15 -0000	1.25.2.1
+++ drush_pm/drush_pm.module	1 Oct 2008 00:08:55 -0000
@@ -4,9 +4,26 @@
 /**
  * @file
  *  The drush Package Manager
+ * 
+ * Terminology:
+ * - Request: a requested package (string or keyed array), with a project name and (optionally) version. 
+ * - Project: a drupal.org project, such as cck or drush.
+ * - Version: a requested version, such as 1.0 or 1.x-dev.
+ * - Release: a specific release of a project, with associated metadata (from the drupal.org update service).
+ * - Package: the collection of files that make up a release.
  */
 
 /**
+ * Project is a user requested version update.
+ */
+define('DRUSH_PM_REQUESTED_UPDATE', 101);
+
+/**
+ * User requested version already installed.
+ */
+define('DRUSH_PM_REQUESTED_CURRENT', 102);
+
+/**
  * Implementation of hook_help().
  */
 function drush_pm_help($section) {
@@ -54,7 +71,7 @@ View all releases for a given project. U
  */
 function drush_pm_drush_command() {
   $items['pm install'] = array(
-    'callback' => 'drush_pm_install_package',
+    'callback' => 'drush_pm_install',
     'description' => 'Install one or more modules'
   );
   $items['pm refresh'] = array(
@@ -89,45 +106,45 @@ function drush_pm_requirements($phase) {
 /**
  * Parse out the project name and version and return as a structured array
  *
- * @param $projects an array of project names
+ * @param $requests an array of project names
  */
-function drush_pm_parse_package_name($projects) {
-  $projectdata = array();
-  foreach($projects as $project) {
+function drush_pm_parse_project_version($requests) {
+  $requestdata = array();
+  foreach($requests as $request) {
     // project-HEAD or project-5.x-1.0-beta
     // '5.x-' is optional, as is '-beta'
-    preg_match('/-(HEAD|(\d+)\.([\dx]+)(-.+)?)$/', $project, $matches);
+    preg_match('/-(HEAD|(\d+)\.([\dx]+)(-.+)?)$/', $request, $matches);
     if ($matches[0]) {
       // Specific version requested
       $version = $matches[0];
-      $name = substr($project, 0, strlen($project) - strlen($version));
+      $project = substr($request, 0, strlen($request) - strlen($version));
     }
     else {
       // Recommended stable version requested
-      $name = $project;
+      $project = $request;
     }
-    if (empty($name)) {
+    if (empty($project)) {
       drush_die(t("Project name not found.\n\nRun drush help pm install for more information."));
     }
-    $projectdata[$name] = array(
-      'name' => $name,
+    $requestdata[$project] = array(
+      'name' => $project,
       'version' => trim($version, ' -'),
     );
   }
-  return $projectdata;
+  return $requestdata;
 }
 
 /**
  * Command callback. Installs one or more packages (so far only modules).
  */
-function drush_pm_install_package() {
-  $projects = func_get_args();
-  if (empty($projects)) {
+function drush_pm_install() {
+  $requests = func_get_args();
+  if (empty($requests)) {
     drush_die(t("No project specified.\n\nRun drush help pm install for more information."));
   }
   
   // Parse out project name and version
-  $projects = drush_pm_parse_package_name($projects);
+  $requests = drush_pm_parse_project_version($requests);
   
   // If a URI is provided then we install to that specific site, otherwise we install to sites/all/modules
   if (DRUSH_URI) {
@@ -140,8 +157,8 @@ function drush_pm_install_package() {
   }
 
   // Get the module info from drupal.org via xml-rpc
-  $info = drush_pm_get_project_info($projects);
-  if (!$info) {
+  $releases = drush_pm_get_project_info($requests);
+  if (!$releases) {
     drush_die(t("None of the given projects exists or has releases that are compatible with your Drupal version."));
   }
   
@@ -153,19 +170,19 @@ function drush_pm_install_package() {
   }
 
   // Download and install each module
-  foreach($projects as $project_name => $project) {
-    if (isset($info[$project_name]) && $release = drush_pm_get_release($project, $info[$project_name])) {
-      if (is_dir($modulepath . $project_name)) {
-        drush_error(t('Project !project is already installed. Skipping.', array('!project' => $project_name)));
+  foreach($requests as $project => $request) {
+    if (isset($releases[$project]) && $release = drush_pm_get_release($request, $releases[$project])) {
+      if (is_dir($modulepath . $project)) {
+        drush_error(t('Project !project is already installed. Skipping.', array('!project' => $project)));
       }
-      elseif ($package_handler($project_name, $release, $modulepath)) {
+      elseif ($package_handler($project, $release, $modulepath)) {
         drush_print(t("Project !project successfully installed (version !version).",
-          array('!project' => $project_name, '!version' => $release['version'])));
-        module_invoke_all('drush_pm_post_install', $project_name, $release, $modulepath);
+          array('!project' => $project, '!version' => $release['version'])));
+        module_invoke_all('drush_pm_post_install', $project, $release, $modulepath);
       }
     }
     else {
-      drush_error(t('Project !project doesn\' exist or has no releases that are compatible with your Drupal version. Skipping.', array('!project' => $project_name)));
+      drush_error(t('Project !project doesn\' exist or has no releases that are compatible with your Drupal version. Skipping.', array('!project' => $project)));
     }
   }
 
@@ -181,73 +198,121 @@ function drush_pm_install_package() {
  */
 function drush_pm_update() {  
   // Get update status information.
-  $data = _drush_pm_get_update_info();
+  $releases = _drush_pm_get_update_info();
+
+  // Get specific requests
+  $requests = func_get_args();
   
-  // Remove info for projects we don't care about (if specified)
-  if ($specified_projects = func_get_args()) {
-    foreach ($data as $key => $value) {
-      if (!in_array($key, $specified_projects)) {
-        unset($data[$key]);
+  // Parse out project name and version
+  $requests = drush_pm_parse_project_version($requests);
+
+  // Preprocess releases
+  if (!empty($requests)) {
+    // Force update projects where a specific version is reqested
+    foreach ($requests as $project => $request) {
+      if (!empty($request['version'])) {
+        // Match the requested release
+        $release = drush_pm_get_release($request, $releases[$project]);
+        if ($release['version'] == $releases[$project]['existing_version']) {
+          $releases[$project]['status'] = DRUSH_PM_REQUESTED_CURRENT;          
+        }
+        else {
+          $releases[$project]['status'] = DRUSH_PM_REQUESTED_UPDATE;
+        }
+        // Set the candidate version to the requested release
+        $releases[$project]['candidate_version'] = $release['version'];
       }
     }
   }
-  
-  $last = variable_get('update_last_check', 0);
-  drush_print(t('Update information last refreshed: ') . ($last  ? format_date($last) : t('Never')));
-  drush_print();
 
-  // table headers
+  // Table headers.
   $rows[] = array(t('Name'), t('Installed version'), t('Recommended version'), t('Status'));
 
-  foreach ($data as $project) {
-    if (!$project['title']) {
+  // Process releases, notifying user of status and building a list of proposed updates
+  $updateable = array();
+  foreach ($releases as $release) {
+    if (!$release['title']) {
       continue;
     }
-    
-    $project['candidate_version'] = $project['recommended'];
 
-    switch($project['status']) {
+    switch($release['status']) {
       case UPDATE_CURRENT:
         $status = t('OK');
+        $release['candidate_version'] = $release['recommended'];
         break;
       case UPDATE_NOT_CURRENT:
         $status = t('Update available');
-        $updateable[$project['name']] = $project;
+        $release['candidate_version'] = $release['recommended'];
+        $updateable[$release['name']] = $release;
         break;
       case UPDATE_NOT_SECURE:
         $status = t('SECURITY UPDATE available');
-        $updateable[$project['name']] = $project;
+        $release['candidate_version'] = $release['recommended'];
+        $updateable[$release['name']] = $release;
         break;
       case UPDATE_REVOKED:
         $status = t('Intalled version REVOKED');
+        $release['candidate_version'] = $release['recommended'];
         $updateable[$project['name']] = $project;
         break;
+      case DRUSH_PM_REQUESTED_UPDATE:
+        $status = t('Specified version available');
+        $updateable[$release['name']] = $release;
+        break;
+      case DRUSH_PM_REQUESTED_CURRENT:
+        $status = t('Specified version already installed');
+        break;
       default:
-        $status = t('ignored: !reason', array('!reason' => $project['reason']));
-        $project['title'] = $project['name'];
-        $project['candidate_version'] = t('unknown');
+        $status = t('Ignored: !reason', array('!reason' => $project['reason']));
+        $release['title'] = $release['name'];
+        $release['candidate_version'] = t('Unknown');
         break;
     }
 
-    $rows[] = array($project['title'], $project['existing_version'], $project['candidate_version'], $status);
+    $rows[] = array($release['title'], $release['existing_version'], $release['candidate_version'], $status);
   }
 
+  $last = variable_get('update_last_check', 0);
+  drush_print(t('Update information last refreshed: ') . ($last  ? format_date($last) : t('Never')));
+  drush_print();
   drush_print(t("Update status information on all installed and enabled Drupal modules:"));
   drush_print_table($rows, 2, TRUE);
   drush_print();
 
-  if (isset($updateable['drupal'])) {
-    drush_print("NOTE: An update for the Drupal core is available. \nDrupal itself can't yet be updated by this tool. Please update Drupal manually.\n");
-    unset($updateable['drupal']);
+  // If specific project updates were requested then remove releases for all others
+  if (!empty($requests)) {
+    foreach ($updateable as $project => $release) {
+      if (!isset($requests[$project])) {
+        unset($updateable[$project]);
+      }
+    }
   }
 
   if (empty($updateable)) {
     drush_die(t('No updates available.'));
   }
 
-  drush_print(t('Updates are available for the following projects:'));
-  foreach($updateable as $project) {
-    $print .= $project['title'] . " [" . $project['name'] . "], ";
+  // Offer to update to the identified releases
+  drush_pm_update_packages($updateable);
+}
+
+/**
+ * Update packages according to an array of releases, following interactive
+ * confirmation from the user.
+ * 
+ * @param $releases
+ *   An array of releases from the drupal.org update service, with an additional
+ *   array key candidate_version that specifies the version to be installed.
+ */
+function drush_pm_update_packages($releases) {
+  if (isset($releases['drupal'])) {
+    drush_print("NOTE: An update for the Drupal core is available. \nDrupal itself can't yet be updated by this tool. Please update Drupal manually.\n");
+    unset($releases['drupal']);
+  }
+
+  drush_print(t('Updates will be made to the following projects:'));
+  foreach($releases as $release) {
+    $print .= $release['title'] . " [" . $release['name'] . '-' . $release['candidate_version'] . "], ";
   }
   drush_print(substr($print, 0, strlen($print)-2));
   drush_print();
@@ -262,17 +327,17 @@ function drush_pm_update() {  
 
   $package_handler = drush_pm_get_package_handler() .'_update_project';
   if (!function_exists($package_handler)) {
-  	drush_die(t("The $package_handler package handler does not handle updates."));
+    drush_die(t("The $package_handler package handler does not handle updates."));
   }
   
   // Save the date to be used in the backup directory's path name.
   $date = date('YmdHis');
   
   // Now we start the actual updating.
-  foreach($updateable as $project) {
-    drush_verbose(t('Starting to update !project ...', array('!project' => $project['title'])));
+  foreach($releases as $release) {
+    drush_verbose(t('Starting to update !project ...', array('!project' => $release['title'])));
     
-    $source = DRUSH_DRUPAL_ROOT .'/' . $project['path'];
+    $source = DRUSH_DRUPAL_ROOT .'/' . $release['path'];
     
     $skip_backup = module_invoke_all('drush_pm_skip_backup', $source);
     if (!count($skip_backup)) {
@@ -282,31 +347,31 @@ function drush_pm_update() {  
       drush_op('mkdir', $backup_dir, 0777);
       $backup_dir .= "/$date";
       drush_op('mkdir', $backup_dir, 0777);
-      $backup_target = $backup_dir . '/'. $project['name'];
+      $backup_target = $backup_dir . '/'. $release['name'];
       if (!drush_op('rename', $source, $backup_target)) {
         drush_die(t('Failed to backup project directory !source to !backup_target', array('!source' => $source, '!backup_target' => $backup_target)));
       }
     }
-
+  
     // Install the new version.
     // $basepath is the dir where the current module is installed. It's one dir up from the
     // place of the project's info files.
-    $basepath = explode('/', $project['path']);
+    $basepath = explode('/', $release['path']);
     // move a directory up, so we can copy updated dir to parent
     array_pop($basepath);
     $project_parent_path = DRUSH_DRUPAL_ROOT. '/'. implode('/', $basepath). '/';
-    if (!$package_handler($project['name'], $project['releases'][$project['candidate_version']], $project_parent_path)) {
+    if (!$package_handler($release['name'], $release['releases'][$release['candidate_version']], $project_parent_path)) {
       if (!count($skip_backup)) {
-        drush_error(t('Updating project !project failed. Restoring previously installed version.', array('!project' => $project['name'])));
+        drush_error(t('Updating project !project failed. Restoring previously installed version.', array('!project' => $release['name'])));
         drush_op('rename', $backup_target, $source);
       }
       else {
-        drush_error(t('Updating project !project failed. Please revert to the previously installed version.', array('!project' => $project['name'])));
+        drush_error(t('Updating project !project failed. Please revert to the previously installed version.', array('!project' => $release['name'])));
       }
     }
     else {
-      drush_print(t('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
-      module_invoke_all('drush_pm_post_update', $project['name'], $project['releases'][$project['candidate_version']], $project_parent_path);
+      drush_print(t('Project !project was updated successfully. Installed version is now !version.', array('!project' => $release['name'], '!version' => $release['candidate_version'])));
+      module_invoke_all('drush_pm_post_update', $release['name'], $release['releases'][$release['candidate_version']], $project_parent_path);
     }
   }
   if ($backup_dir) {
