From ef5ce455dc66f1aeda5b30a9a85c4a15d30ea368 Mon Sep 17 00:00:00 2001 From: Tobias Stckler Date: Fri, 7 Oct 2011 04:50:17 +0200 Subject: [PATCH] Add support for external libraries via 'path callback' --- libraries.api.php | 83 ++++++++++++++++++++++++++++++++++++++---- libraries.module | 57 ++++++++++++++++------------- tests/libraries.test | 6 ++- tests/libraries_test.module | 38 ++++++++++---------- 4 files changed, 130 insertions(+), 54 deletions(-) diff --git a/libraries.api.php b/libraries.api.php index 023cc0a..ffeecbc 100644 --- a/libraries.api.php +++ b/libraries.api.php @@ -15,15 +15,38 @@ * an associative array containing: * - name: 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. - * - library path: (optional) The absolute path to the library directory. This + * - download url: (optional) The URL of a web page on which the library can + * be obtained. For external libraries, which cannot be downloaded, this can + * be omitted. + * - path: (optional) The absolute path to the library directory. This * should not be declared normally, as it is automatically detected, to * allow for multiple possible library locations. A valid use-case is an * external library, in which case the full URL to the library should be * specified here. + * - path callback: (optional) The name of a function that detects and + * returns the full path to the library. If the library cannot be found, it + * should return FALSE. The first argument is always $library, an array + * containing all library information as described here. There are two ways + * to declare the path callback's additional arguments, either as a single + * $options parameter or as multiple parameters, which correspond to the two + * ways to specify the argument values (see 'path arguments'). Defaults to + * libraries_get_path(). + * - path arguments: (optional) A list of arguments to pass to the path + * callback. Path arguments can be declared either as an associative array + * whose keys are the argument names or as an indexed array without + * specifying keys. If declared as an associative array, the arguments get + * passed to the location callback as a single $options parameter whose keys + * are the argument names (i.e. $options is identical to the specified + * array). If declared as an indexed array, the array values get passed to + * the version callback as seperate arguments in the order they were + * declared. The default location callback libraries_get_path() expects a + * single, associative array with the following named keys: + * - fallback: (optional) A fallback path to use in case the library is not + * found locally. This can be used for libraries that can be downloaded, + * but are also available externally. + * - directory: (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: (optional) The version of the library. This should not be * declared normally, as it is automatically detected (see 'version * callback' below) to allow for version changes of libraries without code @@ -47,7 +70,7 @@ * declared as an indexed array, the array values get passed to the version * callback as seperate arguments in the order they were declared. The * default version callback libraries_get_version() expects a single, - * associative array with named keys: + * associative array with the following 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 @@ -180,7 +203,7 @@ function hook_libraries_info() { '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', + 'directory' => 'lib', // Optional: Define a custom version detection callback, if required. 'version callback' => 'mymodule_get_version', // Specify arguments for the version callback. By default, @@ -307,7 +330,7 @@ function hook_libraries_info() { 'name' => 'TinyMCE', 'vendor url' => 'http://tinymce.moxiecode.com', 'download url' => 'http://tinymce.moxiecode.com/download.php', - 'path' => 'jscripts/tiny_mce', + 'directory' => 'jscripts/tiny_mce', // The regular expression catches two parts (the major and the minor // version), which libraries_get_version() doesn't allow. 'version callback' => 'tinymce_get_version', @@ -377,6 +400,50 @@ function hook_libraries_info() { ), ), ); + + // A simple external library. + $libraries['external'] = array( + 'name' => 'External library', + // This library does not have a download url! + 'vendor url' => 'http://example.com/external', + // It is only availably externally so we must declare the library path. + 'path' => 'http://example.com/external/source', + // The version cannot be detected programmatically, so it must be declared + // upfront. + 'version' => '1.0.1', + // The library only consists of one file, which is located at + // http://example.com/external/source/external.js. We have already declared + // the library path above, so we only need to specify the filename here. + 'files' => array( + 'js' => array( + 'external.js', + ), + ), + ); + + // A library that is both available locally and externally, i.e. you can + // download it to your server for performance or use the online version for + // convenience. + $libraries['openlayers'] = array( + 'name' => 'OpenLayers', + 'vendor url' => 'http://openlayers.org/', + 'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload', + // Because we want people to be able to use the library locally, but fall + // back to the external library, we declare the optional 'fallback' option. + // The path we specify is then used as a fallback if the library is not + // found locally. + 'path arguments' => array( + 'fallback' => 'http://openlayers.org/api', + ), + // Regardless of whether it is available locally or externally, the files + // are the same. + 'files' => array( + 'js' => array( + 'OpenLayers.js', + ), + ), + ); + return $libraries; } diff --git a/libraries.module b/libraries.module index 929b8ef..4f0ab1a 100644 --- a/libraries.module +++ b/libraries.module @@ -15,32 +15,30 @@ function libraries_flush_caches() { /** * Gets the path of a library. * - * @param $name - * The machine name of a library to return the path for. - * @param $base_path - * Whether to prefix the resulting path with base_path(). + * @param $library + * An associative array containing all information about the library. + * @param $fallback + * An external URL that, if specified, is returned if the library is not found + * locally. * * @return * The path to the specified library or FALSE if the library wasn't found. * * @ingroup libraries */ -function libraries_get_path($name, $base_path = FALSE) { - $libraries = &drupal_static(__FUNCTION__); +function libraries_get_path($library, $fallback)) { + $directories = &drupal_static(__FUNCTION__); - if (!isset($libraries)) { - $libraries = libraries_get_libraries(); + if (!isset($directories)) { + $directories = libraries_get_directories(); } - $path = ($base_path ? base_path() : ''); - if (!isset($libraries[$name])) { - return FALSE; + if (!isset($directories[$library['machine name']])) { + return (isset($options['fallback']) ? $options['fallback'] : FALSE); } else { - $path .= $libraries[$name]; + return $directories[$library['machine name']]; } - - return $path; } /** @@ -59,7 +57,7 @@ function libraries_get_path($name, $base_path = FALSE) { * * @ingroup libraries */ -function libraries_get_libraries() { +function libraries_get_directories() { $directory = 'libraries'; $searchdir = array(); $profile = drupal_get_profile(); @@ -383,8 +381,9 @@ function libraries_info_defaults(&$library, $name) { 'name' => $name, 'vendor url' => '', 'download url' => '', - 'path' => '', - 'library path' => NULL, + 'path callback' => 'libraries_get_path', + 'path arguments' => array(), + 'directory' => '', 'version callback' => 'libraries_get_version', 'version arguments' => array(), 'files' => array(), @@ -445,10 +444,18 @@ function libraries_detect($name) { $library['installed'] = FALSE; // Check whether the library exists. - if (!isset($library['library path'])) { - $library['library path'] = libraries_get_path($library['machine name']); + if (!isset($library['path'])) { + // We support both a single parameter, which is an associative array, and an + // indexed array of multiple parameters. + if (isset($library['path arguments'][0])) { + // Add the library name as the first argument. + $library['path'] = call_user_func_array($library['path callback'], array_merge(array($library), $library['path arguments'])); + } + else { + $library['path'] = $library['path callback']($library, $library['path arguments']); + } } - if ($library['library path'] === FALSE || !file_exists($library['library path'])) { + if ($library['path'] == FALSE) { $library['error'] = 'not found'; $library['error message'] = t('The %library library could not be found.', array( '%library' => $library['name'], @@ -622,15 +629,15 @@ function libraries_load_files($library) { foreach ($library['integration files'] as $module => $files) { libraries_load_files(array( 'files' => $files, - 'path' => '', - 'library path' => drupal_get_path('module', $module), + 'path' => drupal_get_path('module', $module), + 'directory' => '', )); } } // Construct the full path to the library for later use. - $path = $library['library path']; - $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path); + $path = $library['path']; + $path = ($library['directory'] !== '' ? $path . '/' . $library['directory'] : $path); // Count the number of loaded files for the return value. $count = 0; @@ -712,7 +719,7 @@ function libraries_get_version($library, $options) { 'cols' => 200, ); - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + $file = DRUPAL_ROOT . '/' . $library['path'] . '/' . $options['file']; if (empty($options['file']) || !file_exists($file)) { return; } diff --git a/tests/libraries.test b/tests/libraries.test index 6ff9296..59b7610 100644 --- a/tests/libraries.test +++ b/tests/libraries.test @@ -102,6 +102,7 @@ class LibrariesTestCase extends DrupalWebTestCase { // FALSE for missing or incompatible dependencies. $library['installed'] = TRUE; libraries_detect_dependencies($library); + $this->verbose('
' . var_export($library, TRUE) . '
'); $this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'"); } foreach ($incompatible as $version_string) { @@ -109,6 +110,7 @@ class LibrariesTestCase extends DrupalWebTestCase { $library['installed'] = TRUE; unset($library['error'], $library['error message']); libraries_detect_dependencies($library); + $this->verbose('
' . var_export($library, TRUE) . '
'); $this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'"); } // Instead of repeating this assertion for each version string, we just @@ -125,7 +127,7 @@ class LibrariesTestCase extends DrupalWebTestCase { // Test that library information is found correctly. $expected = array( 'name' => 'Example files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'files' => array( 'js' => array('example_1.js' => array()), @@ -246,7 +248,7 @@ class LibrariesTestCase extends DrupalWebTestCase { // Test the applying of callbacks. $expected = array( 'name' => 'Example callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'versions' => array( '1' => array( diff --git a/tests/libraries_test.module b/tests/libraries_test.module index 3ea679a..b5a2c72 100644 --- a/tests/libraries_test.module +++ b/tests/libraries_test.module @@ -12,17 +12,18 @@ function libraries_test_libraries_info() { // Test library detection. $libraries['example_missing'] = array( 'name' => 'Example missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/missing', + // Not specifying a path invokes libraries_get_path(), which will return + // FALSE. ); $libraries['example_undetected_version'] = array( 'name' => 'Example undetected version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array(FALSE), ); $libraries['example_unsupported_version'] = array( 'name' => 'Example unsupported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array('1'), 'versions' => array( @@ -32,7 +33,7 @@ function libraries_test_libraries_info() { $libraries['example_supported_version'] = array( 'name' => 'Example supported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array('1'), 'versions' => array( @@ -43,7 +44,7 @@ function libraries_test_libraries_info() { // Test the default version callback. $libraries['example_default_version_callback'] = array( 'name' => 'Example default version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version arguments' => array( 'file' => 'README.txt', // Version 1 @@ -55,7 +56,7 @@ function libraries_test_libraries_info() { // Test a multiple-parameter version callback. $libraries['example_multiple_parameter_version_callback'] = array( 'name' => 'Example multiple parameter version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', // Version 1 'version callback' => '_libraries_test_get_version', 'version arguments' => array('README.txt', '/Version (\d+)/', 5), @@ -64,7 +65,7 @@ function libraries_test_libraries_info() { // Test a top-level files property. $libraries['example_files'] = array( 'name' => 'Example files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'files' => array( 'js' => array('example_1.js'), @@ -78,7 +79,7 @@ function libraries_test_libraries_info() { // these files should be automatically loaded when the library is loaded. $libraries['example_integration_files'] = array( 'name' => 'Example integration files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'integration files' => array( 'libraries_test' => array( @@ -92,7 +93,7 @@ function libraries_test_libraries_info() { // Test version overloading. $libraries['example_versions'] = array( 'name' => 'Example versions', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '2', 'versions' => array( '1' => array( @@ -115,7 +116,7 @@ function libraries_test_libraries_info() { // Test variant detection. $libraries['example_variant_missing'] = array( 'name' => 'Example variant missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -132,7 +133,7 @@ function libraries_test_libraries_info() { $libraries['example_variant'] = array( 'name' => 'Example variant', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -150,7 +151,7 @@ function libraries_test_libraries_info() { // Test correct behaviour with multiple versions and multiple variants. $libraries['example_versions_and_variants'] = array( 'name' => 'Example versions and variants', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '2', 'versions' => array( '1' => array( @@ -206,27 +207,27 @@ function libraries_test_libraries_info() { // This library acts as a dependency for the libraries below. $libraries['example_dependency'] = array( 'name' => 'Example dependency', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1.1', 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_missing'] = array( 'name' => 'Example dependency missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'dependencies' => array('example_missing'), 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_incompatible'] = array( 'name' => 'Example dependency incompatible', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'dependencies' => array('example_dependency (>1.1)'), 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_compatible'] = array( 'name' => 'Example dependency compatible', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'dependencies' => array('example_dependency (>=1.1)'), 'files' => array('js' => array('example_1.js')), @@ -235,7 +236,7 @@ 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', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'versions' => array( '1' => array( @@ -331,8 +332,7 @@ function _libraries_test_return_version($library, $version) { * @see libraries_get_version() */ function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { - - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file; + $file = DRUPAL_ROOT . '/' . $library['path'] . '/' . $file; if (!file_exists($file)) { return; } -- 1.5.6.5 From 3983923f942e6fb592ab7a49948eae5700ecc7c6 Mon Sep 17 00:00:00 2001 From: Tobias Stckler Date: Fri, 7 Oct 2011 04:53:09 +0200 Subject: [PATCH] Move libraries_get_path() and libraries_get_directories() (and fix vs. , sorry...) --- libraries.module | 184 +++++++++++++++++++++++++++--------------------------- 1 files changed, 92 insertions(+), 92 deletions(-) diff --git a/libraries.module b/libraries.module index 4f0ab1a..6ecf600 100644 --- a/libraries.module +++ b/libraries.module @@ -13,98 +13,6 @@ function libraries_flush_caches() { } /** - * Gets the path of a library. - * - * @param $library - * An associative array containing all information about the library. - * @param $fallback - * An external URL that, if specified, is returned if the library is not found - * locally. - * - * @return - * The path to the specified library or FALSE if the library wasn't found. - * - * @ingroup libraries - */ -function libraries_get_path($library, $fallback)) { - $directories = &drupal_static(__FUNCTION__); - - if (!isset($directories)) { - $directories = libraries_get_directories(); - } - - if (!isset($directories[$library['machine name']])) { - return (isset($options['fallback']) ? $options['fallback'] : FALSE); - } - else { - return $directories[$library['machine name']]; - } -} - -/** - * Returns an array of library directories. - * - * Returns an array of library directories from the all-sites directory - * (i.e. sites/all/libraries/), the profiles directory, and site-specific - * directory (i.e. sites/somesite/libraries/). The returned array will be keyed - * by the library name. Site-specific libraries are prioritized over libraries - * in the default directories. That is, if a library with the same name appears - * in both the site-wide directory and site-specific directory, only the - * site-specific version will be listed. - * - * @return - * A list of library directories. - * - * @ingroup libraries - */ -function libraries_get_directories() { - $directory = 'libraries'; - $searchdir = array(); - $profile = drupal_get_profile(); - $config = conf_path(); - - // Similar to 'modules' and 'themes' directories in the root directory, - // certain distributions may want to place libraries into a 'libraries' - // directory in Drupal's root directory. - $searchdir[] = $directory; - - // The 'profiles' directory contains pristine collections of modules and - // themes as organized by a distribution. It is pristine in the same way - // that /modules is pristine for core; users should avoid changing anything - // there in favor of sites/all or sites/ directories. - if (file_exists("profiles/$profile/$directory")) { - $searchdir[] = "profiles/$profile/$directory"; - } - - // Always search sites/all/*. - $searchdir[] = 'sites/all/' . $directory; - - // Also search sites//*. - if (file_exists("$config/$directory")) { - $searchdir[] = "$config/$directory"; - } - - // Retrieve list of directories. - // @todo Core: Allow to scan for directories. - $directories = array(); - $nomask = array('CVS'); - foreach ($searchdir as $dir) { - if (is_dir($dir) && $handle = opendir($dir)) { - while (FALSE !== ($file = readdir($handle))) { - if (!in_array($file, $nomask) && $file[0] != '.') { - if (is_dir("$dir/$file")) { - $directories[$file] = "$dir/$file"; - } - } - } - closedir($handle); - } - } - - return $directories; -} - -/** * Looks for library info files. * * This function scans the following directories for info files: @@ -689,6 +597,98 @@ function libraries_load_files($library) { } /** + * Gets the path of a library. + * + * @param $library + * An associative array containing all information about the library. + * @param $fallback + * An external URL that, if specified, is returned if the library is not found + * locally. + * + * @return + * The path to the specified library or FALSE if the library wasn't found. + * + * @ingroup libraries + */ +function libraries_get_path($library, $fallback)) { + $directories = &drupal_static(__FUNCTION__); + + if (!isset($directories)) { + $directories = libraries_get_directories(); + } + + if (!isset($directories[$library['machine name']])) { + return (isset($fallback) ? $fallback : FALSE); + } + else { + return $directories[$library['machine name']]; + } +} + +/** + * Returns an array of library directories. + * + * Returns an array of library directories from the all-sites directory + * (i.e. sites/all/libraries/), the profiles directory, and site-specific + * directory (i.e. sites/somesite/libraries/). The returned array will be keyed + * by the library name. Site-specific libraries are prioritized over libraries + * in the default directories. That is, if a library with the same name appears + * in both the site-wide directory and site-specific directory, only the + * site-specific version will be listed. + * + * @return + * A list of library directories. + * + * @ingroup libraries + */ +function libraries_get_directories() { + $directory = 'libraries'; + $searchdir = array(); + $profile = drupal_get_profile(); + $config = conf_path(); + + // Similar to 'modules' and 'themes' directories in the root directory, + // certain distributions may want to place libraries into a 'libraries' + // directory in Drupal's root directory. + $searchdir[] = $directory; + + // The 'profiles' directory contains pristine collections of modules and + // themes as organized by a distribution. It is pristine in the same way + // that /modules is pristine for core; users should avoid changing anything + // there in favor of sites/all or sites/ directories. + if (file_exists("profiles/$profile/$directory")) { + $searchdir[] = "profiles/$profile/$directory"; + } + + // Always search sites/all/*. + $searchdir[] = 'sites/all/' . $directory; + + // Also search sites//*. + if (file_exists("$config/$directory")) { + $searchdir[] = "$config/$directory"; + } + + // Retrieve list of directories. + // @todo Core: Allow to scan for directories. + $directories = array(); + $nomask = array('CVS'); + foreach ($searchdir as $dir) { + if (is_dir($dir) && $handle = opendir($dir)) { + while (FALSE !== ($file = readdir($handle))) { + if (!in_array($file, $nomask) && $file[0] != '.') { + if (is_dir("$dir/$file")) { + $directories[$file] = "$dir/$file"; + } + } + } + closedir($handle); + } + } + + return $directories; +} + +/** * Gets the version information from an arbitrary library. * * @param $library -- 1.5.6.5