---- middleware.py
import re
from django.conf import settings
from module_info_2_5_1 import MODULE_INFO
from components import Components
DEFAULT_BASE = 'http://yui.yahooapis.com/2.5.1/build/'
DEFAULT_JS_TAG = ''
DEFAULT_CSS_TAG = ''
YUI_BASE = getattr(settings, 'YUI_INCLUDE_BASE', DEFAULT_BASE)
PREFIX_RE = getattr(settings, 'YUI_INCLUDE_PREFIX_RE', '')
TAGS = {'js': getattr(settings, 'YUI_INCLUDE_JS_TAG', DEFAULT_JS_TAG),
'css': getattr(settings, 'YUI_INCLUDE_CSS_TAG', DEFAULT_CSS_TAG)}
VERSIONS = {'raw': '', '': '', 'min': '-min', 'debug': '-debug'}
COMPONENTS = Components(MODULE_INFO)
class YUILoader:
def __init__(self):
self._components = set()
self._rolled_up_components = {}
self._rollup_counters = {}
self.set_version('min')
def set_version(self, version):
self._version = VERSIONS[version]
def add_component(self, new_component_name):
if not self._has_component(new_component_name):
self._add_requirements(new_component_name)
self._count_in_rollups(new_component_name)
rollup_name = self._get_satisfied_rollup(new_component_name)
if rollup_name:
self.add_component(rollup_name)
else:
self._components.add(new_component_name)
self._roll_up_superseded(new_component_name)
def render(self):
return '\n'.join(self._render_component(component)
for component in self._sort_components())
def _has_component(self, component_name):
return component_name in self._components \
or component_name in self._rolled_up_components
def _get_satisfied_rollup(self, component_name):
if self._version == '-min':
for rollup_name in COMPONENTS.get_rollups(component_name):
rollup_status = self._rollup_counters.get(rollup_name, set())
if len(rollup_status) >= COMPONENTS[rollup_name].rollup:
return rollup_name
def _count_in_rollups(self, component_name):
for rollup_name in COMPONENTS.get_rollups(component_name):
rolled_up = self._rollup_counters.setdefault(rollup_name, set())
rolled_up.add(component_name)
for superseded in COMPONENTS[component_name].supersedes:
self._count_in_rollups(superseded)
def _roll_up_superseded(self, component_name):
for superseded in COMPONENTS[component_name].supersedes:
self._rolled_up_components[superseded] = component_name
if superseded in self._components:
self._components.remove(superseded)
def _add_requirements(self, component_name):
for requirement in COMPONENTS[component_name].requires:
self.add_component(requirement)
def _render_component(self, component_name):
component = COMPONENTS[component_name]
path = component.path
if component.type == 'js':
if self._version != '-min' and path.endswith('-min.js'):
path = path[:-7] + self._version + '.js'
elif component.type == 'css':
if self._version == '' and path.endswith('-min.css'):
path = path[:-8] + '.css'
return TAGS[component.type] % (YUI_BASE + path,)
def _sort_components(self, component_names=None):
if component_names is None:
comps = self._components.copy()
else:
comps = component_names
while comps:
component_name = comps.pop()
component = COMPONENTS[component_name]
direct_deps = component.requires + component.after
indirect_deps = [
self._rolled_up_components[r] for r in direct_deps
if r in self._rolled_up_components]
all_deps = set(direct_deps).union(set(indirect_deps))
deps_left = comps.intersection(all_deps)
for r in self._sort_components(deps_left):
yield r
comps.remove(r)
yield component_name
YUI_RE = re.compile(
r'%s(include|version) +(.*?)%s' % (PREFIX_RE, SUFFIX_RE))
YUI_INIT_RE = re.compile(
'%sinit%s' % (PREFIX_RE, SUFFIX_RE))
class YUIIncludeMiddleware(object):
def process_response(self, request, response):
components = set()
node = YUILoader()
def collect(match):
cmd, data = match.groups()
if cmd == 'include':
components.update(data.split())
elif cmd == 'version':
node.set_version(data)
else:
return '' % cmd
return ''
content = YUI_RE.sub(collect, response.content)
for component in components:
node.add_component(component)
content = YUI_INIT_RE.sub(node.render(), content, 1)
response.content = YUI_INIT_RE.sub(
'', content)
return response
---- components.py
class ComponentAdapter:
def __init__(self, components, name, data):
self.components = components
self.name = name
self.data = data
@property
def supersedes(self):
return self.data.get('supersedes', [])
@property
def requires(self):
r = self.data.get('requires', [])
if self.type != 'js' \
or self.name == 'yahoo' \
or 'yahoo' in self.supersedes \
or 'yahoo' in r:
return r
else:
return ['yahoo'] + r
@property
def after(self):
if self.type == 'css':
return self.data.get('after', [])
elif self.type == 'js':
return self.data.get('after', []) + \
self.components.get_css_components()
@property
def rollup(self):
return self.data.get('rollup', 1)
def __getattr__(self, attname):
return self.data[attname]
class Components(dict):
def __init__(self, components_dict):
super(Components, self).__init__(components_dict)
self.rollup_mapping = {}
for component_name, data in components_dict.items():
self.rollup_mapping.setdefault(component_name, set())
if 'rollup' not in data:
continue
for rolled_up in data['supersedes']:
self.rollup_mapping.setdefault(rolled_up, set()).add(
component_name)
def __getitem__(self, component_name):
data = super(Components, self).__getitem__(component_name)
return ComponentAdapter(self, component_name, data)
def get_rollups(self, component_name):
return self.rollup_mapping[component_name]
def get_all_rollups(self, component_name):
rollups = self.get_rollups(component_name)
for superseded in self[component_name].supersedes:
rollups.update(self.get_all_rollups(superseded))
return rollups
def get_css_components(self):
return [name for name, data in self.items()
if data['type'] == 'css']
---- module_info_2_5_1.py
MODULE_INFO = {
'animation': {'path': 'animation/animation-min.js',
'requires': ['dom', 'event'],
'type': 'js'},
'autocomplete': {'optional': ['connection', 'animation'],
'path': 'autocomplete/autocomplete-min.js',
'requires': ['dom', 'event'],
'skinnable': True,
'type': 'js'},
'base': {'after': ['reset', 'fonts', 'grids'],
'path': 'base/base-min.css',
'type': 'css'},
'button': {'optional': ['menu'],
'path': 'button/button-min.js',
'requires': ['element'],
'skinnable': True,
'type': 'js'},
'calendar': {'path': 'calendar/calendar-min.js',
'requires': ['event', 'dom'],
'skinnable': True,
'type': 'js'},
'charts': {'path': 'charts/charts-experimental-min.js',
'requires': ['element', 'json', 'datasource'],
'type': 'js'},
'colorpicker': {'optional': ['animation'],
'path': 'colorpicker/colorpicker-min.js',
'requires': ['slider', 'element'],
'skinnable': True,
'type': 'js'},
'connection': {'path': 'connection/connection-min.js',
'requires': ['event'],
'type': 'js'},
'container': {'optional': ['dragdrop', 'animation', 'connection'],
'path': 'container/container-min.js',
'requires': ['dom', 'event'],
'skinnable': True,
'supersedes': ['containercore'],
'type': 'js'},
'containercore': {'path': 'container/container_core-min.js',
'pkg': 'container',
'requires': ['dom', 'event'],
'type': 'js'},
'cookie': {'path': 'cookie/cookie-beta-min.js',
'requires': ['yahoo'],
'type': 'js'},
'datasource': {'optional': ['connection'],
'path': 'datasource/datasource-beta-min.js',
'requires': ['event'],
'type': 'js'},
'datatable': {'optional': ['calendar', 'dragdrop'],
'path': 'datatable/datatable-beta-min.js',
'requires': ['element', 'datasource'],
'skinnable': True,
'type': 'js'},
'dom': {'path': 'dom/dom-min.js', 'requires': ['yahoo'], 'type': 'js'},
'dragdrop': {'path': 'dragdrop/dragdrop-min.js',
'requires': ['dom', 'event'],
'type': 'js'},
'editor': {'optional': ['animation', 'dragdrop'],
'path': 'editor/editor-beta-min.js',
'requires': ['menu', 'element', 'button'],
'skinnable': True,
'type': 'js'},
'element': {'path': 'element/element-beta-min.js',
'requires': ['dom', 'event'],
'type': 'js'},
'event': {'path': 'event/event-min.js',
'requires': ['yahoo'],
'type': 'js'},
'fonts': {'path': 'fonts/fonts-min.css', 'type': 'css'},
'get': {'path': 'get/get-min.js', 'requires': ['yahoo'], 'type': 'js'},
'grids': {'optional': ['reset'],
'path': 'grids/grids-min.css',
'requires': ['fonts'],
'type': 'css'},
'history': {'path': 'history/history-min.js',
'requires': ['event'],
'type': 'js'},
'imagecropper': {'path': 'imagecropper/imagecropper-beta-min.js',
'requires': ['dom',
'event',
'dragdrop',
'element',
'resize'],
'skinnable': True,
'type': 'js'},
'imageloader': {'path': 'imageloader/imageloader-min.js',
'requires': ['event', 'dom'],
'type': 'js'},
'json': {'path': 'json/json-min.js',
'requires': ['yahoo'],
'type': 'js'},
'layout': {'optional': ['animation', 'dragdrop', 'resize', 'selector'],
'path': 'layout/layout-beta-min.js',
'requires': ['dom', 'event', 'element'],
'skinnable': True,
'type': 'js'},
'logger': {'optional': ['dragdrop'],
'path': 'logger/logger-min.js',
'requires': ['event', 'dom'],
'skinnable': True,
'type': 'js'},
'menu': {'path': 'menu/menu-min.js',
'requires': ['containercore'],
'skinnable': True,
'type': 'js'},
'profiler': {'path': 'profiler/profiler-beta-min.js',
'requires': ['yahoo'],
'type': 'js'},
'profilerviewer': {'path': 'profilerviewer/profilerviewer-beta-min.js',
'requires': ['profiler', 'yuiloader', 'element'],
'skinnable': True,
'type': 'js'},
'reset': {'path': 'reset/reset-min.css', 'type': 'css'},
'reset-fonts': {'path': 'reset-fonts/reset-fonts.css',
'rollup': 2,
'supersedes': ['reset', 'fonts'],
'type': 'css'},
'reset-fonts-grids': {'path': 'reset-fonts-grids/reset-fonts-grids.css',
'rollup': 4,
'supersedes': ['reset',
'fonts',
'grids',
'reset-fonts'],
'type': 'css'},
'resize': {'optional': ['animation'],
'path': 'resize/resize-beta-min.js',
'requires': ['dom', 'event', 'dragdrop', 'element'],
'skinnable': True,
'type': 'js'},
'selector': {'path': 'selector/selector-beta-min.js',
'requires': ['yahoo', 'dom'],
'type': 'js'},
'simpleeditor': {'optional': ['containercore',
'menu',
'button',
'animation',
'dragdrop'],
'path': 'editor/simpleeditor-beta-min.js',
'pkg': 'editor',
'requires': ['element'],
'skinnable': True,
'type': 'js'},
'slider': {'optional': ['animation'],
'path': 'slider/slider-min.js',
'requires': ['dragdrop'],
'type': 'js'},
'tabview': {'optional': ['connection'],
'path': 'tabview/tabview-min.js',
'requires': ['element'],
'skinnable': True,
'type': 'js'},
'treeview': {'path': 'treeview/treeview-min.js',
'requires': ['event'],
'skinnable': True,
'type': 'js'},
'uploader': {'path': 'uploader/uploader-experimental.js',
'requires': ['yahoo', 'element'],
'type': 'js'},
'utilities': {'path': 'utilities/utilities.js',
'rollup': 8,
'supersedes': ['yahoo',
'event',
'dragdrop',
'animation',
'dom',
'connection',
'element',
'yahoo-dom-event',
'get',
'yuiloader',
'yuiloader-dom-event'],
'type': 'js'},
'yahoo': {'path': 'yahoo/yahoo-min.js', 'type': 'js'},
'yahoo-dom-event': {'path': 'yahoo-dom-event/yahoo-dom-event.js',
'rollup': 3,
'supersedes': ['yahoo', 'event', 'dom'],
'type': 'js'},
'yuiloader': {'path': 'yuiloader/yuiloader-beta-min.js',
'supersedes': ['yahoo', 'get'],
'type': 'js'},
'yuiloader-dom-event': {'path': 'yuiloader-dom-event/yuiloader-dom-event.js',
'rollup': 5,
'supersedes': ['yahoo',
'dom',
'event',
'get',
'yuiloader',
'yahoo-dom-event'],
'type': 'js'},
'yuitest': {'path': 'yuitest/yuitest-min.js',
'requires': ['logger'],
'skinnable': True,
'type': 'js'}}