[Checkins] SVN: z3c.resourceinclude/trunk/ Added merge functionality.

Malthe Borch mborch at gmail.com
Mon Mar 3 07:39:07 EST 2008


Log message for revision 84436:
  Added merge functionality.

Changed:
  U   z3c.resourceinclude/trunk/README.txt
  U   z3c.resourceinclude/trunk/setup.py
  U   z3c.resourceinclude/trunk/z3c/resourceinclude/collector.py
  U   z3c.resourceinclude/trunk/z3c/resourceinclude/configure.zcml

-=-
Modified: z3c.resourceinclude/trunk/README.txt
===================================================================
--- z3c.resourceinclude/trunk/README.txt	2008-03-03 11:22:45 UTC (rev 84435)
+++ z3c.resourceinclude/trunk/README.txt	2008-03-03 12:39:06 UTC (rev 84436)
@@ -2,6 +2,8 @@
 --------
 
 
+The package is able to include the following types of resources:
+
 * Cascading stylesheets (.css)
 * Kinetic stylesheets (.kss)
 * Javascript (.js)
@@ -49,3 +51,9 @@
 Resources will be included in the order they're registered for
 inclusion; within an include-definition, order is respected only for
 resources of similar kind.
+
+Merging
+-------
+
+When not in 'devmode', the resource collector will automatically merge
+resources, giving them a filename based on the contents (sha digest).

Modified: z3c.resourceinclude/trunk/setup.py
===================================================================
--- z3c.resourceinclude/trunk/setup.py	2008-03-03 11:22:45 UTC (rev 84435)
+++ z3c.resourceinclude/trunk/setup.py	2008-03-03 12:39:06 UTC (rev 84436)
@@ -1,7 +1,7 @@
 from setuptools import setup, find_packages
 import sys, os
 
-version = '0.1'
+version = '0.2'
 
 setup(name='z3c.resourceinclude',
       version=version,

Modified: z3c.resourceinclude/trunk/z3c/resourceinclude/collector.py
===================================================================
--- z3c.resourceinclude/trunk/z3c/resourceinclude/collector.py	2008-03-03 11:22:45 UTC (rev 84435)
+++ z3c.resourceinclude/trunk/z3c/resourceinclude/collector.py	2008-03-03 12:39:06 UTC (rev 84436)
@@ -2,10 +2,104 @@
 from zope import component
 
 from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.browser import TestRequest
+from zope.app.publisher.browser.resource import Resource
+from zope.publisher.interfaces import NotFound
+from zope.datetime import rfc1123_date
+from zope.datetime import time as timeFromDateTimeString
 
 from interfaces import IResourceCollector
 from interfaces import IResourceManager
 
+import mimetypes
+import tempfile
+import sha
+import time
+
+class TemporaryResource(Resource):
+    interface.implements(IBrowserPublisher)
+
+    def __init__(self, request, f, content_type, lmt):
+        self.request = request
+        self.__file = f
+        self.content_type = content_type
+        self.lmt = lmt
+        self.context = self
+        
+    def publishTraverse(self, request, name):
+        '''See interface IBrowserPublisher'''
+        raise NotFound(None, name)
+
+    def browserDefault(self, request):
+        '''See interface IBrowserPublisher'''
+        return getattr(self, request.method), ()
+
+    def GET(self):
+        lmt = self.lmt
+
+        request = self.request
+        response = request.response
+
+        # HTTP If-Modified-Since header handling. This is duplicated
+        # from OFS.Image.Image - it really should be consolidated
+        # somewhere...
+        header = request.getHeader('If-Modified-Since', None)
+        if header is not None:
+            header = header.split(';')[0]
+            # Some proxies seem to send invalid date strings for this
+            # header. If the date string is not valid, we ignore it
+            # rather than raise an error to be generally consistent
+            # with common servers such as Apache (which can usually
+            # understand the screwy date string as a lucky side effect
+            # of the way they parse it).
+            try:    mod_since=long(timeFromDateTimeString(header))
+            except: mod_since=None
+            if mod_since is not None:
+                last_mod = long(lmt)
+                if last_mod > 0 and last_mod <= mod_since:
+                    response.setStatus(304)
+                    return ''
+
+        # set response headers
+        response.setHeader('Content-Type', self.content_type)
+        response.setHeader('Last-Modified', rfc1123_date(lmt))
+
+        secs = 31536000 # one year - never expire
+        t = time.time() + secs
+        response.setHeader('Cache-Control', 'public,max-age=%s' % secs)
+        response.setHeader(
+            'Expires', time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(t)))
+
+        f = self.__file
+        f.seek(0)
+        return f.read()
+
+class TemporaryResourceFactory(object):
+    def __init__(self, f, checker, content_type, name):
+        self.__file = f
+        self.__name = name
+        self.__checker = checker
+        self.content_type = content_type
+        self.lmt = time.time()
+        
+    def __call__(self, request):
+        resource = TemporaryResource(request, self.__file, self.content_type, self.lmt)
+        resource.__Security_checker__ = self.__checker
+        resource.__name__ = self.__name
+        return resource
+
+class TemporaryRequest(TestRequest):
+    def __init__(self, request):
+        self.__request = request
+        TestRequest.__init__(self)
+
+    def getURL(self, *args, **kwargs):
+        return self.__request.getURL(*args, **kwargs)
+
+    def getApplicationURL(self, *args, **kwargs):
+        return self.__request.getApplicationURL(*args, **kwargs)
+
 class ResourceCollector(object):
     interface.implements(IResourceCollector)
     component.adapts(IBrowserRequest)
