Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple pattern definition files #15 #22

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/Plugin/Discovery/UiPatternsDiscovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Drupal\ui_patterns\Plugin\Discovery;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Plugin\Discovery\YamlDiscovery as PluginYamlDiscovery;

/**
* Allows ui_patterns.yml files to define pattern plugin definitions.
*/
class UiPatternsDiscovery extends PluginYamlDiscovery {

/**
* Constructs an UiPatternsDiscovery object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* ModuleHanderInterface.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* ThemeHandlerInterface.
*/
public function __construct(ModuleHandlerInterface $moduleHandler, ThemeHandlerInterface $themeHandler) {
parent::__construct('ui_patterns', array());

// Use our discovery instead of the one set in the parent class.
// Create a list of all directories to scan. This includes all module
// directories and directories of the default theme and all of its possible
// base themes.
$directories = $this->getDefaultAndBaseThemesDirectories($themeHandler) + $moduleHandler->getModuleDirectories();

$this->setYamlDiscovery(new YamlDiscovery('ui_patterns', $directories));
}

/**
* Sets the YamlDiscovery.
*
* @param \Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery $discovery
* YamlDiscovery.
*/
public function setYamlDiscovery(YamlDiscovery $discovery) {
$this->discovery = $discovery;
}

/**
* Returns an array containing theme directory paths.
*
* Returns the directory paths of the default theme and all its possible base
* themes.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* ThemeHandlerInterface.
*
* @return array
* An array containing directory paths.
*/
protected function getDefaultAndBaseThemesDirectories(ThemeHandlerInterface $themeHandler) {
$defaultTheme = $themeHandler->getDefault();
$baseThemes = $themeHandler->getBaseThemes($themeHandler->listInfo(), $defaultTheme);
$themeDirectories = $themeHandler->getThemeDirectories();

$directories = array();
$directories[$defaultTheme] = $themeDirectories[$defaultTheme];
foreach ($baseThemes as $name => $theme) {
$directories[$name] = $themeDirectories[$name];
}

return $directories;
}

}
106 changes: 106 additions & 0 deletions src/Plugin/Discovery/YamlDiscovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace Drupal\ui_patterns\Plugin\Discovery;

use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Core\Discovery\YamlDiscovery as CoreYamlDiscovery;
use Drupal\Core\Site\Settings;

/**
* Provides recursive discovery for YAML files.
*
* Discovered data is keyed by provider (module/theme). If multiple YAML files
* are discovered from a provider their data will be merged.
*/
class YamlDiscovery extends CoreYamlDiscovery {

/**
* {@inheritdoc}
*/
public function findAll() {
$files = $this->findFiles();
$processFiles = array_keys($files);

$file_cache = FileCacheFactory::get('yaml_discovery:' . $this->name);

// Try to load from the file cache first.
foreach ($processFiles as $i => $file) {
$data = $file_cache->get($file);
if ($data) {
$files[$file]['data'] = $data;
unset($processFiles[$i]);
}
}

// If there are files left that were not returned from the cache, load and
// parse them now.
if (!empty($processFiles)) {
foreach ($processFiles as $file) {
// If a file is empty or its contents are commented out, return an empty
// array instead of NULL for type consistency.
$files[$file]['data'] = $this->decode($file);
$file_cache->set($file, $files[$file]['data']);
}
}

// Create array keyed by provider with merged data as values.
$all = array();
foreach ($files as $file => $value) {
$provider = $value['provider'];
if (isset($all[$provider])) {
$all[$provider] += $value['data'];
}
else {
$all[$provider] = $value['data'];
}
}
return $all;
}

/**
* Returns an array with file paths as keys.
*
* @return array
* An array with file paths as keys.
*/
protected function findFiles() {
// Add options for file scan.
$options = array('nomask' => $this->getNomask());

// Recursively scan the directories for definition files.
$files = array();
foreach ($this->directories as $provider => $directory) {
$found = $this->fileScanDirectory($directory, '/\.' . $this->name . '\.yml$/', $options);
foreach (array_keys($found) as $file) {
$files[$file] = array('provider' => $provider);
}
}
return $files;
}

/**
* Wrapper method for global function call.
*
* @see file.inc
*/
public function fileScanDirectory($dir, $mask, $options = array(), $depth = 0) {
return file_scan_directory($dir, $mask, $options, $depth);
}

/**
* Returns a regular expression for directories to be excluded in a file scan.
*
* @return string
* Regular expression.
*/
protected function getNomask() {
$ignoreDirs = Settings::get('file_scan_ignore_directories', []);
// We add 'tests' directory to the ones found in settings.
$ignoreDirs[] = 'tests';
array_walk($ignoreDirs, function (&$value) {
$value = preg_quote($value, '/');
});
return '/^' . implode('|', $ignoreDirs) . '$/';
}

}
17 changes: 13 additions & 4 deletions src/UiPatternsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Theme\ThemeManager;
use Drupal\ui_patterns\Plugin\Discovery\UiPatternsDiscovery;
use Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery;

/**
* Provides the default ui_patterns manager.
Expand Down Expand Up @@ -131,15 +132,23 @@ protected function providerExists($provider) {
*/
protected function getDiscovery() {
if (!isset($this->discovery)) {
$directories = $this->themeHandler->getThemeDirectories() + $this->moduleHandler->getModuleDirectories();
$this->discovery = new YamlDiscovery('ui_patterns', $directories);
$this->discovery = new UiPatternsDiscovery($this->moduleHandler, $this->themeHandler);
$this->discovery->addTranslatableProperty('label', 'label_context');
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
}

return $this->discovery;
}

