diff --git a/libraries.drush.inc b/libraries.drush.inc index 482d501..91392dc 100644 --- a/libraries.drush.inc +++ b/libraries.drush.inc @@ -1,5 +1,4 @@ 'libraries_drush_list', - 'description' => dt('Lists registered library information.'), + $commands = array(); + + $commands['libraries-list'] = array( + 'description' => dt('List of registered libraries.'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('libl', 'liblist'), ); - /**$items['libraries-download'] = array( - 'callback' => 'libraries_drush_download', - 'description' => dt('Downloads a registered library into the libraries directory for the active site.'), - 'arguments' => array( - 'name' => dt('The internal name of the registered library.'), - ), - );*/ - return $items; -} - -/** - * Implements hook_drush_help(). - */ -function libraries_drush_help($section) { - switch ($section) { - case 'drush:libraries-list': - return dt('Lists registered library information.'); - case 'drush:libraries-download': - return dt('Downloads a registered library into the libraries directory for the active site. + $commands['libraries-download'] = array( + 'description' => dt('Download registered libraries.'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('libd', 'libget'), + 'options' => array( + 'profile' => dt('Download to folder of current Drupal profile. In this case the "--uri (-l)" parameter will be ignored.'), + 'all' => dt('Download all defined, non-existent libraries. All specified arguments (library names) will be ignored.'), + ), + 'examples' => array( + 'drush libget ' => dt('Multiple arguments are allowed.'), + 'drush libget -l site.com -r /var/drupal' => dt('Drush arguments are allowed.'), + 'drush libget -l default' => dt('Download to @sites-default. If -l not specified, @sites-all will be used.', array( + '@sites-default' => 'sites/default', + '@sites-all' => 'sites/all', + )), + ), + ); -See libraries-list for a list of registered libraries.'); - } + return $commands; } /** @@ -44,7 +42,7 @@ See libraries-list for a list of registered libraries.'); * * @see drush_cache_clear_types() */ -function libraries_drush_cache_clear(&$types) { +function libraries_drush_cache_clear(array &$types) { $types['libraries'] = 'libraries_drush_invalidate_cache'; } @@ -59,112 +57,201 @@ function libraries_drush_invalidate_cache() { } /** - * Lists registered library information. + * Implements drush_COMMAND(). */ -function libraries_drush_list() { +function drush_libraries_list() { $libraries = array(); + foreach (libraries_info() as $name => $info) { $libraries[$name] = libraries_detect($name); } + ksort($libraries); if (empty($libraries)) { drush_print('There are no registered libraries.'); } - else { $rows = array(); // drush_print_table() automatically treats the first row as the header, if // $header is TRUE. - $rows[] = array(dt('Name'), dt('Status'), dt('Version'), dt('Variants'), dt('Dependencies')); - foreach ($libraries as $name => $library) { - $status = ($library['installed'] ? dt('OK') : drupal_ucfirst($library['error'])); - $version = (($library['installed'] && !empty($library['version'])) ? $library['version'] : '-'); + $rows[] = array( + dt('Name'), + dt('Status'), + dt('Module'), + dt('Version'), + dt('Variants'), + dt('Dependencies'), + ); + foreach ($libraries as $name => $library) { // Only list installed variants. $variants = array(); + foreach ($library['variants'] as $variant_name => $variant) { - if ($variant['installed']) { + if (!empty($variant['installed'])) { $variants[] = $variant_name; } } - $variants = (empty($variants) ? '-' : implode(', ', $variants)); - $dependencies = (!empty($library['dependencies']) ? implode(', ', $library['dependencies']) : '-'); - - $rows[] = array($name, $status, $version, $variants, $dependencies); + $rows[] = array( + $name, + $library['installed'] ? dt('OK') : drupal_ucfirst($library['error']), + $library['module'], + $library['installed'] || empty($library['version']) ? '-' : $library['version'], + empty($variants) ? '-' : implode(', ', $variants), + empty($library['dependencies']) ? '-' : implode(', ', $library['dependencies']), + ); } + // Make the possible values for the 'Status' column and the 'Version' header // wrap nicely. - $widths = array(0, 12, 7, 0, 0); - drush_print_table($rows, TRUE, $widths); + drush_print_table($rows, TRUE); } } /** - * Downloads a library. - * - * @param $name - * The internal name of the library to download. + * Implements drush_COMMAND(). */ -function libraries_drush_download($name) { - return; +function drush_libraries_download() { + $info = libraries_info(); - // @todo Looks wonky? - if (!drush_shell_exec('type unzip')) { - return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.')); + if (!drush_get_option('all', FALSE)) { + $info = array_intersect_key($info, array_flip(array_slice(drush_get_arguments(), 1))); } - // @todo Simply use current drush site. - $args = func_get_args(); - if ($args[0]) { - $path = $args[0]; + if (empty($info)) { + drush_log(dt('You are not specified any existing library, defined by "hook_libraries_info()".'), 'error'); } else { - $path = 'sites/all/libraries'; - } + $path = sprintf('%s/%s/libraries', drush_locate_root(), _drush_libraries_download_path()); - // Create the path if it does not exist. - if (!is_dir($path)) { - drush_op('mkdir', $path); - drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice'); - } + // Create empty directory if not exist. + if (drush_mkdir($path, TRUE)) { + $libraries = libraries_get_libraries(); + $downloaded = _drush_libraries_download($path, $info, $libraries); - // Set the directory to the download location. - $olddir = getcwd(); - chdir($path); + if (!empty($downloaded) && drush_confirm(dt('The next libraries were already downloaded: "!libs". Do you want re-download them? Old data will be erased!', array('!libs' => implode('", "', $downloaded))))) { + // Reset an array with items for downloading. + $download = array(); - $filename = basename(COLORBOX_DOWNLOAD_URI); - $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip'); + // Set affirmative context to "TRUE", because a user gave his consent + // for re-downloading. + drush_set_context('DRUSH_AFFIRMATIVE', TRUE); - // Remove any existing Colorbox plugin directory - if (is_dir($dirname)) { - drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice'); - } - // Remove any existing Colorbox plugin zip archive - if (is_file($filename)) { - drush_op('unlink', $filename); - } + foreach ($downloaded as $machine_name => $name) { + if (drush_delete_dir($libraries[$machine_name], TRUE)) { + // Allow to re-download successfully deleted libraries only. + $download[$machine_name] = $info[$machine_name]; + } + else { + drush_log(dt('Cannot remove the "@dir" directory.', array('@dir' => $libraries[$machine_name])), 'error'); + } + } - // Download the zip archive - if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) { - drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI); + _drush_libraries_download($path, $download); + } + } } +} + +/** + * Download libraries. + * + * @internal + * + * @param string $path + * Destination path. + * @param array $download + * Structured array from {@link libraries_info()} function. + * @param array $existing + * An empty or returned by {@link libraries_get_libraries()} array. + * + * @return array + * An empty array if nothing has been downloaded or filled by names of + * downloaded libraries. + */ +function _drush_libraries_download($path, array $download, array $existing = array()) { + $downloaded = array(); + + foreach ($download as $name => $library) { + $filename = basename($library['download url']); + // Path to file that will be downloaded. + $file = "$path/$filename"; + // Temporary path needed for extracting archives content + // for future moving of contents to destination folder. + $tmp = "$file-tmp"; + + if (empty($existing[$name])) { + // Keep going, if library does not exist and user allows to download it. + if (drush_confirm(dt('Are you want download the "@lib" library?', array('@lib' => $library['name'])))) { + // Download an archive into "*/libraries" folder. + if (!empty($library['download url']) && _drush_download_file($library['download url'], $file)) { + $destination = "$path/$name"; + // Re-assign variable because we need to remove directory later. + $source = $file; + + if (drush_file_is_tarball($file)) { + // Extract data into "*/libraries/LIBRARY-tmp" directory and get + // the listing of the whole structure of an archive. + $listing = drush_tarball_extract($file, $tmp, TRUE); - if (is_file($filename)) { - // Decompress the zip archive - drush_shell_exec('unzip -qq -o '. $filename); - // Remove the zip archive - drush_op('unlink', $filename); + if (!empty($listing)) { + $source = $tmp . '/' . reset($listing); + } + } + // Process single file. + elseif (drush_mkdir($destination)) { + $destination .= "/$filename"; + } + + // Move the downloaded data into a "*/libraries/LIBRARY" directory. + if (drush_copy_dir($source, $destination)) { + drush_log(dt('The "@lib" library was downloaded to "@dir".', array( + '@lib' => $library['name'], + '@dir' => "$path/$name", + )), 'success'); + } + else { + drush_log(dt('Library could not be moved from temporary folder.'), 'error'); + } + + array_map('drush_delete_dir', array($file, $tmp)); + } + else { + drush_log(dt('To download a library, the "download url" parameter shall point to file that can be downloaded.'), 'error'); + } + } + } + else { + $downloaded[$name] = $library['name']; + } } - // Set working directory back to the previous working directory. - chdir($olddir); + return $downloaded; +} - if (is_dir($path .'/'. $dirname)) { - drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success'); +/** + * Determine downloading destination. + * + * @internal + * + * @return string + * Relative path for downloading. + */ +function _drush_libraries_download_path() { + if (drush_get_option('profile', FALSE)) { + return drupal_get_path('profile', drupal_get_profile()); } - else { - drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error'); + + $uri = drush_get_option('uri'); + $path = drush_conf_path($uri); + + // If "--uri" or "-l" parameter specified, then check that configuration + // path exists or user specially chosen "default" directory. + if (!isset($path) || ('sites/default' === $path && 'default' !== $uri)) { + return drush_drupal_sitewide_directory(); } + + return $path; } diff --git a/libraries.info b/libraries.info index 439cb3c..02c65ff 100644 --- a/libraries.info +++ b/libraries.info @@ -1,6 +1,11 @@ name = Libraries description = Allows version-dependent and shared usage of external libraries. core = 7.x + ; We use hook_system_theme_info() which was added in Drupal 7.11 dependencies[] = system (>=7.11) + files[] = tests/libraries.test +files[] = tests/libraries.drush.test + +files[] = libraries.manager.inc diff --git a/libraries.install b/libraries.install index b210b98..306cb8c 100644 --- a/libraries.install +++ b/libraries.install @@ -25,3 +25,17 @@ function libraries_update_7200() { db_create_table('cache_libraries', $specs['cache_libraries']); } } + +/** + * Implements hook_modules_enabled(). + */ +function libraries_modules_enabled(array $modules) { + array_map(array(new LibrariesManager(), 'install'), $modules); +} + +/** + * Implements hook_modules_disabled(). + */ +function libraries_modules_disabled(array $modules) { + array_map(array(new LibrariesManager(), 'uninstall'), $modules); +} diff --git a/libraries.manager.inc b/libraries.manager.inc new file mode 100644 index 0000000..fa76b09 --- /dev/null +++ b/libraries.manager.inc @@ -0,0 +1,114 @@ +useModule($module) && !empty($this->download)) { + $this->download($this->download); + } + } + + /** + * Remove downloaded libraries of a module. + * + * @param string $module + * Module machine name. + */ + public function uninstall($module) { + if ($this->useModule($module)) { + // Remove module dependencies. + array_map('file_unmanaged_delete_recursive', $this->remove); + } + } + + /** + * Programmatic implementation of "drush libget". + * + * @param string[] $libraries + * Library names. + * @param array $options + * Options for "libget" command. + */ + public function download(array $libraries, array $options = array()) { + if (function_exists('drush_libraries_download')) { + array_unshift($libraries, 'libget'); + drush_set_arguments($libraries); + + foreach ($options as $name => $value) { + drush_set_option($name, $value); + } + + drush_libraries_download(); + } + } + + /** + * Set module for processing. + * + * @param string $module + * Module machine name. + * + * @return bool + * Setup state. + */ + private function useModule($module) { + // Reset variables to prevent keeping of wrong data in loops. + $this->download = array(); + $this->remove = array(); + + // The "module_invoke" will not work in "hook_uninstall", for example. + if (module_load_include('module', $module)) { + $hook = sprintf('%s_libraries_info', $module); + + if (function_exists($hook)) { + $defined = $hook(); + + if (isset($defined) && is_array($defined)) { + $existing = libraries_get_libraries(); + + $this->download = array_keys(array_diff_key($defined, $existing)); + $this->remove = array_intersect_key($existing, $defined); + + foreach (array('download' => TRUE, 'remove' => FALSE) as $prop => $condition) { + foreach ($this->$prop as $i => $name) { + if (empty($defined[$name]["auto $prop"]) && $condition) { + unset($this->{$prop}[$i]); + } + } + } + + return TRUE; + } + } + } + + return FALSE; + } + +} diff --git a/tests/libraries.drush.test b/tests/libraries.drush.test new file mode 100644 index 0000000..9974bf4 --- /dev/null +++ b/tests/libraries.drush.test @@ -0,0 +1,104 @@ + 'Drush integration', + 'group' => 'Libraries API', + 'description' => 'Testing Drush integration.', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + $this->profile = drupal_get_profile(); + + parent::setUp(self::TEST_MODULE); + + // Allow to run these tests only by Drush. Otherwise they are will not work + // because SimpleTest renames database connection and Drush cannot use it. + // Also, Drush functionality has been used. + $this->setup = function_exists('drush_mkdir'); + $this->manager = new LibrariesManager(); + $this->libraries = array_keys(module_invoke(self::TEST_MODULE, 'libraries_info')); + } + + /** + * {@inheritdoc} + */ + public function tearDown() { + parent::tearDown(); + $this->manager->uninstall(self::TEST_MODULE); + } + + /** + * Testing manual downloading. + * + * @param array $options + * Options for "libget" command. + */ + public function testManualDownloading(array $options = array()) { + foreach ($this->libraries as $library) { + $info = libraries_detect($library); + $this->assertFalse($info['installed']); + + $this->manager->download([$library], $options); + drupal_flush_all_caches(); + + $info = libraries_detect($library); + $this->assertTrue($info['installed']); + } + } + + /** + * Testing manual downloading to sub-site. + * + * @param string $site + * Sub-site machine name. + */ + public function testDownloadingToSubSite($site = 'testing') { + $path = "sites/$site"; + + $this->assertTrue(drush_mkdir($path)); + $this->assertTrue(file_unmanaged_copy('sites/default/settings.php', $path)); + + // @see conf_path() + // @see libraries_get_libraries() + $conf =& drupal_static('conf_path'); + $conf = $path; + + $this->testManualDownloading(array('uri' => $site)); + $this->assertTrue(drush_delete_dir($path)); + } + + /** + * Testing manual downloading to profile. + */ + public function testDownloadingToProfile() { + $this->testManualDownloading(array('profile' => TRUE)); + } + +} diff --git a/tests/modules/libraries_test_download/libraries_test_download.info b/tests/modules/libraries_test_download/libraries_test_download.info new file mode 100644 index 0000000..d791567 --- /dev/null +++ b/tests/modules/libraries_test_download/libraries_test_download.info @@ -0,0 +1,6 @@ +name = Testing Libraries Drush integration +package = Testing +hidden = TRUE +core = 7.x + +dependencies[] = libraries diff --git a/tests/modules/libraries_test_download/libraries_test_download.module b/tests/modules/libraries_test_download/libraries_test_download.module new file mode 100644 index 0000000..281752a --- /dev/null +++ b/tests/modules/libraries_test_download/libraries_test_download.module @@ -0,0 +1,32 @@ + 'example_download', + 'vendor url' => 'http://parsedown.org/', + 'download url' => 'https://github.com/BR0kEN-/parsedown/archive/master.zip', + // 'auto remove' => TRUE, + // 'auto download' => TRUE, + 'version arguments' => array( + 'file' => 'Parsedown.php', + 'lines' => 25, + 'pattern' => "/const version = '(\d\.\d\.\d)'/i", + ), + 'files' => array( + 'php' => array( + 'Parsedown.php', + ), + ), + ); + + return $libraries; +}