Source code for coaster.assets
# -*- coding: utf-8 -*-
import re
from collections import defaultdict
# Version is not used here but is made available for others to import from
from semantic_version import Version, Spec
from webassets import Bundle
_VERSION_SPECIFIER_RE = re.compile('[<=>!]')
__all__ = ['Version', 'Spec', 'VersionedAssets']
def split_namespec(namespec):
find_mark = _VERSION_SPECIFIER_RE.search(namespec)
if find_mark is None:
name = namespec
spec = Spec()
else:
name = namespec[:find_mark.start()]
spec = Spec(namespec[find_mark.start():])
return name, spec
[docs]class VersionedAssets(defaultdict):
"""
Semantic-versioned assets. Usage::
>>> assets = VersionedAssets()
Schema: ``assets['name'] = {'version': Bundle(), ...}``
Also: ``assets['name'] = {'version': (dependency, ..., Bundle()), ...}``
where 'dependency' is in the form 'jquery>=1.7.0', etc
Use ``assets.require(name+version_spec)`` to retrieve the required version bundle
"""
def __init__(self):
# Override dict's __init__ to prevent parameters
super(VersionedAssets, self).__init__(dict)
def _require_recursive(self, *namespecs):
asset_versions = {} # Name: version
bundles = []
for namespec in namespecs:
name, spec = split_namespec(namespec)
version = spec.select(self[name].keys())
if version:
if name in asset_versions:
if asset_versions[name] not in spec:
raise ValueError("%s does not match already requested asset %s==%s" % (
namespec, name, asset_versions[name]))
else:
asset = self[name][version]
if isinstance(asset, (list, tuple)):
# We have (requires, bundle). Get requirements
requires = asset[:-1]
provides = []
bundle = asset[-1]
elif isinstance(asset, dict):
requires = asset.get('requires', [])
provides = asset.get('provides', [])
if isinstance(provides, basestring):
provides = [provides]
bundle = asset['bundle']
else:
provides = []
requires = []
bundle = asset
filtered_requires = []
for req in requires:
req_name, req_spec = split_namespec(req)
if req_name in asset_versions:
if asset_versions[req_name] not in req_spec:
# The version asked for conflicts with a version currently used.
raise ValueError("%s is not compatible with already requested version %s" % (
req, asset_versions[req_name]))
else:
filtered_requires.append(req)
# Get these requirements
req_bundles = self._require_recursive(*filtered_requires)
bundles.extend(req_bundles)
# Save list of provided assets
for provided in provides:
if provided not in asset_versions:
asset_versions[provided] = version
for req_name, req_version, req_bundle in req_bundles:
asset_versions[req_name] = req_version
if bundle is not None:
bundles.append((name, version, bundle))
return bundles
[docs] def require(self, *namespec):
return Bundle(*[bundle for name, version, bundle in self._require_recursive(*namespec)])