Index: libraries.api.php
===================================================================
RCS file: libraries.api.php
diff -N libraries.api.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libraries.api.php	3 Apr 2010 17:11:15 -0000
@@ -0,0 +1,280 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Documents API functions for Libraries module.
+ */
+
+/**
+ * Return information about external libraries.
+ *
+ * @return
+ *   An associative array whose keys are internal names of libraries and whose
+ *   values are describing each library. Each key is the directory name below
+ *   the 'libraries' directory, in which the library may be found. Each value is
+ *   an associative array containing:
+ *   - title: The official, human-readable name of the library.
+ *   - vendor url: The URL of the homepage of the library.
+ *   - download url: The URL of a web page on which the library can be obtained.
+ *   - path: (optional) A relative path from the directory of the library to the
+ *     actual library. Only required if the extracted download package contains
+ *     the actual library files in a sub-directory.
+ *   - version callback: (optional) The name of a function that detects and
+ *     returns the full version string of the library. Defaults to
+ *     libraries_get_version().
+ *   - version arguments: A list of arguments to pass to the version callback.
+ *     The default version callback libraries_get_version() expects a single,
+ *     single, associative array with named keys:
+ *     - file: The filename to parse for the version, relative to the library
+ *       path. For example: 'docs/changelog.txt'.
+ *     - pattern: A string containing a regular expression (PCRE) to match the
+ *       library version. For example: '/@version (\d+)\.(\d+)/'.
+ *     - lines: The maximum number of lines to search the pattern in. For
+ *       example: 20.
+ *     - 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.
+ *   - files: An associative array of library files to load. Supported keys are:
+ *     - js: A list of JavaScript files to load, using the same syntax as Drupal
+ *       core's hook_library().
+ *     - css: A list of CSS files to load, using the same syntax as Drupal
+ *       core's hook_library().
+ *     - php: A list of PHP files to load.
+ *   - variants: (optional) An associative array of available library variants.
+ *     For example, the top-level 'files' property may refer to a default
+ *     variant that is compressed. If the library also ships with a minified and
+ *     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.
+ *   - 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
+ *     loaded, different 'variants' may become available, or Drupal modules need
+ *     to load different integration files adapted to the new version.
+ *     Each key is a version *string* (PHP does not support floats as keys).
+ *     Each value is an associative array of top-level properties that are
+ *     entirely overridden by the version.
+ *   - integration files: (optional) An associative array whose keys are module
+ *     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 full path to the file.
+ *   Additional top-level properties can be registered as needed.
+ */
+function hook_libraries_info() {
+  // The following is a full explanation of all properties. See below for more
+  // concrete example implementations.
+
+  // This array key lets Libraries API search for 'sites/all/libraries/example'
+  // directory, which should contain the entire, original extracted library.
+  $libraries['example'] = array(
+    // Only used in administrative UI of Libraries API.
+    'title' => 'Example library',
+    'vendor url' => 'http://example.com',
+    'download url' => 'http://example.com/download',
+    // Optional: If, after extraction, the actual library files are contained in
+    // 'sites/all/libraries/example/lib', specify the relative path here.
+    'path' => 'lib',
+    // Optional: Define a custom version detection callback, if required.
+    'version callback' => 'mymodule_get_version',
+    // Specify arguments for the version callback. By default,
+    // libraries_get_version() takes a named argument array:
+    'version arguments' => array(
+      'file' => 'docs/CHANGELOG.txt',
+      'pattern' => '/@version (\d+)\.(\d+)/',
+      'lines' => 5,
+      'cols' => 20,
+    ),
+    // Default list of files of the library to load. Important: Only specify
+    // third-party files belonging to the library here, not integration files of
+    // your module.
+    'files' => array(
+      // 'js' and 'css' follow the syntax of hook_library(), but file paths are
+      // relative to the library path.
+      'js' => array(
+        'exlib.js',
+        'gadgets/foo.js',
+      ),
+      'css' => array(
+        'lib_style.css',
+        'skin/example.css',
+      ),
+      // For PHP libraries, specify include files here, still relative to the
+      // library path.
+      'php' => array(
+        'exlib.php',
+        'exlib.inc',
+      ),
+    ),
+    // Optional: Specify alternative variants of the library, if available.
+    'variants' => array(
+      // All properties defined for 'minified' override top-level properties.
+      'minified' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.min.js',
+            'gadgets/foo.min.js',
+          ),
+          'css' => array(
+            'lib_style.css',
+            'skin/example.css',
+          ),
+        ),
+      ),
+    ),
+    // Optional, but usually required: Override top-level properties for later
+    // versions of the library. The properties of the minimum version that is
+    // matched override the top-level properties. Note:
+    // - When registering 'versions', it usually does not make sense to register
+    //   'files', 'variants', and 'integration files' on the top-level, as most
+    //   of those likely need to be different per version and there are no
+    //   defaults.
+    // - The array keys have to be strings, as PHP does not support floats for
+    //   array keys.
+    'versions' => array(
+      '2' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.js',
+          ),
+          'css' => array(
+            'exlib_style.css',
+          ),
+        ),
+      ),
+      '3.0' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.js',
+          ),
+          'css' => array(
+            'lib_style.css',
+          ),
+        ),
+      ),
+      '3.2' => array(
+        'files' => array(
+          'js' => array(
+            'exlib.js',
+            'gadgets/foo.js',
+          ),
+          'css' => array(
+            'lib_style.css',
+            'skin/example.css',
+          ),
+        ),
+      ),
+    ),
+    // Optional: Register files to auto-load for your module. All files must be
+    // keyed by module, and follow the syntax of the 'files' property.
+    'integration files' => array(
+      'mymodule' => array(
+        'js' => 'ex_lib.inc',
+      ),
+    ),
+  );
+
+  // A very simple library. No changing APIs (hence, no versions), no variants.
+  // Expected to be extracted into 'sites/all/libraries/simple'.
+  $libraries['simple'] = array(
+    'title' => 'Simple library',
+    'vendor url' => 'http://example.com/simple',
+    'download url' => 'http://example.com/simple',
+    'version arguments' => array(
+      'file' => 'readme.txt',
+      // Best practice: Document the actual version strings for later reference.
+      // 1.x: Version 1.0
+      'pattern' => '/Version (\d+)\.(\d+)/',
+      'lines' => 5,
+    ),
+    'files' => array(
+      'js' => array(
+        'simple.js',
+      ),
+    ),
+  );
+
+  // A library that (naturally) evolves over time with API changes.
+  $libraries['tinymce'] = array(
+    'title' => 'TinyMCE',
+    'vendor url' => 'http://tinymce.moxiecode.com',
+    '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.
+      '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"
+      // 3.x: majorVersion:'3',minorVersion:'2.0.1'
+      'pattern' => '@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@',
+      'lines' => 1,
+      'cols' => 100,
+    ),
+    'versions' => array(
+      '2.1' => array(
+        'files' => array(
+          'js' => array('tiny_mce.js'),
+        ),
+        'variants' => array(
+          'source' => array(
+            'files' => array(
+              'js' => array('tiny_mce_src.js'),
+            ),
+          ),
+        ),
+        'integration files' => array(
+          'wysiwyg' => array(
+            'js' => array(
+              drupal_get_path('module', 'wysiwyg') . '/editors/js/tinymce-2.js',
+            ),
+            'css' => array(
+              drupal_get_path('module', 'wysiwyg') . '/editors/js/tinymce-2.css',
+            ),
+          ),
+        ),
+      ),
+      // Definition used if 3.1 or above is detected.
+      '3.1' => array(
+        // Does not support JS aggregation.
+        'files' => array(
+          'js' => array(
+            'tiny_mce.js' => array('preprocess' => FALSE),
+          ),
+        ),
+        'variants' => array(
+          // New variant leveraging jQuery. Not stable yet; therefore not the
+          // default variant.
+          'jquery' => array(
+            'files' => array(
+              'js' => array(
+                'tiny_mce_jquery.js' => array('preprocess' => FALSE),
+              ),
+            ),
+          ),
+          'source' => array(
+            'files' => array(
+              'js' => array(
+                'tiny_mce_src.js' => array('preprocess' => FALSE),
+              ),
+            ),
+          ),
+        ),
+        'integration files' => array(
+          'wysiwyg' => array(
+            'js' => array(
+              drupal_get_path('module', 'wysiwyg') . '/editors/js/tinymce-3.js',
+            ),
+            'css' => array(
+              drupal_get_path('module', 'wysiwyg') . '/editors/js/tinymce-3.css',
+            ),
+          ),
+        ),
+      ),
+    ),
+  );
+  return $libraries;
+}
Index: libraries.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.info,v
retrieving revision 1.2
diff -u -p -r1.2 libraries.info
--- libraries.info	16 Mar 2010 19:27:34 -0000	1.2
+++ libraries.info	3 Apr 2010 15:13:26 -0000
@@ -1,5 +1,6 @@
 ; $Id: libraries.info,v 1.2 2010/03/16 19:27:34 sun Exp $
 name = Libraries
