Index: libraries.api.php
===================================================================
RCS file: /cvs/drupal/contributions/modules/libraries/libraries.api.php,v
retrieving revision 1.1
diff -u -p -r1.1 libraries.api.php
--- libraries.api.php	3 Apr 2010 18:55:10 -0000	1.1
+++ libraries.api.php	26 May 2010 12:52:43 -0000
@@ -48,7 +48,11 @@
  *     uncompressed/source variant, those can be defined here. Each key should
  *     describe the variant type, e.g. 'minified' or 'source'. Each value is an
  *     associative array of top-level properties that are entirely overridden by
- *     the variant, most often just 'files'. Variants can be version specific.
+ *     the variant, most often just 'files'. Additionally, each variant can
+ *     contain a 'variant callback' and a 'variant arguments' key, which should
+ *     return TRUE or FALSE, depending on whether the variant is available or
+ *     not. If ommitted, the variant is expected to be always available.
+ *     Variants can be version specific.
  *   - versions: (optional) An associative array of supported library versions.
  *     Naturally, external libraries evolve over time and so do their APIs. In
  *     case a library changes between versions, different 'files' may need to be
@@ -62,6 +66,8 @@
  *     the same notion as the top-level 'files' property. Each specified file
  *     should contain the full path to the file.
  *   Additional top-level properties can be registered as needed.
+ *
+ * @see hook_library()
  */
 function hook_libraries_info() {
   // The following is a full explanation of all properties. See below for more
@@ -122,6 +128,10 @@ function hook_libraries_info() {
             'skin/example.css',
           ),
         ),
+        'variant callback' => 'mymodule_check_variant',
+        'variant arguments' => array(
+          'variant' => 'minified',
+        ),
       ),
     ),
     // Optional, but usually required: Override top-level properties for later
