Source code for ew.resource

from __future__ import with_statement
import logging
import os.path
from itertools import groupby
from collections import defaultdict
from urllib import urlencode

import pkg_resources
from webhelpers.html import literal

from .widget import Widget
from .utils import LazyProperty
from .core import widget_context

log = logging.getLogger(__name__)

[docs]class ResourceManager(object): location = [ 'head_css', 'head_js', 'body_top_js', 'body_js', 'body_js_tail' ] block_size = 4096 paths = [] kwargs = {} resource_cache = {} def __init__( self, script_name='/_ew_resources/', compress=False, url_base=None, cache_max_age=60*60*24*365, use_cache=True, use_jsmin=False, use_cssmin=False): self.resources = defaultdict(list) self.script_name = script_name self.compress = compress if url_base is None: url_base = self.script_name self._url_base = url_base self.cache_max_age=cache_max_age self.use_cache = use_cache self.use_jsmin=use_jsmin self.use_cssmin=use_cssmin @classmethod
[docs] def configure(cls, **kw): cls.kwargs = kw
[docs] def register_directory(cls, url_path, directory): for up,dir in cls.paths: if up == url_path: assert dir == directory, 'Attempt to reregister %s: %s=>%s' % ( url_path, dir, directory) return cls.paths.append((url_path, directory))
[docs] def register_all_resources(cls): for ep in pkg_resources.iter_entry_points('easy_widgets.resources'):'Loading ep %s', ep) ep.load()(cls)
[docs] def url_base(self): base = self._url_base if base.startswith(':'): base = widget_context.scheme + base return base
[docs] def emit(self, location): def squash_dupes(it): seen = set() for r in it: if r.squash and r in seen: continue yield r seen.add(r) def compress(it): for (cls, compress), rs in groupby(it, key=lambda r:(type(r), r.compress)): if not compress: for r in rs: yield r else: for cr in cls.compressed(self, rs): yield cr resources = self.resources[location] resources = squash_dupes(resources) if self.compress: resources = compress(resources) yield literal('<!-- ew:%s -->\n' % location) for r in resources: yield r.display() yield literal('\n<!-- /ew:%s -->\n' % location)
[docs] def register_widgets(self, context): '''Registers all the widget/resource-type objects that exist as attrs on context''' for name in dir(context): w = getattr(context, name) if isinstance(w, (Widget, Resource)): log.disabled = 0 self.register(w)
[docs] def register(self, resource): '''Registers the required resources for the given resource/widget''' if isinstance(resource, Resource): assert resource.location in self.location, \ 'Resource.location must be one of %r' % self.location self.resources[resource.location].append(resource) # print 'Register %s as %dth resource in %s' % ( # resource, len(self.resources[resource.location]), resource.location) resource.manager = self elif isinstance(resource, Widget): for r in resource.resources(): self.register(r) else: # pragma no cover raise AssertionError, 'Unknown resource type %r' % resource
[docs] def get_filename(self, res_path): '''Translate a resource path to a filename''' for url_path, directory in self.paths: if res_path.startswith(url_path): fs_path = os.path.join( directory, res_path[len(url_path)+1:]) # Do not allow 'breaking out' of the subdirectory using ../../.., etc if not fs_path.startswith(directory): return None return fs_path return None
[docs] def serve_slim(self, file_type, href): '''Serve a 'slim' version of a file (concat+minify, if possible)''' try: return self.resource_cache[href] except KeyError: pass content = '\n'.join( open(self.get_filename(h)).read() for h in href.split(';')) if file_type == 'js' and self.use_jsmin: try: import jsmin content = jsmin.jsmin(content) except ImportError: # pragma no cover pass elif file_type == 'css' and self.use_cssmin: try: import cssmin content = cssmin.cssmin(content) except ImportError: # pragma no cover pass if self.use_cache: self.resource_cache[href] = content return content
def __repr__(self): # pragma no cover l = ['<ResourceManager>'] for name, res in self.resources.iteritems(): l.append(' <Location %s>' % name) for r in res: l.append(' %r' % r) for u,d in self.paths: l.append(' <Path url="%s" directory="%s">' % (u, d)) return '\n'.join(l)
[docs]class ResourceHolder(Widget): '''Simple widget that does nothing but hold resources''' def __init__(self, *resources): self._resources = resources
[docs] def resources(self): return self._resources
[docs]class Resource(object): def __init__(self, location, squash=True, compress=True): self.location = location self.squash = squash self.compress = compress self.manager = None self.widget = None
[docs] def display(self): return self.widget.display()
[docs] def compressed(cls, manager, resources): return resources
[docs]class ResourceScript(Resource): file_type=None def __init__(self, text, location, squash, compress): self.text = text super(ResourceScript, self).__init__(location, squash, compress) def __hash__(self): return (hash(self.text) + hash(self.location) + hash(self.squash) + hash(self.compress)) def __eq__(self, o): return (self.__class__ == o.__class__ and self.text == o.text and self.location == o.location and self.squash == o.squash and self.compress == o.compress) @classmethod
[docs] def compressed(cls, manager, resources): text = '\n'.join(r.text for r in resources) yield cls(text)
[docs]class JSScript(ResourceScript): file_type='js' WidgetClass=None def __init__(self, text, location='body_js_tail', squash=True, compress=True): super(JSScript, self).__init__(text, location, squash, compress) del self.widget @LazyProperty def widget(self): return self.WidgetClass(text=self.text)
[docs]class CSSScript(ResourceScript): file_type='css' WidgetClass=None def __init__(self, text): super(CSSScript, self).__init__(text, 'head_css', True, True) del self.widget @LazyProperty def widget(self): return self.WidgetClass(text=self.text)
[docs]class GoogleAnalytics(Resource): WidgetClass=None def __init__(self, account): self.account = account super(GoogleAnalytics, self).__init__('head_js', True) del self.widget @LazyProperty def widget(self): return self.WidgetClass(account=self.account)