From 1e090476a16151e7d7d09b9cc4704528d4c49326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20St=C3=B6ckler?= <tobiasstoeckler@googlemail.com> Date: Tue, 24 May 2016 12:59:52 +0200 Subject: [PATCH] by tstoeckler: Implement version detection for libraries --- libraries.services.yml | 3 + src/Annotation/VersionDetector.php | 14 ++ src/ExternalLibrary/Asset/AssetLibrary.php | 14 +- .../Asset/LocalRemoteAssetTrait.php | 4 - .../Asset/SingleAssetLibraryTrait.php | 1 + .../LibraryNotInstalledException.php | 2 +- .../UnknownLibraryVersionException.php | 38 ++++ src/ExternalLibrary/LibraryInterface.php | 8 - src/ExternalLibrary/LibraryTrait.php | 13 -- .../Version/VersionDetectorInterface.php | 23 +++ .../Version/VersionDetectorManager.php | 36 ++++ .../Version/VersionedLibraryInterface.php | 55 +++++ .../Version/VersionedLibraryTrait.php | 85 ++++++++ .../LibraryType/AssetLibraryType.php | 22 +- .../LibraryType/PhpFileLibraryType.php | 20 +- .../VersionDetector/LinePatternDetector.php | 86 ++++++++ .../VersionDetector/StaticDetector.php | 45 ++++ .../test_asset_library.yml | 4 + .../Asset/AssetLibraryTest.php | 19 +- .../PhpFile/PhpFileLibraryTest.php | 5 - .../TestLibraryFilesStream.php | 5 - tests/src/Kernel/LibraryKernelTestBase.php | 5 - .../LinePatternDetectorTest.php | 195 ++++++++++++++++++ 23 files changed, 642 insertions(+), 60 deletions(-) create mode 100644 src/Annotation/VersionDetector.php create mode 100644 src/ExternalLibrary/Exception/UnknownLibraryVersionException.php create mode 100644 src/ExternalLibrary/Version/VersionDetectorInterface.php create mode 100644 src/ExternalLibrary/Version/VersionDetectorManager.php create mode 100644 src/ExternalLibrary/Version/VersionedLibraryInterface.php create mode 100644 src/ExternalLibrary/Version/VersionedLibraryTrait.php create mode 100644 src/Plugin/libraries/VersionDetector/LinePatternDetector.php create mode 100644 src/Plugin/libraries/VersionDetector/StaticDetector.php create mode 100644 tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php diff --git a/libraries.services.yml b/libraries.services.yml index 3f463c8..1ee8a6f 100644 --- a/libraries.services.yml +++ b/libraries.services.yml @@ -15,6 +15,9 @@ services: plugin.manager.libraries.locator: class: Drupal\libraries\ExternalLibrary\Local\LocatorManager parent: default_plugin_manager + plugin.manager.libraries.version_detector: + class: Drupal\libraries\ExternalLibrary\Version\VersionDetectorManager + parent: default_plugin_manager libraries.extension_handler: class: Drupal\libraries\Extension\ExtensionHandler diff --git a/src/Annotation/VersionDetector.php b/src/Annotation/VersionDetector.php new file mode 100644 index 0000000..a664f99 --- /dev/null +++ b/src/Annotation/VersionDetector.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\libraries\Annotation; + +use Drupal\Component\Annotation\PluginID; + +/** + * Provides an annotation class for version detector plugins. + * + * @Annotation + */ +class VersionDetector extends PluginID { + +} diff --git a/src/ExternalLibrary/Asset/AssetLibrary.php b/src/ExternalLibrary/Asset/AssetLibrary.php index dedb5a9..dcfd312 100644 --- a/src/ExternalLibrary/Asset/AssetLibrary.php +++ b/src/ExternalLibrary/Asset/AssetLibrary.php @@ -7,23 +7,27 @@ namespace Drupal\libraries\ExternalLibrary\Asset; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; use Drupal\libraries\ExternalLibrary\LibraryTrait; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface; use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryTrait; /** * Provides a base asset library implementation. */ -class AssetLibrary implements AssetLibraryInterface, LocalLibraryInterface, RemoteLibraryInterface { +class AssetLibrary implements AssetLibraryInterface, VersionedLibraryInterface, LocalLibraryInterface, RemoteLibraryInterface { use LibraryTrait, LocalLibraryTrait, RemoteLibraryTrait, SingleAssetLibraryTrait, - LocalRemoteAssetTrait + LocalRemoteAssetTrait, + VersionedLibraryTrait ; /** @@ -36,7 +40,11 @@ class AssetLibrary implements AssetLibraryInterface, LocalLibraryInterface, Remo */ public function __construct($id, array $definition) { $this->id = (string) $id; - // @todo Split this into proper properties. + // @todo Split this into a generic trait. + if (isset($definition['version_detector'])) { + // @todo Validate the sub-keys. + $this->versionDetector = $definition['version_detector']; + } if (isset($definition['remote_url'])) { $this->remoteUrl = $definition['remote_url']; } diff --git a/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php b/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php index dc57c04..ee3e4ac 100644 --- a/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php +++ b/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php @@ -42,10 +42,6 @@ trait LocalRemoteAssetTrait { /** * Gets the locator of this library using the locator factory. * - * Because determining the installation status and library path of a library - * is not specific to any library or even any library type, this logic is - * offloaded to separate locator objects. - * * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory * * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface diff --git a/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php b/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php index 2dca415..aa890de 100644 --- a/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php +++ b/src/ExternalLibrary/Asset/SingleAssetLibraryTrait.php @@ -17,6 +17,7 @@ use Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException * * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface */ trait SingleAssetLibraryTrait { diff --git a/src/ExternalLibrary/Exception/LibraryNotInstalledException.php b/src/ExternalLibrary/Exception/LibraryNotInstalledException.php index ea44864..5d3bc6a 100644 --- a/src/ExternalLibrary/Exception/LibraryNotInstalledException.php +++ b/src/ExternalLibrary/Exception/LibraryNotInstalledException.php @@ -12,7 +12,7 @@ use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait; use Exception; /** - * Provides an exception for a library definition that cannot be found. + * Provides an exception for a library that is not installed. */ class LibraryNotInstalledException extends \RuntimeException { diff --git a/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php b/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php new file mode 100644 index 0000000..72bd153 --- /dev/null +++ b/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Provides an exception for libraries whose version has not been detected. + */ +class UnknownLibraryVersionException extends \RuntimeException { + + use LibraryAccessorTrait; + + /** + * Constructs a library exception. + * + * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library + * The library. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + VersionedLibraryInterface $library, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->library = $library; + $message = $message ?: "The version of library '{$this->library->getId()}' could not be detected."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/src/ExternalLibrary/LibraryInterface.php b/src/ExternalLibrary/LibraryInterface.php index 024bc23..c3574a5 100644 --- a/src/ExternalLibrary/LibraryInterface.php +++ b/src/ExternalLibrary/LibraryInterface.php @@ -23,14 +23,6 @@ interface LibraryInterface { */ public function getId(); - /** - * Returns the currently installed version of the library. - * - * @return string - * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. - */ - public function getVersion(); - /** * Returns the libraries dependencies, if any. * diff --git a/src/ExternalLibrary/LibraryTrait.php b/src/ExternalLibrary/LibraryTrait.php index f28fd91..836a29d 100644 --- a/src/ExternalLibrary/LibraryTrait.php +++ b/src/ExternalLibrary/LibraryTrait.php @@ -16,19 +16,6 @@ trait LibraryTrait { use IdAccessorTrait; - /** - * Returns the currently installed version of the library. - * - * @return string - * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. - * - * @see \Drupal\libraries\ExternalLibrary\LibraryInterface::getVersion() - */ - public function getVersion() { - // @todo Turn into something useful and split into some other trait. - return '1.0'; - } - /** * Returns the libraries dependencies, if any. * diff --git a/src/ExternalLibrary/Version/VersionDetectorInterface.php b/src/ExternalLibrary/Version/VersionDetectorInterface.php new file mode 100644 index 0000000..da42b33 --- /dev/null +++ b/src/ExternalLibrary/Version/VersionDetectorInterface.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +/** + * Provides an interface for version detectors. + */ +interface VersionDetectorInterface { + + /** + * Detects the version of a library. + * + * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library + * The library whose version to detect. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @todo Provide a mechanism for version detectors to provide a reason for + * failing. + */ + public function detectVersion(VersionedLibraryInterface $library); + +} diff --git a/src/ExternalLibrary/Version/VersionDetectorManager.php b/src/ExternalLibrary/Version/VersionDetectorManager.php new file mode 100644 index 0000000..d7160b0 --- /dev/null +++ b/src/ExternalLibrary/Version/VersionDetectorManager.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\libraries\Annotation\VersionDetector; +use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface; + +/** + * Provides a plugin manager for library version detector plugins. + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ +class VersionDetectorManager extends DefaultPluginManager { + + /** + * Constructs a version detector manager. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to invoke the alter hook with. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/libraries/VersionDetector', $namespaces, $module_handler, VersionDetectorInterface::class, VersionDetector::class); + // @todo Document this hook. + $this->alterInfo('libraries_version_detector_info'); + $this->setCacheBackend($cache_backend, 'libraries_version_detector_info'); + } + +} diff --git a/src/ExternalLibrary/Version/VersionedLibraryInterface.php b/src/ExternalLibrary/Version/VersionedLibraryInterface.php new file mode 100644 index 0000000..d9b8df4 --- /dev/null +++ b/src/ExternalLibrary/Version/VersionedLibraryInterface.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * Provides an interface for versioned libraries. + * + * Version detection and negotiation is a key aspect of Libraries API's + * functionality so every type of library should implement this interface. + */ +interface VersionedLibraryInterface extends LibraryInterface { + + /** + * Gets the version of the library. + * + * @return string + * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion() + */ + public function getVersion(); + + /** + * Sets the version of the library. + * + * @param string $version + * The version of the library. + * + * @reutrn $this + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion() + */ + public function setVersion($version); + + /** + * Gets the version detector of this library using the detector factory. + * + * Because determining the installation version of a library is not specific + * to any library or even any library type, this logic is offloaded to + * separate detector objects. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * + * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ + public function getVersionDetector(FactoryInterface $detector_factory); + +} diff --git a/src/ExternalLibrary/Version/VersionedLibraryTrait.php b/src/ExternalLibrary/Version/VersionedLibraryTrait.php new file mode 100644 index 0000000..2404a10 --- /dev/null +++ b/src/ExternalLibrary/Version/VersionedLibraryTrait.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; + +/** + * Provides a trait for versioned libraries. + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface + */ +trait VersionedLibraryTrait { + + /** + * The library version. + * + * @var string + */ + protected $version; + + /** + * Information about the version detector to use fo rthis library. + * + * Contains the following keys: + * id: The plugin ID of the version detector. + * configuration: The plugin configuration of the version detector. + * + * @var array + */ + protected $versionDetector = [ + 'id' => NULL, + 'configuration' => [], + ]; + + /** + * Gets the version of the library. + * + * @return string + * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion() + */ + public function getVersion() { + if (!isset($this->version)) { + throw new UnknownLibraryVersionException($this); + } + return $this->version; + } + + /** + * Sets the version of the library. + * + * @param string $version + * The version of the library. + * + * @return $this + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion() + */ + public function setVersion($version) { + $this->version = (string) $version; + return $this; + } + + /** + * Gets the version detector of this library using the detector factory. + * + * Because determining the installation version of a library is not specific + * to any library or even any library type, this logic is offloaded to + * separate detector objects. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * + * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ + public function getVersionDetector(FactoryInterface $detector_factory) { + return $detector_factory->createInstance($this->versionDetector['id'], $this->versionDetector['configuration']); + } + +} diff --git a/src/Plugin/libraries/LibraryType/AssetLibraryType.php b/src/Plugin/libraries/LibraryType/AssetLibraryType.php index 9c1b262..ac5c3c9 100644 --- a/src/Plugin/libraries/LibraryType/AssetLibraryType.php +++ b/src/Plugin/libraries/LibraryType/AssetLibraryType.php @@ -15,6 +15,7 @@ use Drupal\libraries\ExternalLibrary\LibraryType\LibraryCreationListenerInterfac use Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface; use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -35,6 +36,13 @@ class AssetLibraryType implements */ protected $locatorFactory; + /** + * The version detector factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $detectorFactory; + /** * Constructs the asset library type. * @@ -42,17 +50,24 @@ class AssetLibraryType implements * The plugin ID taken from the class annotation. * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory * The locator factory. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * The version detector factory. */ - public function __construct($plugin_id, FactoryInterface $locator_factory) { + public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory) { $this->id = $plugin_id; $this->locatorFactory = $locator_factory; + $this->detectorFactory = $detector_factory; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static($plugin_id, $container->get('plugin.manager.libraries.locator')); + return new static( + $plugin_id, + $container->get('plugin.manager.libraries.locator'), + $container->get('plugin.manager.libraries.version_detector') + ); } /** @@ -71,6 +86,9 @@ class AssetLibraryType implements if ($library instanceof LocalLibraryInterface) { $library->getLocator($this->locatorFactory)->locate($library); } + if ($library instanceof VersionedLibraryInterface) { + $library->getVersionDetector($this->detectorFactory)->detectVersion($library); + } } } diff --git a/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php b/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php index dced4c3..821bf6a 100644 --- a/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php +++ b/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php @@ -16,6 +16,7 @@ use Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface; use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary; use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface; use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -37,6 +38,13 @@ class PhpFileLibraryType implements */ protected $locatorFactory; + /** + * The version detector factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $detectorFactory; + /** * The PHP file loader. * @@ -51,12 +59,15 @@ class PhpFileLibraryType implements * The plugin ID taken from the class annotation. * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory * The locator factory. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * The version detector factory. * @param \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface $php_file_loader * The PHP file loader. */ - public function __construct($plugin_id, FactoryInterface $locator_factory, PhpFileLoaderInterface $php_file_loader) { + public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory, PhpFileLoaderInterface $php_file_loader) { $this->id = $plugin_id; $this->locatorFactory = $locator_factory; + $this->detectorFactory = $detector_factory; $this->phpFileLoader = $php_file_loader; } @@ -67,6 +78,7 @@ class PhpFileLibraryType implements return new static( $plugin_id, $container->get('plugin.manager.libraries.locator'), + $container->get('plugin.manager.libraries.version_detector'), $container->get('libraries.php_file_loader') ); } @@ -82,8 +94,11 @@ class PhpFileLibraryType implements * {@inheritdoc} */ public function onLibraryCreate(LibraryInterface $library) { - /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface $library */ + /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface|\Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library */ $library->getLocator($this->locatorFactory)->locate($library); + if ($library instanceof VersionedLibraryInterface) { + $library->getVersionDetector($this->detectorFactory)->detectVersion($library); + } } /** @@ -94,6 +109,7 @@ class PhpFileLibraryType implements foreach ($library->getPhpFiles() as $file) { $this->phpFileLoader->load($file); } + } } diff --git a/src/Plugin/libraries/VersionDetector/LinePatternDetector.php b/src/Plugin/libraries/VersionDetector/LinePatternDetector.php new file mode 100644 index 0000000..9a8ece7 --- /dev/null +++ b/src/Plugin/libraries/VersionDetector/LinePatternDetector.php @@ -0,0 +1,86 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\VersionDetector; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginBase; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Detects the version by matching lines in a file against a specified pattern. + * + * This version detector can be used if the library version is denoted in a + * particular format in a changelog or readme file, for example. + * + * @VersionDetector("line_pattern") + */ +class LinePatternDetector extends PluginBase implements VersionDetectorInterface, ContainerFactoryPluginInterface { + + /** + * The app root. + * + * @var string + */ + protected $appRoot; + + /** + * Constructs a line pattern version detector. + * + * @param array $configuration + * @param string $plugin_id + * @param array $plugin_definition + * @param string $app_root + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, $app_root) { + $configuration += [ + 'file' => '', + 'pattern' => '', + 'lines' => 20, + 'columns' => 200, + ]; + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->appRoot = $app_root; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('app.root') + ); + } + + /** + * {@inheritdoc} + */ + public function detectVersion(VersionedLibraryInterface $library) { + if (!($library instanceof LocalLibraryInterface)) { + throw new UnknownLibraryVersionException($library); + } + + $filepath = $this->appRoot . '/' . $library->getLocalPath() . '/' . $this->configuration['file']; + if (!file_exists($filepath)) { + throw new UnknownLibraryVersionException($library); + } + + $file = fopen($filepath, 'r'); + $lines = $this->configuration['lines']; + while ($lines && $line = fgets($file, $this->configuration['columns'])) { + if (preg_match($this->configuration['pattern'], $line, $version)) { + fclose($file); + $library->setVersion($version[1]); + return; + } + $lines--; + } + fclose($file); + } + +} diff --git a/src/Plugin/libraries/VersionDetector/StaticDetector.php b/src/Plugin/libraries/VersionDetector/StaticDetector.php new file mode 100644 index 0000000..2a4258d --- /dev/null +++ b/src/Plugin/libraries/VersionDetector/StaticDetector.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\VersionDetector; +use Drupal\Core\Plugin\PluginBase; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; +use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Detects the version by returning a static string. + * + * As this does not perform any actual detection and, thus, circumvents any + * negotiation of versions by Libraries API it should only be used for testing + * or when the version of a library cannot be determined from the source code + * itself. + * + * @VersionDetector("static") + */ +class StaticDetector extends PluginBase implements VersionDetectorInterface { + + /** + * Constructs a static version detector. + * + * @param array $configuration + * @param string $plugin_id + * @param array $plugin_definition + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition) { + $configuration += [ + 'version' => NULL, + ]; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function detectVersion(VersionedLibraryInterface $library) { + if (!isset($this->configuration['version'])) { + throw new UnknownLibraryVersionException($library); + } + $library->setVersion($this->configuration['version']); + } + +} diff --git a/tests/library_definitions/test_asset_library.yml b/tests/library_definitions/test_asset_library.yml index 3c6e1a1..4c712e6 100644 --- a/tests/library_definitions/test_asset_library.yml +++ b/tests/library_definitions/test_asset_library.yml @@ -1,4 +1,8 @@ type: asset +version_detector: + id: static + configuration: + version: '1.0.0' remote_url: http://example.com css: base: diff --git a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php index 851aa3a..9b840d4 100644 --- a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Contains \Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset\AssetLibraryTest. - */ - namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; use Drupal\libraries\ExternalLibrary\Asset\AssetLibrary; @@ -55,7 +50,7 @@ class AssetLibraryTest extends LibraryKernelTestBase { $this->assertEquals('test_asset_library', $library->getId()); $expected = ['test_asset_library' => [ - 'version' => 1.0, + 'version' => '1.0.0', 'css' => ['base' => ['http://example.com/example.css' => []]], 'js' => ['http://example.com/example.js' => []], 'dependencies' => [], @@ -84,19 +79,19 @@ class AssetLibraryTest extends LibraryKernelTestBase { public function testAssetLibraryRemote() { $library = $this->libraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); $expected = [ - 'version' => '1.0', + 'version' => '1.0.0', 'css' => [[ 'weight' => -200, 'group' => 0, 'type' => 'external', 'data' => 'http://example.com/example.css', - 'version' => '1.0', + 'version' => '1.0.0', ]], 'js' => [[ 'group' => -100, 'type' => 'external', 'data' => 'http://example.com/example.js', - 'version' => '1.0', + 'version' => '1.0.0', ]], 'dependencies' => [], 'license' => [ @@ -120,19 +115,19 @@ class AssetLibraryTest extends LibraryKernelTestBase { $this->libraryDiscovery->clearCachedDefinitions(); $library = $this->libraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); $expected = [ - 'version' => '1.0', + 'version' => '1.0.0', 'css' => [[ 'weight' => -200, 'group' => 0, 'type' => 'file', 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.css', - 'version' => '1.0', + 'version' => '1.0.0', ]], 'js' => [[ 'group' => -100, 'type' => 'file', 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.js', - 'version' => '1.0', + 'version' => '1.0.0', 'minified' => FALSE, ]], 'dependencies' => [], diff --git a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php index 7897472..9e5d1e8 100644 --- a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Contains \Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile\PhpFileLibraryTest. - */ - namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile; use Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException; diff --git a/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php b/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php index c18faea..fd29e92 100644 --- a/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php +++ b/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Contains \Drupal\Tests\libraries\Kernel\ExternalLibrary\TestPhpLibraryFilesStream. - */ - namespace Drupal\Tests\libraries\Kernel\ExternalLibrary; use Drupal\Core\Extension\ModuleHandlerInterface; diff --git a/tests/src/Kernel/LibraryKernelTestBase.php b/tests/src/Kernel/LibraryKernelTestBase.php index 32e5f85..6b5cc96 100644 --- a/tests/src/Kernel/LibraryKernelTestBase.php +++ b/tests/src/Kernel/LibraryKernelTestBase.php @@ -1,10 +1,5 @@ <?php -/** - * @file - * Contains \Drupal\Tests\libraries\Kernel\LibraryKernelTestBase. - */ - namespace Drupal\Tests\libraries\Kernel; use Drupal\KernelTests\KernelTestBase; diff --git a/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php b/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php new file mode 100644 index 0000000..0d1cdd3 --- /dev/null +++ b/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php @@ -0,0 +1,195 @@ +<?php + +namespace Drupal\Tests\libraries\Unit\Plugin\libraries\VersionDetector; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector; +use Drupal\Tests\UnitTestCase; +use org\bovigo\vfs\vfsStream; + +/** + * Tests the line pattern version detector. + * + * @coversDefaultClass \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + */ +class LinePatternDetectorTest extends UnitTestCase { + + protected $libraryId = 'test_library'; + + /** + * Tests that version detection fails for a non-local library. + * + * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @covers ::detectVersion + */ + public function testDetectVersionNonLocal() { + $library = $this->prophesize(VersionedLibraryInterface::class); + $detector = $this->setupDetector(); + $detector->detectVersion($library->reveal()); + } + + /** + * Tests that version detection fails for a missing file. + * + * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @covers ::detectVersion + */ + public function testDetectVersionMissingFile() { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector(['file' => 'CHANGELOG.txt']); + $detector->detectVersion($library->reveal()); + } + + /** + * Tests that version detection fails without a version in the file. + * + * @dataProvider providerTestDetectVersionNoVersion + * + * @covers ::detectVersion + */ + public function testDetectVersionNoVersion($configuration, $file_contents) { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector($configuration); + $this->setupFile($configuration['file'], $file_contents); + + $library->setVersion()->shouldNotBeCalled(); + $detector->detectVersion($library->reveal()); + } + + /** + * @return array + */ + public function providerTestDetectVersionNoVersion() { + $test_cases = []; + + $configuration = [ + 'file' => 'CHANGELOG.txt', + 'pattern' => '/@version (\d+\.\d+\.\d+)/' + ]; + + $test_cases['empty_file'] = [$configuration, '']; + + $test_cases['no_version'] = [$configuration, <<<EOF +This is a file with +multiple lines that does +not contain a version. +EOF + ]; + + $configuration['lines'] = 3; + $test_cases['long_file'] = [$configuration, <<<EOF +This is a file that +contains the version after +the maximum number of lines +to test has been surpassed. + +@version 1.2.3 +EOF + ]; + + $configuration['columns'] = 10; + // @todo Document why this is necessary. + $configuration['lines'] = 2; + $test_cases['long_column'] = [$configuration, <<<EOF +This is a file that contains the version after +the maximum number of columns to test has been surpassed. @version 1.2.3 +EOF + ]; + + return $test_cases; + } + + /** + * Tests that version detection succeeds with a version in the file. + * + * @dataProvider providerTestDetectVersion + * + * @covers ::detectVersion + */ + public function testDetectVersion($configuration, $file_contents, $version) { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector($configuration); + $this->setupFile($configuration['file'], $file_contents); + + $library->setVersion($version)->shouldBeCalled(); + $detector->detectVersion($library->reveal()); + } + + /** + * @return array + */ + public function providerTestDetectVersion() { + $test_cases = []; + + $configuration = [ + 'file' => 'CHANGELOG.txt', + 'pattern' => '/@version (\d+\.\d+\.\d+)/' + ]; + $version = '1.2.3'; + + $test_cases['version'] = [$configuration, <<<EOF +This a file with a version + +@version $version +EOF + , $version]; + + return $test_cases; + } + + /** + * Sets up the library prophecy and returns it. + * + * @return \Prophecy\Prophecy\ObjectProphecy + */ + protected function setupLibrary() { + $library = $this->prophesize(VersionedLibraryInterface::class); + $library->willImplement(LocalLibraryInterface::class); + $library->getId()->willReturn($this->libraryId); + $library->getLocalPath()->willReturn('libraries/' . $this->libraryId); + return $library; + } + + /** + * Sets up the version detector for testing and returns it. + * + * @param array $configuration + * The plugin configuration to set the version detector up with. + * + * @return \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + * The line pattern version detector to test. + */ + protected function setupDetector(array $configuration = []) { + $app_root = 'root'; + vfsStream::setup($app_root); + + $plugin_id = 'line_pattern'; + $plugin_definition = [ + 'id' => $plugin_id, + 'class' => LinePatternDetector::class, + 'provider' => 'libraries', + ]; + return new LinePatternDetector($configuration, $plugin_id, $plugin_definition, 'vfs://' . $app_root); + } + + /** + * @param $file + * @param $file_contents + */ + protected function setupFile($file, $file_contents) { + vfsStream::create([ + 'libraries' => [ + $this->libraryId => [ + $file => $file_contents, + ], + ], + ]); + } + +} -- GitLab