/**
* Sets the YamlDiscovery.
*
* @param \Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery $yamlDiscovery
* YamlDiscovery.
*/
public function setYamlDiscovery(YamlDiscovery $yamlDiscovery) {
$this->getDiscovery()->setYamlDiscovery($yamlDiscovery);
}

/**
* Validate plugin definition.
*
Expand Down
95 changes: 88 additions & 7 deletions tests/src/PhpUnit/AbstractUiPatternsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ abstract class AbstractUiPatternsTest extends TestCase {
* Full test extension path.
*/
protected function getExtensionsPath($name) {
return realpath(dirname(__FILE__) . '/../../../tests/target/custom/' . $name);
switch ($name) {
case 'bootstrap':
return realpath(dirname(__FILE__) . '/../../../tests/target/drupal/themes/contrib/bootstrap');

default:
return realpath(dirname(__FILE__) . '/../../../tests/target/custom/' . $name . '/');
}
}

/**
Expand All @@ -33,9 +39,7 @@ protected function getExtensionsPath($name) {
*/
protected function getModuleHandlerMock() {
$module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface');
$module_handler->method('getModuleDirectories')->willReturn([
'ui_patterns_test' => $this->getExtensionsPath('ui_patterns_test'),
]);
$module_handler->method('getModuleDirectories')->willReturn($this->getModuleDirectoriesMock());

$extension = $this->getExtensionMock();
$module_handler->method('getModule')->willReturn($extension);
Expand Down Expand Up @@ -85,10 +89,13 @@ protected function getThemeHandlerMock() {
$theme_handler = $this->getMockBuilder('Drupal\Core\Extension\ThemeHandlerInterface')
->disableOriginalConstructor()
->getMock();
$theme_handler->method('getThemeDirectories')->willReturn([
'ui_patterns_test_theme' => $this->getExtensionsPath('ui_patterns_test_theme'),
]);
$theme_handler->method('getThemeDirectories')->willReturn($this->getDefaultAndBaseThemesDirectoriesMock());
$theme_handler->method('themeExists')->willReturn(TRUE);
$theme_handler->method('getDefault')->willReturn('ui_patterns_test_theme');
$theme_handler->method('listInfo')->willReturn([]);
$theme_handler->method('getBaseThemes')->willReturn([
'bootstrap' => new \stdClass(),
]);

/** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
return $theme_handler;
Expand All @@ -109,4 +116,78 @@ protected function getThemeManagerMock() {
return $theme_manager;
}

/**
* Get YamlDiscovery mock.
*
* @return \Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery
* YamlDiscovery mock.
*/
protected function getYamlDiscoveryMock() {
$discovery = $this->getMockBuilder('Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery')
->setConstructorArgs([
'ui_patterns',
$this->getModuleDirectoriesMock() + $this->getDefaultAndBaseThemesDirectoriesMock(),
])
->setMethods(array('fileScanDirectory'))
->getMock();
$discovery->method('fileScanDirectory')
->willReturnCallback(array($this, 'fileScanDirectoryMock'));

/** @var \Drupal\ui_patterns\Plugin\Discovery\YamlDiscovery $discovery */
return $discovery;
}

/**
* ModuleHandlerInterface::getModuleDirectories method mock.
*
* @return array
* Array with module names as keys and full paths as values.
*/
protected function getModuleDirectoriesMock() {
$directories = array(
'ui_patterns_test' => $this->getExtensionsPath('ui_patterns_test'),
);
return $directories;
}

/**
* UiPatternsDiscovery::getDefaultAndBaseThemesDirectories method mock.
*
* @return array
* Array with theme names as keys and full paths as values.
*/
protected function getDefaultAndBaseThemesDirectoriesMock() {
$directories = array(
'ui_patterns_test_theme' => $this->getExtensionsPath('ui_patterns_test_theme'),
'bootstrap' => $this->getExtensionsPath('bootstrap'),
);
return $directories;
}

/**
* YamlDiscovery::fileScanDirectory method mock.
*
* @return array
* Array keyed with full file paths of all definition files.
*/
public function fileScanDirectoryMock($dir) {
$files = array();

switch ($dir) {
case $this->getExtensionsPath('ui_patterns_test'):
$files = array(
$this->getExtensionsPath('ui_patterns_test') . '/ui_patterns_test.ui_patterns.yml',
);
break;

case $this->getExtensionsPath('ui_patterns_test_theme'):
$files = array(
$this->getExtensionsPath('ui_patterns_test_theme') . '/ui_patterns_test_theme.ui_patterns.yml',
);
break;
}

return array_flip($files);
}

}
14 changes: 1 addition & 13 deletions tests/src/PhpUnit/UiPatternsManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function testProcessDefinition($id, array $expected) {
$theme_manager = $this->getThemeManagerMock();

$plugin_manager = new UiPatternsManager($module_handler, $theme_handler, $theme_manager, $cache_backend);
$plugin_manager->setYamlDiscovery($this->getYamlDiscoveryMock());
$definitions = $plugin_manager->getDefinitions();

assert($definitions, hasKey($id));
Expand Down Expand Up @@ -113,17 +114,4 @@ public function definitionsProvider() {
return $data;
}

/**
* Get full test extension path.
*
* @param string $name
* Test extension name.
*
* @return string
* Full test extension path.
*/
protected function getExtensionsPath($name) {
return realpath(dirname(__FILE__) . '/../../../tests/target/custom/' . $name);
}

}