@@ -16,9 +110,11 @@
     def collect(self):
         resources = []
         names = []
+
+        request = TemporaryRequest(self.request)
         
         for name, manager in self._get_managers():
-            items = manager.getResources(self.request)
+            items = manager.getResources(request)
 
             # filter out duplicates
             rs = [resource for name, resource in items if name not in names]
@@ -35,13 +131,11 @@
         return tuple(resources)
 
     def sort(self, resources):
-        # order resources by content type
         resources.sort(key=lambda resource: resource.context.content_type)
-        return resources
 
     def merge(self, resources):
-        return resources
-
+        pass
+    
     def _get_managers(self):
         managers = [(name, manager) for name, manager in \
                     component.getAdapters((self.request,), IResourceManager) if \
@@ -50,3 +144,46 @@
         managers.sort(key=lambda (name, manager): name)
 
         return managers
+
+class DigestResourceCollector(ResourceCollector):
+    def merge(self, resources):
+        by_type = {}
+        for resource in resources:
+            by_type.setdefault(resource.context.content_type, []).append(resource)
+
+        del resources[:]
+        merged = resources
+        
+        for content_type, resources in by_type.items():
+            f = tempfile.TemporaryFile()
+
+            for resource in resources:
+                method, views = resource.browserDefault(self.request)
+
+                print >> f, "/* %s */" % resource.__name__
+                f.write(method())
+                print >> f, ""
+
+            # generate filename
+            ext = mimetypes.guess_extension(content_type)
+            f.seek(0)
+            digest = sha.new(f.read()).hexdigest()
+            name = digest + ext
+
+            # check if resource is already registered
+            res = component.queryAdapter((self.request,), name=name)
+
+            if res is None:
+                # adopt last resource's security checker
+                checker = resource.__Security_checker__
+            
+                factory = TemporaryResourceFactory(
+                    f, checker, content_type, name)
+
+                # register factory
+                component.provideAdapter(
+                    factory, (IBrowserRequest,), interface.Interface, name=name)
+
+                res = factory(self.request)
+
+            merged.append(res)

Modified: z3c.resourceinclude/trunk/z3c/resourceinclude/configure.zcml
===================================================================
--- z3c.resourceinclude/trunk/z3c/resourceinclude/configure.zcml	2008-03-03 11:22:45 UTC (rev 84435)
+++ z3c.resourceinclude/trunk/z3c/resourceinclude/configure.zcml	2008-03-03 12:39:06 UTC (rev 84436)
@@ -1,5 +1,12 @@
-<configure xmlns="http://namespaces.zope.org/zope">
+<configure xmlns="http://namespaces.zope.org/zope"
+	   xmlns:zcml="http://namespaces.zope.org/zcml">
 
-  <adapter factory=".collector.ResourceCollector" />
-  
+  <adapter
+     zcml:condition="have devmode"
+     factory=".collector.ResourceCollector" />
+
+  <adapter
+     zcml:condition="not-have devmode"
+     factory=".collector.DigestResourceCollector" />
+
 </configure>



More information about the Checkins mailing list