-description = External library handling for Drupal modules.
+description = Allows version dependent and shared usage of external libraries.
 core = 7.x
 files[] = libraries.module
+files[] = tests/libraries.test
Index: libraries.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v
retrieving revision 1.3
diff -u -p -r1.3 libraries.module
--- libraries.module	2 Apr 2010 15:38:38 -0000	1.3
+++ libraries.module	3 Apr 2010 18:51:34 -0000
@@ -112,3 +112,141 @@ function libraries_get_libraries() {
   return $directories;
 }
 
+/**
+ * Returns information about registered libraries.
+ *
+ * The returned information is unprocessed, i.e. as registered by modules.
+ *
+ * @param $library
+ *   (optional) The internal name of a library to return registered information
+ *   for, or FALSE if no library with the given name exists. If omitted,
+ *   information about all libraries is returned.
+ *
+ * @return
+ *   An associative array containing registered information for all libraries,
+ *   or the registered information for the library specified by $name.
+ *
+ * @see hook_libraries_info()
+ *
+ * @todo Re-introduce support for include file plugin system - either by copying
+ *   Wysiwyg's code, or directly switching to CTools.
+ */
+function libraries_info($library = NULL) {
+  $libraries = &drupal_static(__FUNCTION__);
+
+  if (!isset($libraries)) {
+    $libraries = array();
+    foreach (module_implements('libraries_info') as $module) {
+      foreach (module_invoke($module, 'libraries_info') as $name => $properties) {
+        $properties['module'] = $module;
+        $properties['name'] = $name;
+
+        $properties += array(
+          'title' => $name,
+          'vendor url' => '',
+          'download url' => '',
+          'path' => '',
+          'version callback' => 'libraries_get_version',
+          'version arguments' => array(),
+          'files' => array(),
+          'variants' => array(),
+          'versions' => array(),
+          'integration files' => array(),
+        );
+
+        $libraries[$name] = $properties;
+      }
+    }
+    // Allow modules to alter the registered libraries.
+    drupal_alter('libraries_info', $libraries);
+  }
+
+  if (isset($library)) {
+    return !empty($libraries[$library]) ? $libraries[$library] : FALSE;
+  }
+  return $libraries;
+}
+
+/**
+ * Detect libraries and library versions.
+ *
+ * @todo It makes little sense to pass $libraries by reference here. We need to
+ *   figure out whether, and if, how we want to retain the processed
+ *   information. I.e. either use a static cache here, or make libraries_info()
+ *   conditionally invoke libraries_detect($name). D7 only way: Re-use
+ *   drupal_static() of libraries_info() - but would still require to update the
+ *   (DB) cache (there likely will be one soon).
+ *   Also, we probably do not want to ALWAYS parse ALL possible libraries; rather,
+ *   the requesting/consuming module likely wants to know whether a list of
+ *   supported libraries (possibly those registered by itself, or in a certain
+ *   "category") is available... Food for thought.
+ *
+ * @param $libraries
+ *   An array of libraries to detect, as returned from libraries_info().
+ *
+ * @see libraries_info()
+ */
+function libraries_detect($libraries) {
+  foreach ($libraries as $name => $library) {
+    libraries_detect_library(&$libraries[$name]);
+  }
+  return $libraries;
+}
+
+/**
+ * Tries to detect a library and its installed version.
+ *
+ * @param $library
+ *   An associative array describing a single library, as returned from
+ *   libraries_info(), passed by reference.
+ */
+function libraries_detect_library(&$library) {
+  $library['installed'] = FALSE;
+  $name = $library['name'];
+
+  // Check whether the library exists.
+  if (!isset($library['library path'])) {
+    $library['library path'] = libraries_get_path($name);
+  }
+  if (!file_exists($library['library path'])) {
+    $library['error'] = t('%library could not be found.', array('%library' => $library['title']));
+    continue;
+  }
+
+  // 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;
+  }
+
+  // Determine to which supported version the installed version maps.
+  if (!empty($library['versions'])) {
+    ksort($library['versions']);
+    $version = 0;
+    foreach ($library['versions'] as $supported_version => $version_properties) {
+      if (version_compare($library['installed 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;
+    }
+
+    // Apply version specific definitions and overrides.
+    $library = array_merge($library, $library['versions'][$version]);
+    unset($library['versions']);
+  }
+
+  // If we end up here, the library should be usable.
+  $library['installed'] = TRUE;
+  if (!empty($library['path'])) {
+    $library['library path'] .= '/' . $library['path'];
+  }
+}
+
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	3 Apr 2010 15:10:51 -0000
@@ -0,0 +1,8 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Unit tests for Libraries API.
+ */
+
