Index: libraries.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v
retrieving revision 1.6
diff -u -p -r1.6 libraries.api.php
--- libraries.api.php	27 Jan 2011 02:22:04 -0000	1.6
+++ libraries.api.php	8 Feb 2011 13:43:32 -0000
@@ -103,6 +103,41 @@
  *     names and whose values are sets of files to load for the module, using
  *     the same notion as the top-level 'files' property. Each specified file
  *     should contain the path to the file relative to the module it belongs to.
+ *   - callbacks: An associative array whose keys are callback groups and whose
+ *     values are arrays of callbacks to apply to the library in that group.
+ *     Each callback receives the following arguments:
+ *     - $library: An array of library information belonging to the top-level
+ *       library, a specific version, a specific variant or a specific variant
+ *       of a specific version. Because library information such as the 'files'
+ *       property (see above) can be declared in all these different locations
+ *       of the library array, but a callback may have to act on all these
+ *       different parts of the library, it is called recursively for each
+ *       library with a certain part of the libraries array passed as $library
+ *       each time.
+ *     - $version: If the $library array belongs to a certain version (see
+ *       above), a string containing the version. NULL, otherwise.
+ *     - $variant: If the $library array belongs to a certain variant (see
+ *       above), a string containing the variant name. NULL, otherwise.
+ *     Valid callback groups are:
+ *     - prepare: Callbacks registered in this group are applied as soon as the
+ *       library information has been retrieved via hook_libraries_info() or
+ *       info files.
+ *     - detect: Callbacks registered in this group are applied as soon as
+ *       library detection has been completed. At this point the library
+ *       contains the version-specific information, if specified, and following
+ *       additional information is available:
+ *       - $library['installed']: A boolean indicating whether the library is
+ *         installed or not.
+ *       - $library['version']: If it could be detected, a string containing the
+ *         version of the library.
+ *       - $library['variants'][$variant]['installed']: For each specified
+ *         variant, a boolean indicating whether the variant is installed or
+ *         not.
+ *       Note that in this group the 'versions' property is no longer available.
+ *     - load: Callbacks registered in this group are applied directly
+ *       before this library is loaded. At this point the library contains
+ *       variant-specific information, if specified. Note that in this group the
+ *       'variants' property is no longer available.
  *   Additional top-level properties can be registered as needed.
  *
  * @see hook_library()
@@ -214,6 +249,14 @@ function hook_libraries_info() {
         'js' => array('ex_lib.inc'),
       ),
     ),
+    // Optionally register callbacks to apply to the library during different
+    // stages of its lifetime ('callback groups'). Here, a callback is
+    // registered in the 'detect' group.
+    'callbacks' => array(
+      'detect' => array(
+        'mymodule_example_detect_callback',
+      ),
+    ),
   );
 
   // A very simple library. No changing APIs (hence, no versions), no variants.
Index: libraries.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v
retrieving revision 1.20
diff -u -p -r1.20 libraries.module
--- libraries.module	29 Jan 2011 18:41:15 -0000	1.20
+++ libraries.module	8 Feb 2011 13:43:32 -0000
@@ -145,6 +145,60 @@ function libraries_scan_info_files() {
 
   return $files;
 }