@@ -203,9 +213,9 @@ function hook_libraries_info() {
     'download url' => 'http://tinymce.moxiecode.com/download.php',
     'path' => 'jscripts/tiny_mce',
     'version arguments' => array(
-      // It can be easier to parse the first chars of a minified file instead of
-      // doing a multi-line pattern matching in a source file. See 'lines' and
-      // 'cols' below.
+      // It can be easier to parse the first characters of a minified file
+      // instead of doing a multi-line pattern matching in a source file. See
+      // 'lines' and 'cols' below.
       'file' => 'jscripts/tiny_mce/tiny_mce.js',
       // Best practice: Document the actual version strings for later reference.
       // 2.x: this.majorVersion="2";this.minorVersion="1.3"
Index: libraries.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/libraries/libraries.module,v
retrieving revision 1.4
diff -u -p -r1.4 libraries.module
--- libraries.module	3 Apr 2010 18:55:10 -0000	1.4
+++ libraries.module	26 May 2010 12:52:43 -0000
@@ -188,7 +188,7 @@ function libraries_info($library = NULL)
  */
 function libraries_detect($libraries) {
   foreach ($libraries as $name => $library) {
-    libraries_detect_library(&$libraries[$name]);
+    libraries_detect_library($libraries[$name]);
   }
   return $libraries;
 }
@@ -210,18 +210,14 @@ function libraries_detect_library(&$libr
   }
   if (!file_exists($library['library path'])) {
     $library['error'] = t('%library could not be found.', array('%library' => $library['title']));
-    continue;
+    return;
   }
 
   // Detect library version.
-  // Special handling for named arguments (single array).
-  if (!isset($library['version arguments'][0])) {
-    $library['version arguments'] = array($library['version arguments']);
-  }
   $library['version'] = call_user_func_array($library['version callback'], $library['version arguments']);
   if (empty($library['version'])) {
     $library['error'] = t('The version of %library could not be detected.', array('%library' => $library['title']));
-    continue;
+    return;
   }
 
   // Determine to which supported version the installed version maps.
@@ -229,13 +225,13 @@ function libraries_detect_library(&$libr
     ksort($library['versions']);
     $version = 0;
     foreach ($library['versions'] as $supported_version => $version_properties) {
-      if (version_compare($library['installed version'], $supported_version, '>=')) {
+      if (version_compare($library['version'], $supported_version, '>=')) {
         $version = $supported_version;
       }
     }
     if (!$version) {
       $library['error'] = t('The installed version %version of %library is not supported.', array('%version' => $library['installed version'], '%library' => $library['title']));
-      continue;
+      return;
     }
 
     // Apply version specific definitions and overrides.
@@ -243,10 +239,127 @@ function libraries_detect_library(&$libr
     unset($library['versions']);
   }
 
+  // Check each variant if it is available.
+  if (!empty($library['variants'])) {
+    foreach ($library['variants'] as $variant => $info) {
+      $library['variants'][$variant]['installed'] = !empty($info['variant callback']) ? call_user_func_array($info['variant callback'], $info['version arguments']) : TRUE;
+    }
+  }
+
   // If we end up here, the library should be usable.
   $library['installed'] = TRUE;
-  if (!empty($library['path'])) {
-    $library['library path'] .= '/' . $library['path'];
+}
+
+/**
+ * Loads a library.
+ *
+ * @param $library
+ *   The name of the library to load.
+ * @param $variant
+ *   The name of the variant to load.
+ */
+function libraries_load($library, $variant = NULL) {
+  $library = libraries_info($library);
+  libraries_detect_library($library);
+  libraries_load_files($library, $variant);
+}
+
+/**
+ * Loads a library's files.
+ *
+ * @param $library
+ *   The name of the library to load.
+ * @param $variant
+ *   The name of the variant to load.
+ */
+function libraries_load_files($library, $variant = NULL) {
+
+  if (isset($variant)) {
+    if ($library['variants'][$variant]['installed'] == TRUE) {
+      drupal_set_message('Well, there is a variant');
+      $library = array_merge($library, $library['variants'][$variant]);
+    }
+  }
+
+  // Load integration files.
+  if (!empty($library['integration files'])) {
+    foreach ($library['integration files'] as $module => $files) {
+      libraries_load_files(array(
+        'files' => $files,
+        'library path' => drupal_get_path('module', $module),
+      ));
+    }
+  }
+
+  // Load both the JavaScript and the CSS files.
+  // The parameters for drupal_add_js() and drupal_add_css() require special
+  // handling.
+  foreach (array('js', 'css') as $type) {
+    foreach ($library['files'][$type] as $data => $options) {
+      // If the value is not an array, it's a filename and passed as first
+      // (and only) argument.
+      if (!is_array($options)) {
+        $data = $options;
+        $options = NULL;
+      }
+      // In some cases, the first parameter ($data) is an array. Arrays can't be
+      // passed as keys in PHP, so we have to get $data from the value array.
+      if (is_numeric($data)) {
+        $data = $options['data'];
+        unset($options['data']);
+      }
+      // Apply the default weight if the weight isn't explicitly given.
+      if (!isset($options['weight'])) {
+        $options['weight'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
+      }
+      call_user_func('drupal_add_' . $type, $data, $options);
+    }
+  }
+
+
+  // Load PHP files.
+  if ($files = $library['files']['php']) {
+    foreach ($files as $file) {
+      $file_path = isset($library['path']) ? $library['library path'] . '/' . $library['path'] . '/' . $file : $library['library path'] . '/' . $file;
+      if (file_exists($file_path)) {
+        require_once($file_path);
+      }
+    }
+  }
+}
+
+/**
+ * Gets the version information from an arbitrary library.
+ *
+ * @param $file
+ *   The filename to parse for the version, relative to the library path. For
+ *   example: 'docs/changelog.txt'.
+ * @param $pattern
+ *   A string containing a regular expression (PCRE) to match the library
+ *   version. For example: '/@version (\d+)\.(\d+)/'.
+ * @param $lines
+ *   The maximum number of lines to search the pattern in. For example: 20.
+ * @param $cols
+ *   (optional) The maximum number of characters per line to take into account.
+ *   For example: 40. Defaults to unlimited. To be used if the file containing
+ *   the library version is minified/compressed, i.e. reading a single line
+ *   would read the entire library into memory.
+ *
+ * @return
+ *   A string containing the version of the library.
+ */
+function libraries_get_version($file, $pattern, $lines, $cols = NULL) {
+  if (!file_exists(libraries_get_path('example2') . '/' . $file)) {
+    return;
+  }
+  $file = fopen($file, 'r');
+  while ($lines && $line = fgets($file, $cols)) {
+    if (preg_match($pattern, $line, $version)) {
+      fclose($library);
+      return $version[1];
+    }
+    $lines--;
   }
+  fclose($library);
 }
 
Index: tests/example1.css
===================================================================
RCS file: tests/example1.css
diff -N tests/example1.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1.css	26 May 2010 12:52:43 -0000
@@ -0,0 +1 @@
+/* $Id$ */
Index: tests/example1.js
===================================================================
RCS file: tests/example1.js
diff -N tests/example1.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1.js	26 May 2010 12:52:43 -0000
@@ -0,0 +1,2 @@
+<?php
+// $Id$
Index: tests/example1.php
===================================================================
RCS file: tests/example1.php
diff -N tests/example1.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1.php	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+<?php
+// $Id$
+
+// Define a dummy function to see if this file was loaded.
+function example1() {
+}
Index: tests/example1_1.css
===================================================================
RCS file: tests/example1_1.css
diff -N tests/example1_1.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_1.css	26 May 2010 12:52:43 -0000
@@ -0,0 +1 @@
+/* $Id$ */
Index: tests/example1_1.js
===================================================================
RCS file: tests/example1_1.js
diff -N tests/example1_1.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_1.js	26 May 2010 12:52:43 -0000
@@ -0,0 +1,2 @@
+<?php
+// $Id$
Index: tests/example1_1.php
===================================================================
RCS file: tests/example1_1.php
diff -N tests/example1_1.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_1.php	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+<?php
+// $Id$
+
+// Define a dummy function to see if this file was loaded.
+function example1_1() {
+}
Index: tests/example1_2.css
===================================================================
RCS file: tests/example1_2.css
diff -N tests/example1_2.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.css	26 May 2010 12:52:43 -0000
@@ -0,0 +1 @@
+/* $Id$ */
Index: tests/example1_2.js
===================================================================
RCS file: tests/example1_2.js
diff -N tests/example1_2.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.js	26 May 2010 12:52:43 -0000
@@ -0,0 +1,2 @@
+<?php
+// $Id$
Index: tests/example1_2.min.css
===================================================================
RCS file: tests/example1_2.min.css
diff -N tests/example1_2.min.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.min.css	26 May 2010 12:52:43 -0000
@@ -0,0 +1 @@
+/* $Id$ */
Index: tests/example1_2.min.js
===================================================================
RCS file: tests/example1_2.min.js
diff -N tests/example1_2.min.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.min.js	26 May 2010 12:52:43 -0000
@@ -0,0 +1,2 @@
+<?php
+// $Id$
Index: tests/example1_2.min.php
===================================================================
RCS file: tests/example1_2.min.php
diff -N tests/example1_2.min.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.min.php	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+<?php
+// $Id$
+
+// Define a dummy function to see if this file was loaded.
+function example1_2_min() {
+}
Index: tests/example1_2.php
===================================================================
RCS file: tests/example1_2.php
diff -N tests/example1_2.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example1_2.php	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+<?php
+// $Id$
+
+// Define a dummy function to see if this file was loaded.
+function example1_2() {
+}
Index: tests/example2.txt
===================================================================
RCS file: tests/example2.txt
diff -N tests/example2.txt
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/example2.txt	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+; $Id$
+
+Example library 2
+
+Version 1.0
+
Index: tests/libraries.test
===================================================================
RCS file: tests/libraries.test
diff -N tests/libraries.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries.test	26 May 2010 12:52:43 -0000
@@ -0,0 +1,56 @@
+<?php
+// $Id: libraries.test,v 1.1 2010/04/03 18:55:10 sun Exp $
+
+/**
+ * @file
+ * Unit tests for Libraries API.
+ */
+
+class LibrariesTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => t('Libraries API tests'),
+      'description' => t('Test the detection and loading of libraries'),
+      'group' => t('Libraries API'),
+    );
+  }
+
+  public function setUp() {
+    parent::setUp('libraries', 'libraries_test');
+  }
+
+  public function testLibrariesDetection() {
+    // Test version-overloading.
+    $this->drupalGet('libraries_test_version');
+    $this->assertRaw('example1_2.js', t('Make sure that the JavaScript file of the correct library version is loaded.'));
+    $this->assertNoRaw('example1.js', t('Make sure that the JavaScript file of the wrong library version is not loaded.'));
+    $this->assertNoRaw('example1_1.js', t('Make sure that the JavaScript file of the wrong library version is not loaded.'));
+    $this->assertRaw('example1_2.css', t('Make sure that the CSS file of the correct library version is loaded.'));
+    $this->assertNoRaw('example1.css', t('Make sure that the CSS file of the wrong library version is not loaded.'));
+    $this->assertNoRaw('example1_1.css', t('Make sure that the CSS file of the wrong library version is not loaded.'));
+    $this->assertText('example1_2.php', t('Make sure that the PHP file of the correct library version is loaded.'));
+    $this->assertNoText('example1.php', t('Make sure that the PHP file of the wrong library version is not loaded.'));
+    $this->assertNoText('example1_1.php', t('Make sure that the PHP file of the wrong library version is not loaded.'));
+    $this->assertRaw('libraries_test_1.js', t('Make sure that the JavaScript integration file is loaded.'));
+    $this->assertRaw('libraries_test_1.css', t('Make sure that the CSS integration file is loaded.'));
+    $this->assertText('libraries_test_1.php', t('Make sure that the PHP integration file is loaded.'));
+
+    // Test variant-overloading.
+    $this->drupalGet('libraries_test_variant');
+    $this->assertRaw('example1_2.min.js', t('Make sure that the JavaScript file of the correct library variant is loaded.'));
+    $this->assertNoRaw('example1.js', t('Make sure that the default JavaScript file is not loaded.'));
+    $this->assertNoRaw('example1_2.js', t('Make sure that the JavaScript file of the correct library version is not loaded.'));
+    $this->assertRaw('example1_2.min.css', t('Make sure that the CSS file of the correct library variant is loaded.'));
+    $this->assertNoRaw('example1.css', t('Make sure that the default CSS file is not loaded.'));
+    $this->assertNoRaw('example1_2.css', t('Make sure that the CSS file of the correct library version is not loaded.'));
+    $this->assertText('example1_2.min.php', t('Make sure that the PHP file of the correct library variant is loaded.'));
+    $this->assertNoText('example1.php', t('Make sure that the default PHP file is not loaded.'));
+    $this->assertNoText('example1_2.php', t('Make sure that the PHP file of the correct version is not loaded.'));
+    $this->assertRaw('libraries_test_1.js', t('Make sure that the JavaScript integration file is loaded.'));
+    $this->assertRaw('libraries_test_1.css', t('Make sure that the CSS integration file is loaded.'));
+    $this->assertText('libraries_test_1.php', t('Make sure that the PHP integration file is loaded.'));
+  }
+
+}
+
Index: tests/libraries_test.info
===================================================================
RCS file: tests/libraries_test.info
diff -N tests/libraries_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries_test.info	26 May 2010 12:52:43 -0000
@@ -0,0 +1,7 @@
+; $Id$
+name = Libraries test module
+description = Tests library detection and loading.
+core = 7.x
+dependencies[] = libraries
+files[] = libraries_test.module
+hidden = TRUE
Index: tests/libraries_test.module
===================================================================
RCS file: tests/libraries_test.module
diff -N tests/libraries_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries_test.module	26 May 2010 12:52:43 -0000
@@ -0,0 +1,186 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Tests the library detection and loading.
+ */
+
+/**
+ * Implements hook_libraries_info().
+ */
+function libraries_test_libraries_info() {
+  // Test all possible options with an existing library.
+  $libraries['example1'] = array(
+    'library path' => drupal_get_path('module', 'libraries') . '/tests',
+    'version callback' => 'libraries_test_get_version',
+    'files' => array(
+      'js' => array(
+        'example1.js',
+      ),
+      'css' => array(
+        'example1.css',
+      ),
+      'php' => array(
+        'example1.php'
+      ),
+    ),
+    'versions' => array(
+      '1' => array(
+        'files' => array(
+          'js' => array(
+            'example1_1.js',
+          ),
+          'css' => array(
+            'example1_1.css',
+          ),
+          'php' => array(
+            'example1_1.php',
+          ),
+        ),
+      ),
+      '2' => array(
+        'files' => array(
+          'js' => array(
+            'example1_2.js',
+          ),
+          'css' => array(
+            'example1_2.css',
+          ),
+          'php' => array(
+            'example1_2.php',
+          ),
+        ),
+      ),
+    ),
+    'variants' => array(
+      'minified' => array(
+        'files' => array(
+          'js' => array(
+            'example1_2.min.js',
+          ),
+          'css' => array(
+            'example1_2.min.css',
+          ),
+          'php' => array(
+            'example1_2.min.php',
+          ),
+        ),
+      ),
+    ),
+    'integration files' => array(
+      'libraries_test' => array(
+        'js' => array(
+          'libraries_test_1.js',
+        ),
+        'css' => array(
+          'libraries_test_1.css',
+        ),
+        'php' => array(
+          'libraries_test_1.php',
+        ),
+      ),
+    ),
+  );
+  // Test the default version callback libraries_get_version() as well as the
+  // error handling when the library files do not exist.
+  $libraries['example2'] = array(
+    'library path' => drupal_get_path('module', 'libraries') . '/tests',
+    'version arguments' => array(
+      'file' => 'example2.txt',
+      'pattern' => '/Version (\d+)\.(\d+)/',
+      'lines' => 7,
+    ),
+    'files' => array(
+      'js' => array(
+        'example2.js',
+      ),
+      'css' => array(
+        'example2.css',
+      ),
+      'php' => array(
+        'example2.php',
+      ),
+    ),
+  );
+  return $libraries;
+}
+
+/**
+ * Gets the version of the example library.
+ *
+ * @return
+ *   The string '2'.
+ */
+function libraries_test_get_version() {
+  return '2';
+}
+
+/**
+ * Implements hook_menu().
+ */
+function libraries_test_menu() {
+  $items['libraries_test_version'] = array(
+    'title' => 'Libraries version test',
+    'page callback' => 'libraries_test_version',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  $items['libraries_test_variant'] = array(
+    'title' => 'Libraries variant test',
+    'page callback' => 'libraries_test_variant',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Loads the test libraries.
+ */
+function libraries_test_version() {
+  libraries_load('example1');
+  // Since SimpleTest fails on any shown errors, we load a non-existing library
+  // to check the fallback system.
+  libraries_load('example2');
+  // The JavaScript and CSS files can be checked directly by SimpleTest, so we
+  // only need to manually check for the PHP files.
+  $output = '';
+  if (function_exists('example1')) {
+    $output .= 'example1.php';
+  }
+  if (function_exists('example1_1')) {
+    $output .= 'example1_1.php';
+  }
+  if (function_exists('example1_2')) {
+    $output .= 'example1_2.php';
+  }
+  // Also check for the PHP integration file.
+  if (function_exists('libraries_test_1')) {
+    $output .= 'libraries_test_1.php';
+  }
+  drupal_set_message($output);
+  return '';
+}
+
+function libraries_test_variant() {
+  libraries_load('example1', 'minified');
+  // The JavaScript and CSS files can be checked directly by SimpleTest, so we
+  // only need to manually check for the PHP files.
+  $output = '';
+  if (function_exists('example1')) {
+    $output .= 'example1.php';
+  }
+  if (function_exists('example1_2')) {
+    $output .= 'example1_2.php';
+  }
+  if (function_exists('example1_2_min')) {
+    $output .= 'example1_2.min.php';
+  }
+  // Also check for the PHP integration file.
+  if (function_exists('libraries_test_1')) {
+    $output .= 'libraries_test_1.php';
+  }
+  drupal_set_message($output);
+  return '';
+}
Index: tests/libraries_test_1.css
===================================================================
RCS file: tests/libraries_test_1.css
diff -N tests/libraries_test_1.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries_test_1.css	26 May 2010 12:52:43 -0000
@@ -0,0 +1 @@
+/* $Id$ */
Index: tests/libraries_test_1.js
===================================================================
RCS file: tests/libraries_test_1.js
diff -N tests/libraries_test_1.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries_test_1.js	26 May 2010 12:52:43 -0000
@@ -0,0 +1,2 @@
+<?php
+// $Id$
Index: tests/libraries_test_1.php
===================================================================
RCS file: tests/libraries_test_1.php
diff -N tests/libraries_test_1.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/libraries_test_1.php	26 May 2010 12:52:43 -0000
@@ -0,0 +1,6 @@
+<?php
+// $Id$
+
+// Define a dummy function to see if this file was loaded.
+function libraries_test_1() {
+}
