diff --git a/composer.lock b/composer.lock index 3080532..a7af19e 100644 --- a/composer.lock +++ b/composer.lock @@ -1113,16 +1113,16 @@ }, { "name": "sdboyer/gliph", - "version": "0.1.1", + "version": "0.1.3", "source": { "type": "git", "url": "https://github.com/sdboyer/gliph.git", - "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a" + "reference": "f85ca76fde4913e3b6996691672998e646e0c642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sdboyer/gliph/zipball/c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a", - "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a", + "url": "https://api.github.com/repos/sdboyer/gliph/zipball/f85ca76fde4913e3b6996691672998e646e0c642", + "reference": "f85ca76fde4913e3b6996691672998e646e0c642", "shasum": "" }, "require": { @@ -1153,7 +1153,7 @@ "php", "spl" ], - "time": "2013-09-14 04:16:01" + "time": "2013-09-22 03:30:09" }, { "name": "symfony-cmf/routing", diff --git a/core/core.services.yml b/core/core.services.yml index 8b2182b..b2e15bb 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -641,6 +641,4 @@ services: class: Drupal\Core\Asset\JsCollectionGrouper asset.js.dumper: class: Drupal\Core\Asset\AssetDumper - asset.library_repository: - class: Drupal\Core\Asset\AssetLibraryRepository - arguments: ['@module_handler'] + diff --git a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php index d5643d0..f81cd2a 100644 --- a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php +++ b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php @@ -95,7 +95,7 @@ public function id() { return $this->id; } - /** + /** * {@inheritdoc} */ public function getAssetType() { @@ -164,25 +164,6 @@ public function contains(AssetInterface $asset) { /** * {@inheritdoc} */ - public function reindex() { - $map = array(); - foreach ($this->assetIdMap as $asset) { - $map[$asset->id()] = $asset; - } - $this->assetIdMap = $map; - - // Recalculate the id, too. - $this->calculateId(); - - // Recursively reindex contained aggregates. - foreach ($this->nestedStorage as $aggregate) { - $aggregate->reindex(); - } - } - - /** - * {@inheritdoc} - */ public function getById($id, $graceful = TRUE) { if (isset($this->assetIdMap[$id])) { return $this->assetIdMap[$id]; diff --git a/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php index c400772..934317a 100644 --- a/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php +++ b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php @@ -10,12 +10,11 @@ use Drupal\Core\Asset\Exception\AssetTypeMismatchException; use Drupal\Core\Asset\Metadata\AssetMetadataBag; use Drupal\Core\Asset\Metadata\CssMetadataBag; -use Drupal\Core\Asset\StylesheetAssetInterface; /** * A CSS asset that is an aggregate of multiple other CSS assets. */ -class CssAggregateAsset extends BaseAggregateAsset implements StylesheetAssetInterface { +class CssAggregateAsset extends BaseAggregateAsset { /** * {@inheritdoc} @@ -27,14 +26,14 @@ public function __construct(AssetMetadataBag $metadata, $assets = array(), $filt throw new AssetTypeMismatchException('CSS aggregates require CSS metadata bags.'); } - parent::__construct($metadata, $assets, $filters, $sourceRoot); // TODO: Change the autogenerated stub + parent::__construct($metadata, $assets, $filters, $sourceRoot); } /** * {@inheritdoc} */ protected function ensureCorrectType(AssetInterface $asset) { - if (!($asset instanceof StylesheetAssetInterface || $asset instanceof self)) { + if ($asset->getAssetType() !== 'css') { throw new AssetTypeMismatchException('CSS aggregates can only work with CSS assets.'); } } diff --git a/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php index 554e655..37f8a2e 100644 --- a/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php +++ b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php @@ -7,14 +7,14 @@ namespace Drupal\Core\Asset\Aggregate; use Drupal\Core\Asset\AssetInterface; -use Drupal\Core\Asset\JavascriptAssetInterface; +use Drupal\Core\Asset\Exception\AssetTypeMismatchException; use Drupal\Core\Asset\Metadata\AssetMetadataBag; use Drupal\Core\Asset\Metadata\JsMetadataBag; /** * A Javascript asset that aggregates together multiple other Javascript assets. */ -class JsAggregateAsset extends BaseAggregateAsset implements JavascriptAssetInterface { +class JsAggregateAsset extends BaseAggregateAsset { /** * {@inheritdoc} @@ -23,18 +23,18 @@ class JsAggregateAsset extends BaseAggregateAsset implements JavascriptAssetInte */ public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) { if (!$metadata instanceof JsMetadataBag) { - throw new AssetTypeMismatchException('CSS aggregates require CSS metadata bags.'); + throw new AssetTypeMismatchException('JS aggregates require JS metadata bags.'); } - parent::__construct($metadata, $assets, $filters, $sourceRoot); // TODO: Change the autogenerated stub + parent::__construct($metadata, $assets, $filters, $sourceRoot); } /** * {@inheritdoc} */ protected function ensureCorrectType(AssetInterface $asset) { - if (!($asset instanceof StylesheetAssetInterface || $asset instanceof self)) { - throw new AssetTypeMismatchException('CSS aggregates can only work with CSS assets.'); + if ($asset->getAssetType() !== 'js') { + throw new AssetTypeMismatchException('JS aggregates can only work with JS assets.'); } } } \ No newline at end of file diff --git a/core/lib/Drupal/Core/Asset/AssetGraph.php b/core/lib/Drupal/Core/Asset/AssetGraph.php index 04557ad..ee94b08 100644 --- a/core/lib/Drupal/Core/Asset/AssetGraph.php +++ b/core/lib/Drupal/Core/Asset/AssetGraph.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Asset; use Gliph\Exception\InvalidVertexTypeException; -use Gliph\Graph\DirectedAdjacencyGraph; +use Gliph\Graph\DirectedAdjacencyList; /** * An extension of the DirectedAdjacencyGraph concept designed specifically for @@ -35,7 +35,7 @@ * * TODO add stuff that tracks data about unresolved successors/predecessors */ -class AssetGraph extends DirectedAdjacencyGraph { +class AssetGraph extends DirectedAdjacencyList { protected $before = array(); protected $after = array(); diff --git a/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php b/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php deleted file mode 100644 index 5d00565..0000000 --- a/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php +++ /dev/null @@ -1,193 +0,0 @@ -moduleHandler = $module_handler; - } - - protected function initialize() { - if ($this->initialized) { - return; - } - $this->initialized = TRUE; - - // TODO inject or factory-ize the collector class that's used somehow - can't unit test it as-is. - $library_collector = new AssetLibraryCollector($this); - foreach ($this->moduleHandler->getImplementations('library_info') as $module) { - $library_collector->setModule($module); - $libraries = call_user_func("{$module}_library_info"); - foreach ($libraries as $name => $info) { - // Normalize - apparently hook_library_info is allowed to be sloppy. - $info += array('dependencies' => array(), 'js' => array(), 'css' => array()); - - // TODO This works sorta sanely only because of the array_intersect_key() hack in AssetLibrary::construct() - $asset_collector = $library_collector->buildLibrary($name, $info); - foreach (array('js', 'css') as $type) { - foreach ($info[$type] as $data => $options) { - if (is_scalar($options)) { - $data = $options; - $options = array(); - } - // TODO stop assuming these are all files. - $asset_collector->create($type, 'file', $data, $options); - } - } - } - } - } - - /** - * Gets a library by composite key. - * - * @param string $module - * The module owner that declared the library. - * - * @param string $name - * The library name. - * - * @return \Drupal\Core\Asset\Collection\AssetLibrary - * The requested library. - * - * @throws \InvalidArgumentException If there is no library by that name - */ - public function get($module, $name) { - $this->initialize(); - if (!isset($this->libraries[$module][$name])) { - throw new \InvalidArgumentException(sprintf('There is no library identified by "%s/%s" in the repository.', $module, $name)); - } - - return $this->libraries[$module][$name]; - } - - /** - * Checks if the current library repository has a certain library. - * - * @param string $module - * The module owner that declared the library. - * - * @param string $name - * The library name. - * - * @return bool - * True if the library has been set, false if not - */ - public function has($module, $name) { - $this->initialize(); - return isset($this->libraries[$module][$name]); - } - - public function add($module, $name, AssetLibrary $library) { - // TODO add validation - alphanum + underscore only - if (!isset($this->libraries[$module])) { - $this->libraries[$module] = array(); - } - - $this->libraries[$module][$name] = $library; - $this->flattened = NULL; - } - - /** - * Retrieves the asset objects on which the passed asset depends. - * - * @param AssetOrderingInterface $asset - * The asset whose dependencies should be retrieved. - * - * @return array - * An array of AssetInterface objects if any dependencies were found; - * otherwise, an empty array. - */ - public function resolveDependencies(AssetOrderingInterface $asset) { - $dependencies = array(); - - if ($asset->hasDependencies()) { - foreach ($asset->getDependencyInfo() as $info) { - try { - $dependencies[] = $this->get($info[0], $info[1]); - } - // TODO should we really try/catch at a potentially high traffic place like this? - catch (\InvalidArgumentException $e) { - // TODO we're relying on a method that's not in AssetOrderingInterface... - watchdog('assets', 'Asset @asset declared a dependency on nonexistent library @module/@name', array($asset->getSourcePath(), $info[0], $info[1]), WATCHDOG_ERROR); - } - } - } - - return $dependencies; - } - - /** - * Returns an array of library names. - * - * @return array An array of library names - */ - public function getNames() { - $this->initialize(); - return array_keys($this->flatten()); - } - - /** - * Clears all libraries. - */ - public function clear() { - $this->initialize(); - $this->libraries = array(); - $this->flattened = NULL; - } - - /** - * Flattens contained library data into a more accessible form. - */ - protected function flatten() { - if (is_null($this->flattened)) { - $this->flattened = array(); - foreach ($this->libraries as $module => $set) { - foreach ($set as $name => $library) { - $this->flattened["$module:$name"] = $library; - } - } - } - - return $this->flattened; - } - - public function getIterator() { - $this->initialize(); - return new \ArrayIterator($this->flatten()); - } - -} diff --git a/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php index 60f3c76..de2980c 100644 --- a/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php +++ b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php @@ -8,15 +8,14 @@ namespace Drupal\Core\Asset; use Assetic\Asset\AssetInterface; +use Assetic\Asset\BaseAsset as AsseticBaseAsset; use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException; /** * A class that reduces boilerplate code by centrally disabling the Assetic * properties and methods Drupal does not support. - * - * TODO extend Assetic's BaseAsset */ -abstract class AsseticAdapterAsset implements AssetInterface { +abstract class AsseticAdapterAsset extends AsseticBaseAsset implements AssetInterface { /** * @throws \Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php index 78cc3b0..d42f9a2 100644 --- a/core/lib/Drupal/Core/Asset/BaseAsset.php +++ b/core/lib/Drupal/Core/Asset/BaseAsset.php @@ -7,8 +7,6 @@ namespace Drupal\Core\Asset; -use Assetic\Filter\FilterCollection; -use Assetic\Filter\FilterInterface; use Drupal\Core\Asset\AssetInterface; use Drupal\Core\Asset\Metadata\AssetMetadataBag; @@ -24,18 +22,6 @@ */ abstract class BaseAsset extends AsseticAdapterAsset implements AssetInterface, AssetOrderingInterface { - protected $filters; - - protected $sourceRoot; - - protected $sourcePath; - - protected $targetPath; - - protected $content; - - protected $loaded; - /** * @var AssetMetadataBag */ @@ -48,15 +34,12 @@ protected $predecessors = array(); public function __construct(AssetMetadataBag $metadata, $filters = array(), $sourceRoot = NULL, $sourcePath = NULL) { - $this->filters = new FilterCollection($filters); - $this->sourceRoot = $sourceRoot; - $this->sourcePath = $sourcePath; - $this->loaded = FALSE; $this->metadata = $metadata; + parent::__construct($filters, $sourceRoot, $sourcePath); } public function __clone() { - $this->filters = clone $this->filters; + parent::__clone(); $this->metadata = clone $this->metadata; } @@ -77,117 +60,10 @@ public function getAssetType() { /** * {@inheritdoc} */ - public function ensureFilter(FilterInterface $filter) { - $this->filters->ensure($filter); - } - - /** - * {@inheritdoc} - */ - public function getFilters() { - return $this->filters->all(); - } - - /** - * {@inheritdoc} - */ - public function clearFilters() { - $this->filters->clear(); - } - - /** - * Encapsulates asset loading logic. - * - * @param string $content The asset content - * @param FilterInterface $additionalFilter An additional filter - */ - protected function doLoad($content, FilterInterface $additionalFilter = NULL) { - $filter = clone $this->filters; - if ($additionalFilter) { - $filter->ensure($additionalFilter); - } - - $asset = clone $this; - $asset->setContent($content); - - $filter->filterLoad($asset); - $this->content = $asset->getContent(); - - $this->loaded = TRUE; - } - - /** - * {@inheritdoc} - */ - public function dump(FilterInterface $additionalFilter = NULL) { - if (!$this->loaded) { - $this->load(); - } - - $filter = clone $this->filters; - if ($additionalFilter) { - $filter->ensure($additionalFilter); - } - - $asset = clone $this; - $filter->filterDump($asset); - - return $asset->getContent(); - } - - /** - * {@inheritdoc} - */ - public function getContent() { - return $this->content; - } - - /** - * {@inheritdoc} - */ - public function setContent($content) { - $this->content = $content; - } - - /** - * {@inheritdoc} - */ - public function getSourceRoot() { - return $this->sourceRoot; - } - - /** - * {@inheritdoc} - */ - public function getSourcePath() { - return $this->sourcePath; - } - - /** - * {@inheritdoc} - */ - public function getTargetPath() { - return $this->targetPath; - } - - /** - * {@inheritdoc} - */ - public function setTargetPath($targetPath) { - $this->targetPath = $targetPath; - } - - /** - * {@inheritdoc} - */ public function isPreprocessable() { return (bool) $this->metadata->get('preprocess'); } - public function setDefaults(array $defaults) { - $this->metadataDefaults = $defaults; - } - /** * {@inheritdoc} */ @@ -199,6 +75,10 @@ public function hasDependencies() { * {@inheritdoc} */ public function addDependency($module, $name) { + if (!(is_string($module) && is_string($name))) { + throw new \InvalidArgumentException('Dependencies must be expressed as 2-tuple with the first element being owner/module, and the second being name.'); + } + $this->dependencies[] = array($module, $name); } @@ -220,6 +100,10 @@ public function getDependencyInfo() { * {@inheritdoc} */ public function before($asset) { + if (!($asset instanceof AssetInterface || is_string($asset))) { + throw new \InvalidArgumentException('Ordering information must be declared using either an asset string id or the full AssetInterface object.'); + } + $this->successors[] = $asset; } @@ -227,6 +111,10 @@ public function before($asset) { * {@inheritdoc} */ public function after($asset) { + if (!($asset instanceof AssetInterface || is_string($asset))) { + throw new \InvalidArgumentException('Ordering information must be declared using either an asset string id or the full AssetInterface object.'); + } + $this->predecessors[] = $asset; } diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php index c7e2363..0fe0a90 100644 --- a/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php +++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollection.php @@ -6,17 +6,17 @@ */ namespace Drupal\Core\Asset\Collection; -use Drupal\Core\Asset\Aggregate\AssetAggregateInterface; +use Drupal\Core\Asset\Collection\AssetCollectionInterface; use Drupal\Core\Asset\AssetInterface; -use Drupal\Core\Asset\AssetLibraryRepository; use Drupal\Core\Asset\Collection\Iterator\AssetSubtypeFilterIterator; -use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException; /** * A container for assets. * * @see CssCollection * @see JsCollection + * + * TODO allow direct adding of libraries */ class AssetCollection implements \IteratorAggregate, AssetCollectionInterface { @@ -46,6 +46,7 @@ public function add(AssetInterface $asset) { * {@inheritdoc} */ public function contains(AssetInterface $asset) { + // TODO decide whether to do this by id or object instance return $this->assetStorage->contains($asset); } @@ -62,28 +63,33 @@ public function getById($id, $graceful = TRUE) { throw new \OutOfBoundsException(sprintf('This collection does not contain an asset with id %s.', $id)); } - /** - * {@inheritdoc} - */ - public function reindex() { - $map = array(); - foreach ($this->assetIdMap as $asset) { - $map[$asset->id()] = $asset; - } - $this->assetIdMap = $map; - } /** * {@inheritdoc} */ public function remove($needle, $graceful = TRUE) { + // TODO fix horrible complexity of conditionals, exceptions, and returns. $this->attemptWrite(); - if ((is_string($needle) && $needle = $this->getById($needle, $graceful)) || - $needle instanceof AssetInterface) { + // Validate and normalize type to AssetInterface + if (is_string($needle)) { + if (!$needle = $this->getById($needle, $graceful)) { + // Asset couldn't be found but we're in graceful mode - return FALSE. + return FALSE; + } + } + else if (!$needle instanceof AssetInterface) { + throw new \InvalidArgumentException('Invalid type provided to AssetCollection::remove(); must provide either a string asset id or AssetInterface instance.'); + } + + // Check for membership + if ($this->contains($needle)) { unset($this->assetIdMap[$needle->id()], $this->assetStorage[$needle]); return TRUE; } + else if (!$graceful) { + throw new \OutOfBoundsException(sprintf('This collection does not contain an asset with id %s.', $needle->id())); + } return FALSE; } @@ -168,20 +174,6 @@ public function getJs() { } /** - * {@inheritdoc} - */ - public function resolveLibraries(AssetLibraryRepository $repository) { - foreach ($this->assetStorage as $asset) { - foreach ($repository->resolveDependencies($asset) as $dep) { - $this->add($dep); - if ($dep->getAssetType() == $asset->getAssetType()) { - $asset->after($dep); - } - } - } - } - - /** * Checks if the asset library is frozen, throws an exception if it is. */ protected function attemptWrite() { diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php index f63853c..e4cebc2 100644 --- a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php +++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php @@ -70,15 +70,6 @@ public function contains(AssetInterface $asset); public function getById($id, $graceful = TRUE); /** - * Reindexes the ids of all assets contained in the aggregate. - * - * TODO this is necessary because AssetInterface::id() doesn't guarantee stable output. Fix that, and this can go away - * - * @return void - */ - public function reindex(); - - /** * Indicates whether this collection contains any assets. * * @return bool diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php index 41881ef..a4547a8 100644 --- a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php +++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php @@ -86,18 +86,4 @@ public function getCss(); * @return \Traversable */ public function getJs(); - - /** - * Resolves all contained asset references and adds them to this collection. - * - * "References" refers to library ids. This includes both libraries added - * directly to this collection, as well as those libraries included indirectly - * via a contained asset's declared dependencies. - * - * @param AssetLibraryRepository $repository - * The AssetLibraryRepository against which to resolve dependencies. - * - * @return void - */ - public function resolveLibraries(AssetLibraryRepository $repository); } \ No newline at end of file diff --git a/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php index 6b4f157..562163a 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Asset; use Drupal\Core\Asset\Aggregate\CssAggregateAsset; -use Drupal\Core\Asset\Collection\CssCollection; +use Drupal\Core\Asset\Collection\AssetCollection; use Gliph\Traversal\DepthFirst; use Gliph\Visitor\DepthFirstBasicVisitor; use Drupal\Core\Asset\AssetGraph; @@ -46,24 +46,23 @@ public function __construct(AssetLibraryRepository $repository) { * * @param array $assets * An asset collection. - * TODO update the interface to be an AssetCollection, not an array * * @return array * A sorted array of asset groups. */ - public function group(CssCollection $assets) { + public function group(AssetCollection $assets) { $tsl = $this->getOptimalTSL($assets); - // TODO replace with CssCollection // TODO ordering suddenly matters here...problem? - $processed = new CssCollection(); + $processed = new AssetCollection(); $last_key = FALSE; foreach ($tsl as $asset) { // TODO fix the visitor - this will fail right now because the optimality data got depleted during traversal $key = $this->optimal_lookup->contains($asset) ? $this->optimal_lookup[$asset] : FALSE; if ($key !== $last_key) { - $processed[] = $aggregate = new CssAggregateAsset($asset->getMetadata()); + $aggregate = new CssAggregateAsset($asset->getMetadata()); + $processed->add($aggregate); } $aggregate->add($asset); @@ -82,7 +81,7 @@ public function group(CssCollection $assets) { * * @throws \LogicException */ - protected function getOptimalTSL(CssCollection $assets) { + protected function getOptimalTSL(AssetCollection $assets) { // We need to define the optimum minimal group set, given metadata // boundaries across which aggregates cannot be safely made. $this->optimal = array(); @@ -95,7 +94,7 @@ protected function getOptimalTSL(CssCollection $assets) { // sequencing information. $graph = new AssetGraph(); - foreach ($assets as $asset) { + foreach ($assets->getCss() as $asset) { $graph->addVertex($asset); $k = $this->getGroupKey($asset); diff --git a/core/lib/Drupal/Core/Asset/ExternalAsset.php b/core/lib/Drupal/Core/Asset/ExternalAsset.php index 39b9453..2d9705c 100644 --- a/core/lib/Drupal/Core/Asset/ExternalAsset.php +++ b/core/lib/Drupal/Core/Asset/ExternalAsset.php @@ -18,10 +18,7 @@ class ExternalAsset extends BaseAsset { protected $sourceUrl; public function __construct(AssetMetadataBag $metadata, $sourceUrl, $filters = array()) { - if (0 === strpos($sourceUrl, '//')) { - $sourceUrl = 'http:' . $sourceUrl; - } - elseif (FALSE === strpos($sourceUrl, '://')) { + if (FALSE === strpos($sourceUrl, '://')) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl)); } diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php index 83107ad..8a71443 100644 --- a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php +++ b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php @@ -52,6 +52,8 @@ class AssetCollector { protected $defaultJsMetadata; + protected $lastCss; + protected $classMap = array( 'file' => 'Drupal\\Core\\Asset\\FileAsset', 'external' => 'Drupal\\Core\\Asset\\ExternalAsset', @@ -100,18 +102,25 @@ public function add(AssetInterface $asset) { * - 'string': a string containing valid CSS or Javascript to be injected * directly onto the page. * @param array $options - * An array of metadata to explicitly set on the asset. These will override - * metadata defaults that are injected onto the asset at creation time. + * (optional) An array of metadata to explicitly set on the asset. These + * will override metadata defaults that are injected onto the asset at + * creation time. * @param array $filters - * An array of filters to apply to the object + * (optional) An array of filters to apply to the object * TODO this should, maybe, be removed entirely + * @param bool $keep_last + * (optional) Whether or not to retain the created asset for automated + * ordering purposes. Only applies to CSS. Note that passing FALSE will not + * prevent a CSS asset that is being created from automatically being + * after() the existing lastCss asset, if one exists. For that, + * @see clearLastCss(). * * @return \Drupal\Core\Asset\AssetInterface * * @throws \InvalidArgumentException * Thrown if an invalid asset type or source type is passed. */ - public function create($asset_type, $source_type, $data, $options = array(), $filters = array()) { + public function create($asset_type, $source_type, $data, $options = array(), $filters = array(), $keep_last = TRUE) { // TODO this normalization points to a deeper modeling problem. $source_type = $source_type == 'inline' ? 'string' : $source_type; @@ -134,9 +143,38 @@ public function create($asset_type, $source_type, $data, $options = array(), $fi $this->add($asset); } + if ($asset_type == 'css' && !empty($this->lastCss)) { + $asset->after($this->lastCss); + } + + if ($keep_last) { + $this->lastCss = $asset; + } + return $asset; } + /** + * Clears the asset stored in lastCss. + * + * Ordinarily, using the create() factory to generate a CSS asset object will + * automatically set up an ordering relationship between that asset and the + * previous CSS asset that was created. This is intended to facilitate the + * rigid ordering that authors likely expect for CSS assets declared together + * in a contiguous series. + * + * This method clears the last stored CSS asset. It should be called when the + * end of such a contiguous series is reached, or by the asset creator + * themselves if they want to avoid the creation of the ordering relationship. + * + * @return AssetCollector + * The current AssetCollector instance, for easy chaining. + */ + public function clearLastCss() { + unset($this->lastCss); + return $this; + } + public function setCollection(AssetCollectionInterface $collection) { if ($this->isLocked()) { throw new \Exception('The collector instance is locked. A new collection cannot be attached to a locked collector.'); @@ -151,10 +189,6 @@ public function clearCollection() { $this->collection = NULL; } - public function createJavascriptSetting() { - // TODO figure out settings - } - public function lock($key) { if ($this->isLocked()) { throw new \Exception('Collector is already locked.', E_WARNING); @@ -183,11 +217,13 @@ public function isLocked() { return $this->locked; } - public function setDefaultMetadata($type, AssetMetadataBag $metadata) { + public function setDefaultMetadata(AssetMetadataBag $metadata) { if ($this->isLocked()) { throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.'); } + $type = $metadata->getType(); + if ($type === 'css') { $this->defaultCssMetadata = $metadata; } diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php deleted file mode 100644 index 7f065f7..0000000 --- a/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php +++ /dev/null @@ -1,96 +0,0 @@ -manager = $manager; - } - - public function add($name, AssetLibrary $library) { - $this->manager->add($this->module, $name, $library); - return $this; - } - - public function buildLibrary($name, $values) { - $library = $this->createLibrary($name, $values); - - $collector = new AssetCollector($library); - $collector->setDefaultMetadata('js', new JsMetadataBag(array('group' => JS_LIBRARY))); - $collector->lock($this->getPrivateKey()); // TODO is locking here a bad idea? - - return $collector; - } - - public function createLibrary($name, $values) { - $library = new AssetLibrary($values); - $this->add($name, $library); - - return $library; - } - - public function setModule($module) { - $this->module = $module; - } - - public function lock($key) { - if ($this->isLocked()) { - throw new \Exception('Collector is already locked.', E_WARNING); - } - - $this->locked = TRUE; - $this->lockKey = $key; - return TRUE; - } - - public function unlock($key) { - if (!$this->isLocked()) { - throw new \Exception('Collector is not locked', E_WARNING); - } - - if ($this->lockKey !== $key) { - throw new \Exception('Attempted to unlock Collector with incorrect key.', E_WARNING); - } - - $this->locked = FALSE; - $this->lockKey = NULL; - return TRUE; - } - - public function isLocked() { - return $this->locked; - } - - protected function getPrivateKey() { - if (empty($this->privateKey)) { - // This doesn't need to be highly secure, just decently random. - $this->privateKey = Crypt::randomStringHashed(8); - } - return $this->privateKey; - } - -} diff --git a/core/lib/Drupal/Core/Asset/StringAsset.php b/core/lib/Drupal/Core/Asset/StringAsset.php index fa4469f..e98000e 100644 --- a/core/lib/Drupal/Core/Asset/StringAsset.php +++ b/core/lib/Drupal/Core/Asset/StringAsset.php @@ -8,15 +8,31 @@ namespace Drupal\Core\Asset; use Assetic\Filter\FilterInterface; +use Drupal\Component\Utility\Crypt; use Drupal\Core\Asset\BaseAsset; use Drupal\Core\Asset\Metadata\AssetMetadataBag; class StringAsset extends BaseAsset { + /** + * The string id of this asset. + * + * This is generated by hashing the content of the asset when the object is + * first created. The id does NOT change if the content is changed later. + * + * @var string + */ + protected $id; + protected $lastModified; public function __construct(AssetMetadataBag $metadata, $content, $filters = array()) { - $this->content = $content; + if (!is_string($content)) { + throw new \InvalidArgumentException('StringAsset requires a string for its content.'); + } + + $this->id= empty($content) ? Crypt::randomStringHashed(32) : hash('sha256', $content); + $this->setContent($content); $this->lastModified = REQUEST_TIME; // TODO this is terrible parent::__construct($metadata, $filters); @@ -26,8 +42,7 @@ public function __construct(AssetMetadataBag $metadata, $content, $filters = arr * {@inheritdoc} */ public function id() { - // TODO hashing current content means this id is essentially useless. - return md5($this->content); + return $this->id; } public function setLastModified($last_modified) { @@ -39,6 +54,6 @@ public function getLastModified() { } public function load(FilterInterface $additionalFilter = NULL) { - $this->doLoad($this->content, $additionalFilter); + $this->doLoad($this->getContent(), $additionalFilter); } } diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetCollectionTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetCollectionTest.php deleted file mode 100644 index d0bbbaf..0000000 --- a/core/tests/Drupal/Tests/Core/Asset/AssetCollectionTest.php +++ /dev/null @@ -1,66 +0,0 @@ - 'Asset collection tests', - 'description' => 'Unit tests on AssetBag', - 'group' => 'Asset', - ); - } - - public function testAddValidAsset() { - // Dead-simple collection - contains just one css and one js asset, both local files. - $collection = new AssetCollection(); - - $css1 = $this->createMockAsset('css'); - $js1 = $this->createMockAsset('js'); - - $collection->add($css1); - $collection->add($js1); - - $css_result = array(); - foreach ($collection->getCss() as $asset) { - $css_result[] = $asset; - } - - $this->assertEquals(array($css1), $css_result); - - $js_result = array(); - foreach ($collection->getJs() as $asset) { - $js_result[] = $asset; - } - - $this->assertEquals(array($js1), $js_result); - - $css2 = $this->createMockAsset('css'); - - $collection->add($css2); - - $css_result = array(); - foreach ($collection->getCss() as $asset) { - $css_result[] = $asset; - } - $this->assertEquals(array($css1, $css2), $css_result); - } -} diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryRepositoryTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetLibraryRepositoryTest.php deleted file mode 100644 index f970077..0000000 --- a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryRepositoryTest.php +++ /dev/null @@ -1,259 +0,0 @@ - 'Asset library repository test', - 'description' => 'Exercises methods on AssetLibraryRepository.', - 'group' => 'Asset', - ); - } - - /** - * Sets up an AssetLibraryRepository with a fake ModuleHandler that will point - * it at our two stub hook implementations. - */ - public function setUp() { - $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $this->moduleHandler->expects($this->any()) - ->method('getImplementations') - ->with('library_info') - ->will($this->returnValue(array('stub1', 'stub2'))); - - $this->repository = new AssetLibraryRepository($this->moduleHandler); - } - - public function testInitialize() { - // TODO this is a terrible test...but really, because AssetLibraryRepository needs to be refactored. - $this->assertEquals(16, count($this->repository->getNames())); - } -} - -} - -namespace { - -/* - * Several permutations need to be covered: - * - single-asset library | homogeneous multi-asset library | heterogeneous multi-asset library - * - no dependencies | single dep | multi dep - * - dep with same type | dep with cross type | heterogeneous mix - */ -function stub1_library_info() { - $libraries['solo|nodeps|js'] = array( - 'title' => 'solo|nodeps|js', - 'js' => array( - 'js/solo/nodeps.js' => array(), - ) - ); - - $libraries['solo|nodeps|css'] = array( - 'title' => 'solo|nodeps|css', - 'css' => array( - 'css/solo/nodeps.css' => array(), - ) - ); - - $libraries['solo|onedep|same'] = array( - 'title' => 'solo|onedep|same', - 'js' => array( - 'js/solo/onedep/same.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|js'), - ) - ); - - $libraries['solo|onedep|diff'] = array( - 'title' => 'solo|onedep|same', - 'js' => array( - 'js/solo/onedep/diff.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - ) - ); - - $libraries['solo|multidep|same'] = array( - 'title' => 'solo|multidep|same', - 'js' => array( - 'js/solo/multidep/same.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|js'), - array('stub1', 'solo|onedep|same'), - ) - ); - - $libraries['solo|multidep|hetero'] = array( - 'title' => 'solo|multidep|hetero', - 'js' => array( - 'js/solo/multidep/hetero.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|js'), - array('stub1', 'solo|nodeps|css'), - ) - ); - - return $libraries; -} - -function stub2_library_info() { - $libraries['homo|nodeps|js'] = array( - 'title' => 'homo|nodeps|js', - 'js' => array( - 'js/homo/nodeps1.js' => array(), - 'js/homo/nodeps2.js' => array(), - ), - ); - - $libraries['homo|nodeps|css'] = array( - 'title' => 'homo|nodeps|css', - 'css' => array( - 'css/homo/nodeps1.css' => array(), - 'css/homo/nodeps2.css' => array(), - ), - ); - - $libraries['hetero|nodeps'] = array( - 'title' => 'hetero|nodeps', - 'css' => array( - 'css/hetero/nodeps.css' => array(), - ), - 'js' => array( - 'js/hetero/nodeps.js' => array(), - ), - ); - - $libraries['homo|onedep|same'] = array( - 'title' => 'homo|onedep|same', - 'css' => array( - 'css/homo/onedep/same1.css' => array(), - 'css/homo/onedep/same2.css' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - ), - ); - - $libraries['homo|onedep|diff'] = array( - 'title' => 'homo|onedep|diff', - 'css' => array( - 'css/homo/onedep/diff1.css' => array(), - 'css/homo/onedep/diff2.css' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|js'), - ), - ); - - $libraries['hetero|onedep'] = array( - 'title' => 'hetero|onedep', - 'css' => array( - 'css/hetero/onedep.css' => array(), - ), - 'js' => array( - 'js/hetero/onedep.js' => array(), - ), - 'dependencies' => array( - array('stub2', 'hetero|nodeps'), - ), - ); - - $libraries['homo|multidep|same'] = array( - 'title' => 'homo|multidep|same', - 'css' => array( - 'css/homo/multidep/same1.css' => array(), - 'css/homo/multidep/same2.css' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - array('stub2', 'homo|nodeps|css'), - ), - ); - - $libraries['homo|multidep|diff'] = array( - 'title' => 'homo|multidep|diff', - 'js' => array( - 'js/homo/multidep/diff1.js' => array(), - 'js/homo/multidep/diff1.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - array('stub2', 'homo|nodeps|css'), - ), - ); - - $libraries['homo|multidep|hetero'] = array( - 'title' => 'homo|multidep|hetero', - 'css' => array( - 'css/homo/multidep/hetero1.css' => array(), - 'css/homo/multidep/hetero1.css' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - array('stub2', 'homo|nodeps|js'), - ), - ); - - $libraries['hetero|multidep'] = array( - 'title' => 'hetero|multidep', - 'css' => array( - 'css/homo/multidep1.css' => array(), - ), - 'js' => array( - 'js/homo/multidep1.js' => array(), - ), - 'dependencies' => array( - array('stub1', 'solo|nodeps|css'), - array('stub2', 'homo|nodeps|js'), - ), - ); - - return $libraries; -} -} diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetTest.php deleted file mode 100644 index 2752d8c..0000000 --- a/core/tests/Drupal/Tests/Core/Asset/AssetTest.php +++ /dev/null @@ -1,37 +0,0 @@ - 'Asset tests', - 'description' => 'Unit tests for all base asset classes.', - 'group' => 'Asset', - ); - } - - public function setUp() { - parent::setUp(); - } - - public function testStub() { - // TODO anything. without this, phpunit blows up. - } -} diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php index cf5c5ce..dab09b3 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/AssetUnitTest.php @@ -13,7 +13,7 @@ */ abstract class AssetUnitTest extends UnitTestCase { - public function createMockAsset($type) { + public function createMockFileAsset($type) { $asset = $this->getMock('Drupal\\Core\\Asset\\FileAsset', array(), array(), '', FALSE); $asset->expects($this->any()) ->method('getAssetType') diff --git a/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php b/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php new file mode 100644 index 0000000..f40fc23 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Asset/BaseAssetTest.php @@ -0,0 +1,138 @@ + 'Base Asset tests', + 'description' => 'Unit tests for Drupal\'s BaseAsset.', + 'group' => 'Asset', + ); + } + + /** + * Creates a BaseAsset for testing purposes. + * + * @param $type + * + * @return BaseAsset; + */ + public function createBaseAsset($defaults = array()) { + $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag', $defaults); + return $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta)); + } + + public function testGetMetadata() { + $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta)); + + $this->assertSame($mockmeta, $asset->getMetadata()); + } + + public function testGetAssetType() { + $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + $mockmeta->expects($this->once()) + ->method('getType') + ->will($this->returnValue('css')); + $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta)); + + $this->assertEquals('css', $asset->getAssetType()); + } + + public function testIsPreprocessable() { + $mockmeta = $this->getMock('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + $mockmeta->expects($this->once()) + ->method('get') + ->with('preprocess') + ->will($this->returnValue(TRUE)); + $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta)); + + $this->assertTrue($asset->isPreprocessable()); + } + + /** + * Tests all dependency-related methods. + */ + public function testDependencies() { + $asset = $this->createBaseAsset(); + + $asset->addDependency('foo', 'bar'); + $this->assertEquals(array(array('foo', 'bar')), $asset->getDependencyInfo()); + $this->assertTrue($asset->hasDependencies()); + + $asset->clearDependencies(); + $this->assertEmpty($asset->getDependencyInfo()); + + $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass); + + try { + foreach ($invalid as $val) { + $asset->addDependency($val, $val); + $this->fail('Was able to create an ordering relationship with an inappropriate value.'); + } + } catch (\InvalidArgumentException $e) {} + } + + public function testSuccessors() { + $asset = $this->createBaseAsset(); + $dep = $this->createBaseAsset(); + + $asset->before('foo'); + $asset->before($dep); + + $this->assertEquals(array('foo', $dep), $asset->getSuccessors()); + + $asset->clearSuccessors(); + $this->assertEmpty($asset->getSuccessors()); + + $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass); + + try { + foreach ($invalid as $val) { + $asset->before($val); + $this->fail('Was able to create an ordering relationship with an inappropriate value.'); + } + } catch (\InvalidArgumentException $e) {} + } + + public function testPredecessors() { + $asset = $this->createBaseAsset(); + $dep = $this->createBaseAsset(); + + $asset->after('foo'); + $asset->after($dep); + $this->assertEquals(array('foo', $dep), $asset->getPredecessors()); + + $asset->clearPredecessors(); + $this->assertEmpty($asset->getPredecessors()); + + $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass); + + try { + foreach ($invalid as $val) { + $asset->after($val); + $this->fail('Was able to create an ordering relationship with an inappropriate value.'); + } + } catch (\InvalidArgumentException $e) {} + } + + public function testClone() { + $mockmeta = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + $asset = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\BaseAsset', array($mockmeta)); + + $clone = clone $asset; + $this->assertNotSame($mockmeta, $clone->getMetadata()); + } +} diff --git a/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php new file mode 100644 index 0000000..1c12dcd --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetCollectionTest.php @@ -0,0 +1,203 @@ + 'Asset collection tests', + 'description' => 'Unit tests on AssetBag', + 'group' => 'Asset', + ); + } + + public function setUp() { + $this->collection = new AssetCollection(); + } + + public function testAdd() { + $css = $this->createMockFileAsset('css'); + $js = $this->createMockFileAsset('js'); + + $this->collection->add($css); + $this->collection->add($js); + + $this->assertContains($css, $this->collection); + $this->assertContains($js, $this->collection); + } + + public function testGetCss() { + $css = $this->createMockFileAsset('css'); + $js = $this->createMockFileAsset('js'); + + $this->collection->add($css); + $this->collection->add($js); + + $css_result = array(); + foreach ($this->collection->getCss() as $asset) { + $css_result[] = $asset; + } + + $this->assertEquals(array($css), $css_result); + } + + public function testGetJs() { + $css = $this->createMockFileAsset('css'); + $js = $this->createMockFileAsset('js'); + + $this->collection->add($css); + $this->collection->add($js); + + $js_result = array(); + foreach ($this->collection->getJs() as $asset) { + $js_result[] = $asset; + } + + $this->assertEquals(array($js), $js_result); + } + + public function testAll() { + $css = $this->createMockFileAsset('css'); + $js = $this->createMockFileAsset('js'); + + $this->collection->add($css); + $this->collection->add($js); + + $this->assertEquals(array($css->id() => $css, $js->id() => $js), $this->collection->all()); + } + + public function testRemoveByAsset() { + $stub = $this->createMockFileAsset('css'); + + $this->collection->add($stub); + $this->collection->remove($stub); + + $this->assertNotContains($stub, $this->collection); + } + + public function testRemoveById() { + $stub = $this->createMockFileAsset('css'); + + $this->collection->add($stub); + $this->collection->remove($stub->id()); + + $this->assertNotContains($stub, $this->collection); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveNonexistentId() { + $this->assertFalse($this->collection->remove('foo')); + $this->collection->remove('foo', FALSE); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testRemoveNonexistentAsset() { + $stub = $this->createMockFileAsset('css'); + $this->assertFalse($this->collection->remove($stub)); + $this->collection->remove($stub, FALSE); + } + + public function testRemoveInvalidType() { + $invalid = array(0, 1.1, fopen(__FILE__, 'r'), TRUE, array(), new \stdClass); + try { + foreach ($invalid as $val) { + $this->collection->remove($val); + $this->fail('AssetCollection::remove() did not throw exception on invalid argument type.'); + } + } catch (\InvalidArgumentException $e) {} + } + + public function testMergeCollection() { + $coll2 = new AssetCollection(); + $stub1 = $this->createMockFileAsset('css'); + $stub2 = $this->createMockFileAsset('js'); + + $coll2->add($stub1); + $this->collection->mergeCollection($coll2); + + $this->assertContains($stub1, $this->collection); + $this->assertTrue($coll2->isFrozen()); + + $coll3 = new AssetCollection(); + $coll3->add($stub1); + $coll3->add($stub2); + // Ensure no duplicates, and don't freeze merged bag + $this->collection->mergeCollection($coll3, FALSE); + + $contained = array( + $stub1->id() => $stub1, + $stub2->id() => $stub2, + ); + $this->assertEquals($contained, $this->collection->all()); + $this->assertFalse($coll3->isFrozen()); + } + + /** + * Tests that all methods should be disabled by freezing the collection + * correctly trigger an exception. + */ + public function testExceptionOnWriteWhenFrozen() { + $stub = $this->createMockFileAsset('css'); + $write_protected = array( + 'add' => $stub, + 'remove' => $stub, + 'mergeCollection' => $this->getMock('\\Drupal\\Core\\Asset\\Collection\\AssetCollection'), + ); + + $this->collection->freeze(); + foreach ($write_protected as $method => $arg) { + try { + $this->collection->$method($arg); + $this->fail('Was able to run writable method on frozen AssetCollection'); + } + catch (\LogicException $e) {} + } + } + + /** + * @expectedException OutOfBoundsException + */ + public function testGetById() { + $metamock = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + + $asset = $this->getMock('\\Drupal\\Core\\Asset\\FileAsset', array(), array($metamock, 'foo')); + $asset->expects($this->once()) + ->method('id') + ->will($this->returnValue('foo')); + + $this->collection->add($asset); + $this->assertSame($asset, $this->collection->getById('foo')); + + // Nonexistent asset + $this->assertFalse($this->collection->getById('bar')); + + // Nonexistent asset, non-graceful + $this->collection->getById('bar', FALSE); + } + + public function testIsEmpty() { + $this->assertTrue($this->collection->isEmpty()); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php similarity index 95% rename from core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php rename to core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php index 2057048..5608951 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/Collection/AssetLibraryTest.php @@ -4,16 +4,16 @@ * Contains Drupal\Tests\Core\Asset\AssetLibraryTest. */ -namespace Drupal\Tests\Core\Asset; +namespace Drupal\Tests\Core\Asset\Collection; use Drupal\Core\Asset\Collection\AssetLibrary; -use Drupal\Tests\UnitTestCase; +use Drupal\Tests\Core\Asset\AssetUnitTest; /** * * @group Asset */ -class AssetLibraryTest extends UnitTestCase { +class AssetLibraryTest extends AssetUnitTest { public static function getInfo() { return array( diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php b/core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php similarity index 77% rename from core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php rename to core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php index 35b93c4..f3b6d45 100644 --- a/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/Factory/AssetCollectorTest.php @@ -4,7 +4,7 @@ * Contains Drupal\Tests\Core\Asset\AssetCollectorTest. */ -namespace Drupal\Tests\Core\Asset; +namespace Drupal\Tests\Core\Asset\Factory; if (!defined('CSS_AGGREGATE_THEME')) { define('CSS_AGGREGATE_THEME', 100); @@ -22,6 +22,7 @@ use Drupal\Core\Asset\Factory\AssetCollector; use Drupal\Core\Asset\Metadata\CssMetadataBag; use Drupal\Core\Asset\Metadata\JsMetadataBag; +use Drupal\Tests\Core\Asset\AssetUnitTest; use Drupal\Tests\UnitTestCase; /** @@ -62,7 +63,7 @@ public function testMetadataInjection() { public function testDefaultPropagation() { // Test that defaults are correctly applied by the factory. $meta = new CssMetadataBag(array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME)); - $this->collector->setDefaultMetadata('css', $meta); + $this->collector->setDefaultMetadata($meta); $css1 = $this->collector->create('css', 'file', 'foo'); $asset_meta = $css1->getMetadata(); @@ -73,7 +74,7 @@ public function testDefaultPropagation() { /** * @expectedException Exception */ - public function testExceptionOnAddingAssetWithoutBagPresent() { + public function testExceptionOnAddingAssetWithoutCollectionPresent() { $asset = $this->collector->create('css', 'string', 'foo'); $this->collector->add($asset); } @@ -81,19 +82,19 @@ public function testExceptionOnAddingAssetWithoutBagPresent() { /** * TODO separate test for an explicit add() call. */ - public function testAssetsImplicitlyArriveInInjectedBag() { + public function testAssetsImplicitlyArriveInInjectedCollection() { $collection = new AssetCollection(); $this->collector->setCollection($collection); $asset = $this->collector->create('css', 'file', 'bar'); - $this->assertContains($asset, $collection->getCss(), 'Created asset was implicitly added to bag.'); + $this->assertContains($asset, $collection->getCss(), 'Created asset was implicitly added to collection.'); } public function testAddAssetExplicitly() { $collection = new AssetCollection(); $this->collector->setCollection($collection); - $mock = $this->createMockAsset('css'); + $mock = $this->createMockFileAsset('css'); $this->collector->add($mock); $this->assertContains($mock, $collection); @@ -132,9 +133,24 @@ public function testUnlockFailsWithoutCorrectSecret() { /** * @expectedException Exception */ + public function testUnlockFailsIfNotLocked() { + $this->collector->unlock('foo'); + } + + /** + * @expectedException Exception + */ + public function testLockFailsIfLocked() { + $this->collector->lock('foo'); + $this->collector->lock('error'); + } + + /** + * @expectedException Exception + */ public function testLockingPreventsSettingDefaults() { $this->collector->lock($this); - $this->collector->setDefaultMetadata('css', new CssMetadataBag()); + $this->collector->setDefaultMetadata(new CssMetadataBag()); } /** @@ -148,7 +164,7 @@ public function testLockingPreventsRestoringDefaults() { /** * @expectedException Exception */ - public function testLockingPreventsClearingBag() { + public function testLockingPreventsClearingCollection() { $this->collector->lock($this); $this->collector->clearCollection(); } @@ -156,7 +172,7 @@ public function testLockingPreventsClearingBag() { /** * @expectedException Exception */ - public function testLockingPreventsSettingBag() { + public function testLockingPreventsSettingCollection() { $this->collector->lock($this); $this->collector->setCollection(new AssetCollection()); } @@ -168,7 +184,7 @@ public function testBuiltinDefaultAreTheSame() { public function testChangeAndRestoreDefaults() { $changed_css = new CssMetadataBag(array('foo' => 'bar', 'every_page' => TRUE)); - $this->collector->setDefaultMetadata('css', $changed_css); + $this->collector->setDefaultMetadata($changed_css); $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css')); $this->assertNotSame($changed_css, $this->collector->getMetadataDefaults('css'), 'Metadata is cloned on retrieval from collector.'); @@ -178,8 +194,8 @@ public function testChangeAndRestoreDefaults() { // Do another check to ensure that both metadata bags are correctly reset $changed_js = new JsMetadataBag(array('scope' => 'footer', 'fizzbuzz' => 'llama')); - $this->collector->setDefaultMetadata('css', $changed_css); - $this->collector->setDefaultMetadata('js', $changed_js); + $this->collector->setDefaultMetadata($changed_css); + $this->collector->setDefaultMetadata($changed_js); $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css')); $this->assertEquals($changed_js, $this->collector->getMetadataDefaults('js')); @@ -190,6 +206,18 @@ public function testChangeAndRestoreDefaults() { } /** + * @expectedException \InvalidArgumentException + */ + public function testMetadataTypeMustBeCorrect() { + $mock = $this->getMockForAbstractClass('\\Drupal\\Core\\Asset\\Metadata\\AssetMetadataBag'); + $mock->expects($this->once()) + ->method('getType') + ->will($this->returnValue('foo')); + + $this->collector->setDefaultMetadata($mock); + } + + /** * @expectedException InvalidArgumentException */ public function testGetNonexistentDefault() { @@ -232,4 +260,28 @@ public function testCreateJavascriptStringAsset() { $this->assertInstanceOf('\Drupal\Core\Asset\StringAsset', $js_string); $this->assertEquals('js', $js_string->getAssetType()); } -} \ No newline at end of file + + public function testLastCssAutoAfter() { + $css1 = $this->collector->create('css', 'file', 'foo.css'); + $css2 = $this->collector->create('css', 'file', 'foo2.css'); + $this->assertEquals(array($css1), $css2->getPredecessors()); + + $this->collector->clearLastCss(); + $css3 = $this->collector->create('css', 'file', 'foo3.css'); + $this->assertEmpty($css3->getPredecessors()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testExceptionOnInvalidSourceType() { + $this->collector->create('foo', 'bar', 'baz'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testExceptionOnInvalidAssetType() { + $this->collector->create('css', 'bar', 'qux'); + } +} diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php index a525121..54ee8f2 100644 --- a/core/vendor/autoload.php +++ b/core/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer' . '/autoload_real.php'; -return ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2::getLoader(); +return ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700::getLoader(); diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php index 52043fb..70fde72 100644 --- a/core/vendor/composer/autoload_real.php +++ b/core/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2 +class ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700 { private static $loader; @@ -19,9 +19,9 @@ public static function getLoader() return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitd1cf02b92e6e91cd998c4d7a12700700', 'loadClassLoader')); $vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index be27d13..0490c53 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -2067,23 +2067,23 @@ }, { "name": "sdboyer/gliph", - "version": "0.1.1", - "version_normalized": "0.1.1.0", + "version": "0.1.3", + "version_normalized": "0.1.3.0", "source": { "type": "git", "url": "https://github.com/sdboyer/gliph.git", - "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a" + "reference": "f85ca76fde4913e3b6996691672998e646e0c642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sdboyer/gliph/zipball/c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a", - "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a", + "url": "https://api.github.com/repos/sdboyer/gliph/zipball/f85ca76fde4913e3b6996691672998e646e0c642", + "reference": "f85ca76fde4913e3b6996691672998e646e0c642", "shasum": "" }, "require": { "php": ">=5.3" }, - "time": "2013-09-14 04:16:01", + "time": "2013-09-22 03:30:09", "type": "library", "installation-source": "dist", "autoload": { diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php new file mode 100644 index 0000000..22d0860 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php @@ -0,0 +1,15 @@ +vertices = new \SplObjectStorage(); } + /** + * {@inheritdoc} + */ public function addVertex($vertex) { if (!is_object($vertex)) { throw new InvalidVertexTypeException('Vertices must be objects; non-object provided.'); @@ -20,20 +24,39 @@ public function addVertex($vertex) { if (!$this->hasVertex($vertex)) { $this->vertices[$vertex] = new \SplObjectStorage(); } + + return $this; } + /** + * {@inheritdoc} + */ public function eachAdjacent($vertex, $callback) { - foreach ($this->vertices[$vertex] as $e) { - call_user_func($callback, $e); + if (!$this->hasVertex($vertex)) { + throw new NonexistentVertexException('Vertex is not in graph; cannot iterate over its adjacent vertices.'); + } + + foreach ($this->vertices[$vertex] as $adjacent_vertex) { + call_user_func($callback, $adjacent_vertex); } + + return $this; } + /** + * {@inheritdoc} + */ public function eachVertex($callback) { - $this->fev(function ($v, $outgoing) use ($callback) { - call_user_func($callback, $v, $outgoing); + $this->fev(function ($v, $adjacent) use ($callback) { + call_user_func($callback, $v, $adjacent); }); + + return $this; } + /** + * {@inheritdoc} + */ public function hasVertex($vertex) { return $this->vertices->contains($vertex); } @@ -43,5 +66,7 @@ protected function fev($callback) { $outgoing = $this->vertices->getInfo(); $callback($vertex, $outgoing); } + + return $this; } } \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php deleted file mode 100644 index 81f0440..0000000 --- a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php +++ /dev/null @@ -1,76 +0,0 @@ -hasVertex($from)) { - $this->addVertex(($from)); - } - - if (!$this->hasVertex($to)) { - $this->addVertex($to); - } - - $this->vertices[$from]->attach($to); - } - - public function removeVertex($vertex) { - if (!$this->hasVertex($vertex)) { - throw new \OutOfBoundsException('Vertex is not in the graph, it cannot be removed.', E_WARNING); - } - - $this->eachVertex(function($v, $outgoing) use ($vertex) { - if ($outgoing->contains($vertex)) { - $outgoing->detach($vertex); - } - }); - unset($this->vertices[$vertex]); - } - - public function removeEdge($from, $to) { - $this->vertices[$from]->detach($to); - } - - public function eachEdge($callback) { - $edges = array(); - $this->fev(function ($from, $outgoing) use (&$edges) { - foreach ($outgoing as $to) { - $edges[] = array($from, $to); - } - }); - - foreach ($edges as $edge) { - call_user_func($callback, $edge); - } - } - - /** - * Returns the transpose of this graph. - * - * A transpose is identical to the current graph, except that - * its edges have had their directionality reversed. - * - * Also sometimes known as the 'reverse' or 'converse'. - * - * @return \Gliph\Graph\DirectedAdjacencyGraph - */ - public function transpose() { - $graph = new self(); - $this->eachEdge(function($edge) use (&$graph) { - $graph->addDirectedEdge($edge[1], $edge[0]); - }); - - return $graph; - } - - public function getCycles() { - $tarjan = new Tarjan(); - $scc = $tarjan->getCycles($this); - return $scc->count() > 0 ? $scc : FALSE; - } -} - diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php new file mode 100644 index 0000000..fdbfae9 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php @@ -0,0 +1,75 @@ +hasVertex($tail)) { + $this->addVertex(($tail)); + } + + if (!$this->hasVertex($head)) { + $this->addVertex($head); + } + + $this->vertices[$tail]->attach($head); + } + + /** + * {@inheritdoc} + */ + public function removeVertex($vertex) { + if (!$this->hasVertex($vertex)) { + throw new NonexistentVertexException('Vertex is not in the graph, it cannot be removed.', E_WARNING); + } + + $this->eachVertex(function($v, $outgoing) use ($vertex) { + if ($outgoing->contains($vertex)) { + $outgoing->detach($vertex); + } + }); + unset($this->vertices[$vertex]); + } + + /** + * {@inheritdoc} + */ + public function removeEdge($tail, $head) { + $this->vertices[$tail]->detach($head); + } + + /** + * {@inheritdoc} + */ + public function eachEdge($callback) { + $edges = array(); + $this->fev(function ($from, $outgoing) use (&$edges) { + foreach ($outgoing as $to) { + $edges[] = array($from, $to); + } + }); + + foreach ($edges as $edge) { + call_user_func($callback, $edge); + } + } + + /** + * {@inheritdoc} + */ + public function transpose() { + $graph = new self(); + $this->eachEdge(function($edge) use (&$graph) { + $graph->addDirectedEdge($edge[1], $edge[0]); + }); + + return $graph; + } +} + diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php new file mode 100644 index 0000000..309e429 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php @@ -0,0 +1,38 @@ +hasVertex($from)) { $this->addVertex(($from)); @@ -17,9 +22,12 @@ public function addEdge($from, $to) { $this->vertices[$to]->attach($from); } + /** + * {@inheritdoc} + */ public function removeVertex($vertex) { if (!$this->hasVertex($vertex)) { - throw new \OutOfBoundsException('Vertex is not in the graph, it cannot be removed.', E_WARNING); + throw new NonexistentVertexException('Vertex is not in the graph, it cannot be removed.', E_WARNING); } foreach ($this->vertices[$vertex] as $adjacent) { @@ -28,11 +36,17 @@ public function removeVertex($vertex) { unset($this->vertices[$vertex]); } + /** + * {@inheritdoc} + */ public function removeEdge($from, $to) { $this->vertices[$from]->detach($to); $this->vertices[$to]->detach($from); } + /** + * {@inheritdoc} + */ public function eachEdge($callback) { $edges = array(); $complete = new \SplObjectStorage(); diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php new file mode 100644 index 0000000..c29b5d9 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php @@ -0,0 +1,25 @@ +index = 0; - $this->scc = new \SplQueue(); - $this->stack = array(); - - $this->graph = $graph; - if ($graph->getVertexTypes() == DirectedAdjacencyGraph::OBJECT_VERTICES) { - $this->vertexIndices = new \SplObjectStorage(); - $this->vertexLowLimits = new \SplObjectStorage(); - } - else { - $this->vertexIndices = new HashMap(); - $this->vertexLowLimits = new HashMap(); - } - - $that = $this; - $graph->eachVertex(function($vertex) use (&$that, &$graph) { - if (!$that->vertexIndices->contains($vertex)) { - $that->strongconnect($vertex); - } - }); - - return $this->scc; - } - - public function strongconnect($vertex) { - $this->vertexIndices[$vertex] = $this->index; - $this->vertexLowLimits[$vertex] = $this->index; - $this->index++; - $this->stack[] = $vertex; - - $that = $this; - $this->graph->eachAdjacent($vertex, function($to) use (&$vertex, &$that) { - if (!$that->vertexIndices->contains($to)) { - $that->strongconnect($to); - $ll = min($that->vertexLowLimits[$vertex], $that->vertexLowLimits[$to]); - $that->vertexLowLimits[$vertex] = $ll; - } - // FIXME Tarjan dictates this search should be constant time. ruh roh. - else if (array_search($to, $that->stack, TRUE) !== FALSE) { - $min = min($that->vertexLowLimits[$vertex], $that->vertexIndices[$to]); - $that->vertexLowLimits[$vertex] = $min; - } - }); - - if ($this->vertexIndices[$vertex] == $this->vertexLowLimits[$vertex]) { - $component = new \SplQueue(); - do { - $popped = array_pop($this->stack); - $component->push($popped); - } while ($vertex !== $popped); - - if ($component->count() > 1 || $this->storeNonCycles) { - $this->scc->push($component); - } - } - } -} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php index 3879924..3d6b04c 100644 --- a/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php +++ b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php @@ -2,7 +2,9 @@ namespace Gliph\Traversal; -use Gliph\Graph\DirectedAdjacencyGraph; +use Gliph\Exception\RuntimeException; +use Gliph\Graph\DirectedGraph; +use Gliph\Visitor\DepthFirstToposortVisitor; use Gliph\Visitor\DepthFirstVisitorInterface; class DepthFirst { @@ -10,18 +12,28 @@ class DepthFirst { /** * Perform a depth-first traversal on the provided graph. * - * @param DirectedAdjacencyGraph $graph + * @param DirectedGraph $graph * The graph on which to perform the depth-first search. * @param DepthFirstVisitorInterface $visitor * The visitor object to use during the traversal. - * @param mixed $start - * A queue of vertices to ensure are visited. The traversal will deque - * them in order and visit them. + * @param object|\SplDoublyLinkedList $start + * A vertex, or vertices, to use as start points for the traversal. There + * are a few sub-behaviors here: + * - If an SplDoublyLinkedList, SplQueue, or SplStack is provided, the + * traversal will deque and visit vertices contained therein. + * - If a single vertex object is provided, it will be the sole + * originating point for the traversal. + * - If no value is provided, DepthFirst::find_sources() is called to + * search the graph for source vertices. These are place into an + * SplQueue in the order in which they are discovered, and traversal + * is then run over that queue in the same manner as if calling code + * had provided a queue directly. This method *guarantees* that all + * vertices in the graph will be visited. * - * @throws \OutOfBoundsException + * @throws RuntimeException * Thrown if an invalid $start parameter is provided. */ - public static function traverse(DirectedAdjacencyGraph $graph, DepthFirstVisitorInterface $visitor, $start = NULL) { + public static function traverse(DirectedGraph $graph, DepthFirstVisitorInterface $visitor, $start = NULL) { if ($start === NULL) { $queue = self::find_sources($graph, $visitor); } @@ -34,7 +46,7 @@ public static function traverse(DirectedAdjacencyGraph $graph, DepthFirstVisitor } if ($queue->isEmpty()) { - throw new \RuntimeException('No start vertex or vertices were provided, and no source vertices could be found in the provided graph.', E_WARNING); + throw new RuntimeException('No start vertex or vertices were provided, and no source vertices could be found in the provided graph.', E_WARNING); } $visiting = new \SplObjectStorage(); @@ -68,14 +80,14 @@ public static function traverse(DirectedAdjacencyGraph $graph, DepthFirstVisitor } /** - * Finds source vertices in a DirectedAdjacencyGraph, then enqueues them. + * Finds source vertices in a DirectedGraph, then enqueues them. * - * @param DirectedAdjacencyGraph $graph + * @param DirectedGraph $graph * @param DepthFirstVisitorInterface $visitor * * @return \SplQueue */ - public static function find_sources(DirectedAdjacencyGraph $graph, DepthFirstVisitorInterface $visitor) { + public static function find_sources(DirectedGraph $graph, DepthFirstVisitorInterface $visitor) { $incomings = new \SplObjectStorage(); $queue = new \SplQueue(); @@ -100,4 +112,21 @@ public static function find_sources(DirectedAdjacencyGraph $graph, DepthFirstVis return $queue; } + + /** + * Performs a topological sort on the provided graph. + * + * @param DirectedGraph $graph + * @param object|\SplDoublyLinkedList $start + * The starting point(s) for the toposort. @see DepthFirst::traverse() + * + * @return array + * A valid topologically sorted list for the provided graph. + */ + public static function toposort(DirectedGraph $graph, $start = NULL) { + $visitor = new DepthFirstToposortVisitor(); + self::traverse($graph, $visitor, $start); + + return $visitor->getTsl(); + } } \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php index 4c409fa..52c91f4 100644 --- a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php @@ -2,6 +2,9 @@ namespace Gliph\Visitor; +use Gliph\Exception\OutOfRangeException; +use Gliph\Exception\RuntimeException; + /** * Basic depth-first visitor. * @@ -32,7 +35,7 @@ public function __construct() { } public function onBackEdge($vertex, \Closure $visit) { - throw new \RuntimeException(sprintf('Cycle detected in provided graph.')); + throw new RuntimeException(sprintf('Cycle detected in provided graph.')); } public function onInitializeVertex($vertex, $source, \SplQueue $queue) { @@ -86,7 +89,7 @@ public function getTsl() { */ public function getReachable($vertex) { if (!isset($this->paths[$vertex])) { - throw new \OutOfRangeException('Unknown vertex provided.'); + throw new OutOfRangeException('Unknown vertex provided.'); } return $this->paths[$vertex]; diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php new file mode 100644 index 0000000..dd57fb8 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php @@ -0,0 +1,44 @@ +tsl[] = $vertex; + } + + /** + * Returns a valid topological sort of the visited graph as an array. + * + * @return array + */ + public function getTsl() { + return $this->tsl; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php new file mode 100644 index 0000000..36106d1 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php @@ -0,0 +1,49 @@ +v = array( + 'a' => new TestVertex('a'), + 'b' => new TestVertex('b'), + 'c' => new TestVertex('c'), + 'd' => new TestVertex('d'), + 'e' => new TestVertex('e'), + 'f' => new TestVertex('f'), + 'g' => new TestVertex('g'), + ); + } + + public function doCheckVerticesEqual($vertices, AdjacencyList $graph = null) { + $found = array(); + $graph = is_null($graph) ? $this->g : $graph; + + $graph->eachVertex( + function ($vertex) use (&$found) { + $found[] = $vertex; + } + ); + + $this->assertEquals($vertices, $found); + } + + public function doCheckVertexCount($count, AdjacencyList $graph = null) { + $found = array(); + $graph = is_null($graph) ? $this->g : $graph; + + $graph->eachVertex( + function ($vertex) use (&$found) { + $found[] = $vertex; + } + ); + + $this->assertCount($count, $found); + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php similarity index 59% rename from core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php rename to core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php index 438ebbc..baad7bb 100644 --- a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php @@ -2,53 +2,18 @@ namespace Gliph\Graph; - -use Gliph\TestVertex; - -abstract class AdjacencyGraphTest extends \PHPUnit_Framework_TestCase { +class AdjacencyListTest extends AdjacencyListBase { protected $v = array(); /** - * @var AdjacencyGraph + * @var AdjacencyList */ protected $g; - /** - * Creates a set of vertices and an empty graph for testing. - */ public function setUp() { - $this->v = array( - 'a' => new TestVertex('a'), - 'b' => new TestVertex('b'), - 'c' => new TestVertex('c'), - 'd' => new TestVertex('d'), - 'e' => new TestVertex('e'), - 'f' => new TestVertex('f'), - 'g' => new TestVertex('g'), - ); - } - - public function doCheckVerticesEqual($vertices, AdjacencyGraph $graph = NULL) { - $found = array(); - $graph = is_null($graph) ? $this->g : $graph; - - $graph->eachVertex(function ($vertex) use (&$found) { - $found[] = $vertex; - }); - - $this->assertEquals($vertices, $found); - } - - public function doCheckVertexCount($count, AdjacencyGraph $graph = NULL) { - $found = array(); - $graph = is_null($graph) ? $this->g : $graph; - - $graph->eachVertex(function ($vertex) use (&$found) { - $found[] = $vertex; - }); - - $this->assertCount($count, $found); + parent::setUp(); + $this->g = $this->getMockForAbstractClass('\\Gliph\\Graph\\AdjacencyList'); } /** @@ -113,9 +78,9 @@ public function testAddVertexTwice() { } /** - * @expectedException OutOfBoundsException + * @expectedException Gliph\Exception\NonexistentVertexException */ - public function testRemoveNonexistentVertex() { - $this->g->removeVertex($this->v['a']); + public function testEachAdjacentMissingVertex() { + $this->g->eachAdjacent($this->v['a'], function() {}); } } diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php similarity index 88% rename from core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php rename to core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php index c48c3c2..5bd6f2f 100644 --- a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php @@ -2,16 +2,16 @@ namespace Gliph\Graph; -class DirectedAdjacencyGraphTest extends AdjacencyGraphTest { +class DirectedAdjacencyListTest extends AdjacencyListBase { /** - * @var DirectedAdjacencyGraph + * @var DirectedAdjacencyList */ protected $g; public function setUp() { parent::setUp(); - $this->g = new DirectedAdjacencyGraph(); + $this->g = new DirectedAdjacencyList(); } @@ -84,4 +84,11 @@ public function testTranspose() { $this->doCheckVertexCount(3, $transpose); $this->doCheckVerticesEqual(array($this->v['b'], $this->v['a'], $this->v['c']), $transpose); } + + /** + * @expectedException Gliph\Exception\NonexistentVertexException + */ + public function testRemoveNonexistentVertex() { + $this->g->removeVertex($this->v['a']); + } } diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php similarity index 84% rename from core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php rename to core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php index 7aacca4..f7cb554 100644 --- a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php @@ -3,10 +3,10 @@ namespace Gliph\Graph; -class UndirectedAdjacencyGraphTest extends AdjacencyGraphTest { +class UndirectedAdjacencyListTest extends AdjacencyListBase { /** - * @var UndirectedAdjacencyGraph + * @var UndirectedAdjacencyList */ protected $g; @@ -15,7 +15,7 @@ class UndirectedAdjacencyGraphTest extends AdjacencyGraphTest { */ public function setUp() { parent::setUp(); - $this->g = new UndirectedAdjacencyGraph(); + $this->g = new UndirectedAdjacencyList(); } public function testAddUndirectedEdge() { @@ -67,4 +67,11 @@ public function testEachEdge() { $this->assertCount(2, $found); } + + /** + * @expectedException Gliph\Exception\NonexistentVertexException + */ + public function testRemoveNonexistentVertex() { + $this->g->removeVertex($this->v['a']); + } } diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php index 47c384d..81de8a3 100644 --- a/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php @@ -3,20 +3,21 @@ namespace Gliph\Traversal; -use Gliph\Graph\DirectedAdjacencyGraph; +use Gliph\Exception\NonexistentVertexException; +use Gliph\Graph\DirectedAdjacencyList; use Gliph\TestVertex; use Gliph\Visitor\DepthFirstNoOpVisitor; class DepthFirstTest extends \PHPUnit_Framework_TestCase { /** - * @var DirectedAdjacencyGraph + * @var DirectedAdjacencyList */ protected $g; protected $v; public function setUp() { - $this->g = new DirectedAdjacencyGraph(); + $this->g = new DirectedAdjacencyList(); $this->v = array( 'a' => new TestVertex('a'), 'b' => new TestVertex('b'), @@ -24,13 +25,13 @@ public function setUp() { 'd' => new TestVertex('d'), 'e' => new TestVertex('e'), 'f' => new TestVertex('f'), - 'g' => new TestVertex('g'), ); + extract($this->v); - $this->g->addDirectedEdge($this->v['a'], $this->v['b']); - $this->g->addDirectedEdge($this->v['b'], $this->v['c']); - $this->g->addDirectedEdge($this->v['a'], $this->v['c']); - $this->g->addDirectedEdge($this->v['b'], $this->v['d']); + $this->g->addDirectedEdge($a, $b); + $this->g->addDirectedEdge($b, $c); + $this->g->addDirectedEdge($a, $c); + $this->g->addDirectedEdge($b, $d); } public function testBasicAcyclicDepthFirstTraversal() { @@ -45,7 +46,9 @@ public function testBasicAcyclicDepthFirstTraversal() { } public function testDirectCycleDepthFirstTraversal() { - $this->g->addDirectedEdge($this->v['d'], $this->v['b']); + extract($this->v); + + $this->g->addDirectedEdge($d, $b); $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor'); $visitor->expects($this->exactly(1))->method('onBackEdge'); @@ -54,35 +57,57 @@ public function testDirectCycleDepthFirstTraversal() { } public function testIndirectCycleDepthFirstTraversal() { - $this->g->addDirectedEdge($this->v['d'], $this->v['a']); + extract($this->v); + + $this->g->addDirectedEdge($d, $a); $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor'); $visitor->expects($this->exactly(1))->method('onBackEdge'); - DepthFirst::traverse($this->g, $visitor, $this->v['a']); + DepthFirst::traverse($this->g, $visitor, $a); } /** * @covers Gliph\Traversal\DepthFirst::traverse - * @expectedException RuntimeException + * @expectedException Gliph\Exception\RuntimeException */ public function testExceptionOnEmptyTraversalQueue() { + extract($this->v); + // Create a cycle that ensures there are no source vertices - $this->g->addDirectedEdge($this->v['d'], $this->v['a']); + $this->g->addDirectedEdge($d, $a); DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor()); } /** * @covers Gliph\Traversal\DepthFirst::traverse - * @expectedException UnexpectedValueException - * - * This relies on the graph class to internally throw an exception - * when in attempt is made to visit a vertex that is not in the graph. */ public function testProvideQueueAsStartPoint() { + extract($this->v); + $queue = new \SplQueue(); - $queue->push($this->v['a']); - $queue->push($this->v['e']); + $queue->push($a); + $queue->push($e); + + $this->g->addVertex($a); + $this->g->addVertex($e); + DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor(), $queue); } + + /** + * Cheats a bit - tests both the toposort visitor and the toposort method. + * But they're tightly coupled in code, anyway. + * + * @expectedException Gliph\Exception\RuntimeException + * Thrown by the visitor after adding a cycle to the graph. + */ + public function testToposort() { + extract($this->v); + + $this->assertEquals(array($c, $d, $b, $a), DepthFirst::toposort($this->g, $a)); + + $this->g->addDirectedEdge($d, $a); + DepthFirst::toposort($this->g, $a); + } } diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php index 00f6a1d..a2cb144 100644 --- a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php @@ -2,7 +2,7 @@ namespace Gliph\Visitor; -use Gliph\Graph\DirectedAdjacencyGraph; +use Gliph\Graph\DirectedAdjacencyList; use Gliph\TestVertex; use Gliph\Traversal\DepthFirst; @@ -16,7 +16,7 @@ class DepthFirstBasicVisitorTest extends \PHPUnit_Framework_TestCase { protected $vis; /** - * @var DirectedAdjacencyGraph + * @var DirectedAdjacencyList */ protected $g; @@ -28,10 +28,9 @@ public function setUp() { 'd' => new TestVertex('d'), 'e' => new TestVertex('e'), 'f' => new TestVertex('f'), - 'g' => new TestVertex('g'), ); - $this->g = new DirectedAdjacencyGraph(); + $this->g = new DirectedAdjacencyList(); $this->vis = new DepthFirstBasicVisitor(); $this->g->addDirectedEdge($this->v['a'], $this->v['b']); @@ -64,7 +63,7 @@ public function testTraversalWithStartPoint() { } /** - * @expectedException RuntimeException + * @expectedException Gliph\Exception\RuntimeException * @covers Gliph\Visitor\DepthFirstBasicVisitor::onBackEdge * @covers Gliph\Visitor\DepthFirstBasicVisitor::onInitializeVertex */ @@ -74,7 +73,7 @@ public function testErrorOnCycle() { } /** - * @expectedException OutOfRangeException + * @expectedException Gliph\Exception\OutOfRangeException * @covers */ public function testReachableExceptionOnUnknownVertex() {