+
+/**
+ * Invokes library callbacks.
+ *
+ * @param $group
+ *   A string containing the group of callbacks that is to be applied. Should be
+ *   either 'prepare', 'detect', or 'load'.
+ * @param $library
+ *   An array of library information, passed by reference.
+ */
+function libraries_invoke($group, &$library) {
+  foreach ($library['callbacks'][$group] as $callback) {
+    libraries_traverse_library($library, $callback);
+  }
+}
+
+/**
+ * Helper function to apply a callback to all parts of a library.
+ *
+ * Because library declarations can include variants and versions, and those
+ * version declarations can in turn include variants, modifying e.g. the 'files'
+ * property everywhere it is declared can be quite cumbersome, in which case
+ * this helper function is useful.
+ *
+ * @param $library
+ *   An array of library information, passed by reference.
+ * @param $callback
+ *   A string containing the callback to apply to all parts of a library.
+ */
+function libraries_traverse_library(&$library, $callback) {
+  // Always apply the callback to the top-level library.
+  $callback($library, NULL, NULL);
+
+  // Apply the callback to versions.
+  if (isset($library['versions'])) {
+    foreach ($library['versions'] as $version_string => &$version) {
+      $callback($version, $version_string, NULL);
+      // Versions can include variants as well.
+      if (!empty($version['variants'])) {
+        foreach ($version['variants'] as $version_variant_name => &$version_variant) {
+          $callback($version_variant, $version_string, $version_variant_name);
+        }
+      }
+    }
+  }
+
+  // Apply the callback to variants.
+  if (isset($library['variants'])) {
+    foreach ($library['variants'] as $variant_name => &$variant) {
+      $callback($variant, NULL, $variant_name);
+    }
+  }
+}
+
 /**
  * Returns information about registered libraries.
  *
@@ -199,11 +253,21 @@ function libraries_info($name = NULL) {
         'variants' => array(),
         'versions' => array(),
         'integration files' => array(),
+        'callbacks' => array(
+          'prepare' => array(),
+          'detect' => array(),
+          'load' => array(),
+        ),
       );
     }
 
     // Allow modules to alter the registered libraries.
     drupal_alter('libraries_info', $libraries);
+
+    // Invoke callbacks in the 'prepare' group.
+    foreach ($libraries as &$properties) {
+      libraries_invoke('prepare', $properties);
+    }
   }
 
   if (isset($name)) {
@@ -333,6 +397,10 @@ function libraries_detect_library(&$libr
 
   // If we end up here, the library should be usable.
   $library['installed'] = TRUE;
+
+  // Invoke callbacks in the 'detect' group.
+  libraries_invoke('detect', $library);
+
   return $library;
 }
 
@@ -379,6 +447,9 @@ function libraries_load($name, $variant 
     // If the library (variant) is installed, load it.
     $library['loaded'] = FALSE;
     if ($library['installed']) {
+      // Invoke callbacks in the 'load' group.
+      libraries_invoke('load', $library);
+
       $library['loaded'] = libraries_load_files($library);
     }
     $loaded[$name] = $library;
Index: tests/libraries.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries.test,v
retrieving revision 1.12
diff -u -p -r1.12 libraries.test
--- tests/libraries.test	27 Jan 2011 02:22:04 -0000	1.12
+++ tests/libraries.test	8 Feb 2011 13:43:32 -0000
@@ -148,6 +148,73 @@ class LibrariesTestCase extends DrupalWe
     $this->verbose(var_export($library, TRUE));
     $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.');
 
+    // Test the applying of callbacks.
+    $expected = array_merge(libraries_info('example_empty'), array(
+      'machine name' => 'example_callback',
+      'name' => 'Example callback',
+      'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+      'version' => '1',
+      'versions' => array(
+        '1' => array(
+          'variants' => array(
+            'example_variant' => array(
+              'prepare callback' => 'not applied',
+              'detect callback' => 'not applied',
+              'load callback' => 'not applied',
+            ),
+          ),
+          'prepare callback' => 'not applied',
+          'detect callback' => 'not applied',
+          'load callback' => 'not applied',
+        ),
+      ),
+      'variants' => array(
+        'example_variant' => array(
+          'prepare callback' => 'not applied',
+          'detect callback' => 'not applied',
+          'load callback' => 'not applied',
+        ),
+      ),
+      'callbacks' => array(
+        'prepare' => array('_libraries_test_prepare_callback'),
+        'detect' => array('_libraries_test_detect_callback'),
+        'load' => array('_libraries_test_load_callback'),
+      ),
+      'prepare callback' => 'not applied',
+      'detect callback' => 'not applied',
+      'load callback' => 'not applied',
+    ));
+    // Test a callback in the 'prepare' phase.
+    $expected['prepare callback'] = 'applied (top-level)';
+    $expected['versions']['1']['prepare callback'] = 'applied (version 1)';
+    $expected['versions']['1']['variants']['example_variant']['prepare callback'] = 'applied (version 1, variant example_variant)';
+    $expected['variants']['example_variant']['prepare callback'] = 'applied (variant example_variant)';
+    $library = libraries_info('example_callback');
+    $this->verbose(var_export($expected, TRUE));
+    $this->verbose(var_export($library, TRUE));
+    $this->assertEqual($library, $expected, 'Prepare callback was applied correctly.');
+    // Test a callback in the 'detect' phase.
+    unset($expected['versions']);
+    $expected['installed'] = TRUE;
+    $expected['prepare callback'] = 'applied (version 1)';
+    $expected['variants']['example_variant']['installed'] = TRUE;
+    $expected['variants']['example_variant']['prepare callback'] = 'applied (version 1, variant example_variant)';
+    $expected['detect callback'] = 'applied (top-level)';
+    $expected['variants']['example_variant']['detect callback'] = 'applied (variant example_variant)';
+    libraries_detect_library($library);
+    $this->verbose(var_export($expected, TRUE));
+    $this->verbose(var_export($library, TRUE));
+    $this->assertEqual($library, $expected, 'Detect callback was applied correctly.');
+
+    // Test a callback in the 'load' phase.
+    unset($expected['variants']);
+    $expected['loaded'] = 0;
+    $expected['load callback'] = 'applied (top-level)';
+    $library = libraries_load('example_callback');
+    $this->verbose(var_export($expected, TRUE));
+    $this->verbose(var_export($library, TRUE));
+    $this->assertEqual($library, $expected, 'Load callback was applied correctly.');
+
     // Test loading of a simple library with a top-level files property.
     $this->drupalGet('libraries_test/files');
     $this->assertLibraryFiles('example_1', 'File loading');
Index: tests/libraries_test.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v
retrieving revision 1.8
diff -u -p -r1.8 libraries_test.module
--- tests/libraries_test.module	27 Jan 2011 02:22:04 -0000	1.8
+++ tests/libraries_test.module	8 Feb 2011 13:43:32 -0000
@@ -201,6 +201,46 @@ function libraries_test_libraries_info()
     ),
   );
 
+  // Test the applying of callbacks.
+  $libraries['example_callback'] = array(
+    'name' => 'Example callback',
+    'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+    'version' => '1',
+    'versions' => array(
+      '1' => array(
+        'variants' => array(
+          'example_variant' => array(
+            // These keys are for testing purposes only.
+            'prepare callback' => 'not applied',
+            'detect callback' => 'not applied',
+            'load callback' => 'not applied',
+          ),
+        ),
+        // These keys are for testing purposes only.
+        'prepare callback' => 'not applied',
+        'detect callback' => 'not applied',
+        'load callback' => 'not applied',
+      ),
+    ),
+    'variants' => array(
+      'example_variant' => array(
+        // These keys are for testing purposes only.
+        'prepare callback' => 'not applied',
+        'detect callback' => 'not applied',
+        'load callback' => 'not applied',
+      ),
+    ),
+    'callbacks' => array(
+      'prepare' => array('_libraries_test_prepare_callback'),
+      'detect' => array('_libraries_test_detect_callback'),
+      'load' => array('_libraries_test_load_callback'),
+    ),
+    // These keys are for testing purposes only.
+    'prepare callback' => 'not applied',
+    'detect callback' => 'not applied',
+    'load callback' => 'not applied',
+  );
+
   // This library is used together with libraries_info() to be populated with
   // the defaults.
   $libraries['example_empty'] = array();
@@ -287,6 +327,107 @@ function _libraries_test_return_installe
 }
 
 /**
+ * Sets the 'prepare callback' key.
+ *
+ * This function is used as a test callback for the 'prepare' callback group.
+ *
+ * @param $library
+ *   An array of library information, which may be version- or variant-specific.
+ *   Passed by reference.
+ * @param $version
+ *   The version the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not version-specific.
+ * @param $variant
+ *   The variant the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not variant-specific.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_prepare_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'prepare');
+}
+
+/**
+ * Sets the 'detect callback' key.
+ *
+ * This function is used as a test callback for the 'detect' callback group.
+ *
+ * @param $library
+ *   An array of library information, which may be version- or variant-specific.
+ *   Passed by reference.
+ * @param $version
+ *   The version the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not version-specific.
+ * @param $variant
+ *   The variant the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not variant-specific.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_detect_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'detect');
+}
+
+/**
+ * Sets the 'load callback' key.
+ *
+ * This function is used as a test callback for the 'load' callback group.
+ *
+ * @param $library
+ *   An array of library information, which may be version- or variant-specific.
+ *   Passed by reference.
+ * @param $version
+ *   The version the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not version-specific.
+ * @param $variant
+ *   The variant the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not variant-specific.
+ *
+ * @see _libraries_test_callback()
+ */
+function _libraries_test_load_callback(&$library, $version, $variant) {
+  _libraries_test_callback($library, $version, $variant, 'load');
+}
+
+/**
+ * Sets the '[group] callback' key, where [group] is prepare, detect, or load.
+ *
+ * This function is used as a test callback for the all callback groups.
+ *
+ * It sets the '[group] callback' (see above) key to 'applied ([part])' where
+ * [part] is either 'top-level', 'version x.y' (where x.y is the passed-in
+ * version string), 'variant example' (where example is the passed-in variant
+ * name), or 'version x.y, variant example' (see above), depending on the part
+ * of the library the passed-in library information belongs to.
+ *
+ * @param $library
+ *   An array of library information, which may be version- or variant-specific.
+ *   Passed by reference.
+ * @param $version
+ *   The version the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not version-specific.
+ * @param $variant
+ *   The variant the library information passed in $library belongs to, or NULL
+ *   if the passed library information is not variant-specific.
+ */
+function _libraries_test_callback(&$library, $version, $variant, $group) {
+  $string = 'applied';
+  if (isset($version) && isset($variant)) {
+    $string .= " (version $version, variant $variant)";
+  }
+  elseif (isset($version)) {
+    $string .= " (version $version)";
+  }
+  elseif (isset($variant)) {
+    $string .= " (variant $variant)";
+  }
+  else {
+    $string .= ' (top-level)';
+  }
+  $library["$group callback"] = $string;
+}
+
+/**
  * Implements hook_menu().
  */
 function libraries_test_menu() {
