[Checkins] SVN: hurry.resource/trunk/src/hurry/resource/ Massive refactoring:
Martijn Faassen
faassen at infrae.com
Tue Sep 23 13:30:20 EDT 2008
Log message for revision 91401:
Massive refactoring:
* merge ResourceSpec and Inclusion into single ResourceInclusion
* simpler approach to dependencies depending on Python's stable sort.
Changed:
U hurry.resource/trunk/src/hurry/resource/README.txt
U hurry.resource/trunk/src/hurry/resource/__init__.py
U hurry.resource/trunk/src/hurry/resource/core.py
U hurry.resource/trunk/src/hurry/resource/interfaces.py
-=-
Modified: hurry.resource/trunk/src/hurry/resource/README.txt
===================================================================
--- hurry.resource/trunk/src/hurry/resource/README.txt 2008-09-23 17:10:14 UTC (rev 91400)
+++ hurry.resource/trunk/src/hurry/resource/README.txt 2008-09-23 17:30:19 UTC (rev 91401)
@@ -42,79 +42,76 @@
We now create an inclusion of a particular resource in a library. This
inclusion needs ``a.js`` from ``library`` and ``b.js`` as well::
- >>> from hurry.resource import Inclusion, ResourceSpec
- >>> x = Inclusion([ResourceSpec(foo, 'a.js'),
- ... ResourceSpec(foo, 'b.css')])
+ >>> from hurry.resource import ResourceInclusion
+ >>> x1 = ResourceInclusion(foo, 'a.js')
+ >>> x2 = ResourceInclusion(foo, 'b.css')
-Let's examine the resource specs in this inclusion::
+Let's now make an inclusion ``y1`` that depends on ``x1`` and ``x2``::
- >>> x.resources_of_ext('.css')
- [<Resource 'b.css' in library 'foo'>]
+ >>> y1 = ResourceInclusion(foo, 'c.js', depends=[x1, x2])
- >>> x.resources_of_ext('.js')
- [<Resource 'a.js' in library 'foo'>]
+Inclusion requirements
+----------------------
-Let's now make an inclusion ``y`` that depends on ``x``, but also includes
-some other resources itself::
+When rendering a web page we want to require the inclusion of a
+resource anywhere within the request handling process. We might for
+instance have a widget that takes care of rendering its own HTML but
+also needs a resource to be included in the page header.
- >>> y = Inclusion([ResourceSpec(foo, 'c.js'),
- ... ResourceSpec(foo, 'd.css')], depends=[x])
+We have a special object that represents the needed inclusions during
+a certain request cycle::
- >>> y.resources_of_ext('.css')
- [<Resource 'b.css' in library 'foo'>, <Resource 'd.css' in library 'foo'>]
+ >>> from hurry.resource import NeededInclusions
+ >>> needed = NeededInclusions()
- >>> y.resources_of_ext('.js')
- [<Resource 'a.js' in library 'foo'>, <Resource 'c.js' in library 'foo'>]
+We state that a resource is needed by calling the ``needed`` method on
+this object::
-As we can see the resources required by the dependency are sorted
-before the resources listed in this inclusion.
+ >>> needed.need(y1)
-Inclusion requirements
-----------------------
+Let's now see what resources are needed by this inclusion::
-We can also require an inclusion in a particular code path, using
-``inclusion.need()``. This mean that this inclusion is added to the
-inclusions that should be on the page template when it is rendered.
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
- >>> from hurry.resource import NeededInclusions
- >>> needed = NeededInclusions()
- >>> needed.need(y)
+As you can see, ``css`` resources are sorted before ``js`` resources.
-Let's now see what resources are needed::
+A convenience spelling
+----------------------
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+When specifying that we want a resource inclusion to be rendered, we
+now need access to the current ``NeededInclusions`` object and the
+resource inclusion itself.
-A simplified spelling
----------------------
-
Let's introduce a more convenient spelling of needs now::
- y.need()
+ y1.need()
-This can be done without reference to the needed inclusions directly
-as there is typically only a single set of needed inclusions that is
-generated during the rendering of a page. Let's try it::
+We can require a resource without reference to the needed inclusions
+object directly as there is typically only a single set of needed
+inclusions that is generated during the rendering of a page.
- >>> y.need()
+So let's try out this spelling to see it fail::
+
+ >>> y1.need()
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass hurry.resource.interfaces.ICurrentNeededInclusions>, '')
-We get an error. The system says it cannot find the component
-``ICurrentNeededInclusions``. This is the component we need to define
-in order to specify how the system can know what the
-``INeededInclusions`` object is that the inclusion ``y`` should be
-added to. So, our task is to provide a ``ICurrentNeededInclusions``
-component that can give us the current needed inclusions object.
+We get an error because we haven't configured the framework yet. The
+system says it cannot find the utility component
+``ICurrentNeededInclusions``. This is the utility we need to define
+and register so we can tell the system how to obtain the current
+``NeededInclusions`` object. Our task is therefore to provide a
+``ICurrentNeededInclusions`` utility that can give us the current
+needed inclusions object.
This needed inclusions should be maintained on an object that is going
to be present throughout the request/response cycle that generates the
-web page that has the inclusions on them. The most obvious location on
-which to maintain the needed inclusions the request object
+web page that has the inclusions on them. The most obvious place where
+we can maintain the needed inclusions is the request object
itself. Let's introduce such a simple request object (your mileage may
vary in your own web framework)::
@@ -127,129 +124,167 @@
>>> request = Request()
-We now should define a ``ICurrentNeededInclusion`` utility that knows
-how to get the current needed inclusions from that request::
+We should define a ``ICurrentNeededInclusion`` utility that knows how
+to get the current needed inclusions from that request::
>>> def currentNeededInclusions():
... return request.needed
>>> c = currentNeededInclusions
-We now need to register the utility to complete plugging into our pluggability
-point::
+We need to register the utility to complete plugging into the
+``INeededInclusions`` pluggability point of the ``hurry.resource``
+framework::
>>> from zope import component
>>> from hurry.resource.interfaces import ICurrentNeededInclusions
>>> component.provideUtility(currentNeededInclusions,
... ICurrentNeededInclusions)
-Okay, let's check which resources our request needs currently::
+Let's check which resources our request needs currently::
- >>> c().resources()
+ >>> c().inclusions()
[]
-Nothing yet. So, let's now make ``y`` needed using our simplified
-spelling::
+Nothing yet. We now make ``y1`` needed using our simplified spelling::
- >>> y.need()
+ >>> y1.need()
The resource inclusion will now indeed be needed::
- >>> c().resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> c().inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
-By the way, we have a handy reference to ``c`` to get us the current
-needed inclusions, but that doesn't work as soon as we lose that
-reference. Here is how can get to it in general::
+In this document we already have a handy reference to ``c`` to obtain
+the current needed inclusions, but that doesn't work in a larger
+codebase. How do we get this reference back, for instance to obtain those
+resources needed? Here is how you can obtain a utility by hand::
>>> c_retrieved = component.getUtility(ICurrentNeededInclusions)
>>> c_retrieved is c
True
+Let's go back to the original spelling of ``needed.need(y)``
+now. While this is a bit more cumbersome to use in application code, it is
+easier to read for the purposes of this document.
+
Multiple requirements
---------------------
-Let's go back to the original spelling of ``needed.need(y)`` now. This
-is a bit more cumbersome in application code, but more clear to read
-in this document.
+In this section, we will show what happens in various scenarios where
+we requiring multiple ``ResourceInclusion`` objects.
-Let's create a new set of needed inclusions::
+We create a new set of needed inclusions::
>>> needed = NeededInclusions()
- >>> needed.resources()
+ >>> needed.inclusions()
[]
-We need ``y`` again::
+We need ``y1`` again::
- >>> needed.need(y)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(y1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
Needing the same inclusion twice won't make any difference for the
-resources needed. Let's try needing ``y`` againx::
+resources needed. So when we need ``y1`` again, we see no difference
+in the needed resources::
- >>> needed.need(y)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(y1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
-Needing ``x`` won't make any difference either, as ``y`` already
-required ``x``::
+Needing ``x1`` or ``x2`` won't make any difference either, as ``y1``
+already required ``x1`` and ``x2``::
- >>> needed.need(x)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(x1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
+ >>> needed.need(x2)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
-Let's do it in the reverse, and require the ``x`` resources before we
-need those in ``y``. Again this makes no difference::
+Let's do it in reverse, and require the ``x1`` and ``x2`` resources
+before we need those in ``y1``. Again this makes no difference::
>>> needed = NeededInclusions()
- >>> needed.need(x)
- >>> needed.need(y)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(x1)
+ >>> needed.need(x2)
+ >>> needed.need(y1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
-Let's introduce an inclusion that also needs ``d.css``::
+Let's try it with more complicated dependency structures now::
- >>> z = Inclusion([ResourceSpec(foo, 'd.css')])
+ >>> needed = NeededInclusions()
+ >>> a1 = ResourceInclusion(foo, 'a1.js')
+ >>> a2 = ResourceInclusion(foo, 'a2.js', depends=[a1])
+ >>> a3 = ResourceInclusion(foo, 'a3.js', depends=[a2])
+ >>> a4 = ResourceInclusion(foo, 'a4.js', depends=[a1])
+ >>> a5 = ResourceInclusion(foo, 'a5.js', depends=[a4, a3])
+ >>> needed.need(a3)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a1.js' in library 'foo'>,
+ <ResourceInclusion 'a2.js' in library 'foo'>,
+ <ResourceInclusion 'a3.js' in library 'foo'>]
+ >>> needed.need(a4)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a1.js' in library 'foo'>,
+ <ResourceInclusion 'a2.js' in library 'foo'>,
+ <ResourceInclusion 'a3.js' in library 'foo'>,
+ <ResourceInclusion 'a4.js' in library 'foo'>]
-We'll also require this. Again this makes no difference::
+If we reverse the requirements for ``a4`` and ``a3``, we get the following
+inclusion structure, based on the order in which need was expressed::
- >>> needed.need(z)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed = NeededInclusions()
+ >>> needed.need(a4)
+ >>> needed.need(a3)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a1.js' in library 'foo'>,
+ <ResourceInclusion 'a4.js' in library 'foo'>,
+ <ResourceInclusion 'a2.js' in library 'foo'>,
+ <ResourceInclusion 'a3.js' in library 'foo'>]
-We can also state the need for ``z`` first, then for ``y``::
+Let's look at the order in which resources are listed when we need
+something that ends up depending on everything::
+ >>> a5 = ResourceInclusion(foo, 'a5.js', depends=[a4, a3])
>>> needed = NeededInclusions()
- >>> needed.need(z)
- >>> needed.need(y)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(a5)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a1.js' in library 'foo'>,
+ <ResourceInclusion 'a4.js' in library 'foo'>,
+ <ResourceInclusion 'a2.js' in library 'foo'>,
+ <ResourceInclusion 'a3.js' in library 'foo'>,
+ <ResourceInclusion 'a5.js' in library 'foo'>]
-The sort order is still the same as before; inclusions with less depth
-are sorted after ones with more depth.
+When we introduce the extra inclusion of ``a3`` earlier on, we still
+get a valid list of inclusions given the dependency structure, even
+though the sorting order is different::
+ >>> needed = NeededInclusions()
+ >>> needed.need(a3)
+ >>> needed.need(a5)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a1.js' in library 'foo'>,
+ <ResourceInclusion 'a2.js' in library 'foo'>,
+ <ResourceInclusion 'a3.js' in library 'foo'>,
+ <ResourceInclusion 'a4.js' in library 'foo'>,
+ <ResourceInclusion 'a5.js' in library 'foo'>]
+
Modes
-----
@@ -257,44 +292,46 @@
a minified and a debug version. Let's define a resource that exists in
two modes (a main one and a debug alternative)::
- >>> a1 = ResourceSpec(foo, 'a.js', debug='a-debug.js')
+ >>> a1 = ResourceInclusion(foo, 'a.js', debug='a-debug.js')
Let's need this resource::
- >>> inclusion = Inclusion([a1])
>>> needed = NeededInclusions()
- >>> needed.need(inclusion)
+ >>> needed.need(a1)
By default, we get ``a.js``::
- >>> needed.resources()
- [<Resource 'a.js' in library 'foo'>]
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a.js' in library 'foo'>]
We can however also get the resource for mode ``debug`` and get
``a-debug.js``::
- >>> needed.resources(mode='debug')
- [<Resource 'a-debug.js' in library 'foo'>]
+ >>> needed.inclusions(mode='debug')
+ [<ResourceInclusion 'a-debug.js' in library 'foo'>]
-Modes can also be specified fully with a resource spec, which allows
+Modes can also be specified fully with a resource inclusion, which allows
you to specify a different ``library`` and ``part_of`` argumnent::
- >>> a2 = ResourceSpec(foo, 'a2.js', debug=ResourceSpec(foo, 'a2-debug.js'))
- >>> inclusion = Inclusion([a2])
+ >>> a2 = ResourceInclusion(foo, 'a2.js',
+ ... debug=ResourceInclusion(foo, 'a2-debug.js'))
>>> needed = NeededInclusions()
- >>> needed.need(inclusion)
+ >>> needed.need(a2)
By default we get ``a2.js``::
- >>> needed.resources()
- [<Resource 'a2.js' in library 'foo'>]
+ >>> needed.inclusions()
+ [<ResourceInclusion 'a2.js' in library 'foo'>]
We can however also get the resource for mode ``debug`` and get
``a2-debug.js``::
- >>> needed.resources(mode='debug')
- [<Resource 'a2-debug.js' in library 'foo'>]
+ >>> needed.inclusions(mode='debug')
+ [<ResourceInclusion 'a2-debug.js' in library 'foo'>]
+Note that modes are assumed to be identical in dependency structure;
+they functionally should do the same.
+
"Rollups"
---------
@@ -304,71 +341,74 @@
single, larger one. These consolidations can be specified when
specifying the resource::
- >>> b1 = ResourceSpec(foo, 'b1.js', part_of=['giant.js'])
- >>> b2 = ResourceSpec(foo, 'b2.js', part_of=['giant.js'])
+ >>> b1 = ResourceInclusion(foo, 'b1.js', rollups=['giant.js'])
+ >>> b2 = ResourceInclusion(foo, 'b2.js', rollups=['giant.js'])
If we find multiple resources that are also part of a consolidation, the
system automatically collapses them::
- >>> inclusion1 = Inclusion([b1])
- >>> inclusion2 = Inclusion([b2])
>>> needed = NeededInclusions()
- >>> needed.need(inclusion1)
- >>> needed.need(inclusion2)
+ >>> needed.need(b1)
+ >>> needed.need(b2)
- >>> needed.resources()
- [<Resource 'giant.js' in library 'foo'>]
+ >>> needed.inclusions()
+ [<ResourceInclusion 'giant.js' in library 'foo'>]
-Consolidation will not take place if only a single resource in a consolidation
-is present::
+Consolidation will not take place if only a single resource in a
+consolidation is present::
>>> needed = NeededInclusions()
- >>> needed.need(inclusion1)
- >>> needed.resources()
- [<Resource 'b1.js' in library 'foo'>]
+ >>> needed.need(b1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b1.js' in library 'foo'>]
-``part_of`` can also be expressed as a list of fully specified
-``ResourceSpec``::
+``rollups`` can also be expressed as a list of fully specified
+``ResourceInclusion``::
- >>> b3 = ResourceSpec(foo, 'b3.js', part_of=[ResourceSpec(foo, 'giant.js')])
- >>> inclusion3 = Inclusion([b1, b2, b3])
+ >>> b3 = ResourceInclusion(foo, 'b3.js',
+ ... rollups=[ResourceInclusion(foo, 'giant.js')])
>>> needed = NeededInclusions()
- >>> needed.need(inclusion3)
- >>> needed.resources()
- [<Resource 'giant.js' in library 'foo'>]
+ >>> needed.need(b1)
+ >>> needed.need(b2)
+ >>> needed.need(b3)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'giant.js' in library 'foo'>]
-Consolidation also can work with modes::
+Consolidation also works with modes::
- >>> b4 = ResourceSpec(foo, 'b4.js',
- ... part_of=['giant.js'],
- ... debug=ResourceSpec(foo, 'b4-debug.js', part_of=['giant-debug.js']))
+ >>> b4 = ResourceInclusion(foo, 'b4.js',
+ ... rollups=['giant.js'],
+ ... debug=ResourceInclusion(foo, 'b4-debug.js',
+ ... rollups=['giant-debug.js']))
- >>> b5 = ResourceSpec(foo, 'b5.js',
- ... part_of=['giant.js'],
- ... debug=ResourceSpec(foo, 'b5-debug.js', part_of=['giant-debug.js']))
+ >>> b5 = ResourceInclusion(foo, 'b5.js',
+ ... rollups=['giant.js'],
+ ... debug=ResourceInclusion(foo, 'b5-debug.js',
+ ... rollups=['giant-debug.js']))
- >>> inclusion4 = Inclusion([b4, b5])
>>> needed = NeededInclusions()
- >>> needed.need(inclusion4)
- >>> needed.resources()
- [<Resource 'giant.js' in library 'foo'>]
- >>> needed.resources(mode='debug')
- [<Resource 'giant-debug.js' in library 'foo'>]
+ >>> needed.need(b4)
+ >>> needed.need(b5)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'giant.js' in library 'foo'>]
+ >>> needed.inclusions(mode='debug')
+ [<ResourceInclusion 'giant-debug.js' in library 'foo'>]
A resource can be part of multiple rollups. In this case the rollup that
rolls up the most resources is used::
- >>> b6 = ResourceSpec(foo, 'b6.js',
- ... part_of=['giant.js', 'even_bigger.js'])
- >>> b7 = ResourceSpec(foo, 'b7.js',
- ... part_of=['giant.js', 'even_bigger.js'])
- >>> b8 = ResourceSpec(foo, 'b8.js',
- ... part_of=['even_bigger.js'])
- >>> inclusion5 = Inclusion([b6, b7, b8])
+ >>> b6 = ResourceInclusion(foo, 'b6.js',
+ ... rollups=['giant.js', 'even_bigger.js'])
+ >>> b7 = ResourceInclusion(foo, 'b7.js',
+ ... rollups=['giant.js', 'even_bigger.js'])
+ >>> b8 = ResourceInclusion(foo, 'b8.js',
+ ... rollups=['even_bigger.js'])
>>> needed = NeededInclusions()
- >>> needed.need(inclusion5)
- >>> needed.resources()
- [<Resource 'even_bigger.js' in library 'foo'>]
+ >>> needed.need(b6)
+ >>> needed.need(b7)
+ >>> needed.need(b8)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'even_bigger.js' in library 'foo'>]
Rendering resources
-------------------
@@ -376,41 +416,39 @@
Let's define some needed resource inclusions::
>>> needed = NeededInclusions()
- >>> needed.need(y)
- >>> needed.resources()
- [<Resource 'b.css' in library 'foo'>,
- <Resource 'd.css' in library 'foo'>,
- <Resource 'a.js' in library 'foo'>,
- <Resource 'c.js' in library 'foo'>]
+ >>> needed.need(y1)
+ >>> needed.inclusions()
+ [<ResourceInclusion 'b.css' in library 'foo'>,
+ <ResourceInclusion 'a.js' in library 'foo'>,
+ <ResourceInclusion 'c.js' in library 'foo'>]
Now let's try to render these inclusions::
>>> print needed.render()
Traceback (most recent call last):
...
- ComponentLookupError: (<InterfaceClass hurry.resource.interfaces.IResourceUrl>, '')
+ ComponentLookupError: (<InterfaceClass hurry.resource.interfaces.IInclusionUrl>, '')
-That didn't work. In order to render a resource, we need to tell
-``hurry.resource`` how to get the URL for a resource specification. So
-let's define a function that renders resources as some static URL on
-localhost::
+That didn't work. In order to render an inclusion, we need to tell
+``hurry.resource`` how to get the URL for a resource inclusion.
- >>> def get_resource_url(resource):
+For the purposes of this document, we define a function that renders
+resources as some static URL on localhost::
+
+ >>> def get_inclusion_url(inclusion):
... return 'http://localhost/static/%s/%s' % (
- ... resource.library.name, resource.relpath)
+ ... inclusion.library.name, inclusion.relpath)
-We should now register this function as a``IResourceUrl`` utility so the system
-can find it::
+We should now register this function as a``IInclusionUrl`` utility so
+the system can find it::
- >>> from hurry.resource.interfaces import IResourceUrl
- >>> component.provideUtility(get_resource_url,
- ... IResourceUrl)
+ >>> from hurry.resource.interfaces import IInclusionUrl
+ >>> component.provideUtility(get_inclusion_url,
+ ... IInclusionUrl)
-Rendering the resources now will will result in the HTML fragment we need::
+Rendering the inclusions now will will result in the HTML fragment we need::
>>> print needed.render()
<link rel="stylesheet" type="text/css" href="http://localhost/static/foo/b.css" />
- <link rel="stylesheet" type="text/css" href="http://localhost/static/foo/d.css" />
<script type="text/javascript" src="http://localhost/static/foo/a.js"></script>
<script type="text/javascript" src="http://localhost/static/foo/c.js"></script>
-
Modified: hurry.resource/trunk/src/hurry/resource/__init__.py
===================================================================
--- hurry.resource/trunk/src/hurry/resource/__init__.py 2008-09-23 17:10:14 UTC (rev 91400)
+++ hurry.resource/trunk/src/hurry/resource/__init__.py 2008-09-23 17:30:19 UTC (rev 91401)
@@ -1,2 +1 @@
-from hurry.resource.core import (Library, Inclusion, ResourceSpec,
- NeededInclusions)
+from hurry.resource.core import (Library, ResourceInclusion, NeededInclusions)
Modified: hurry.resource/trunk/src/hurry/resource/core.py
===================================================================
--- hurry.resource/trunk/src/hurry/resource/core.py 2008-09-23 17:10:14 UTC (rev 91400)
+++ hurry.resource/trunk/src/hurry/resource/core.py 2008-09-23 17:30:19 UTC (rev 91401)
@@ -14,43 +14,55 @@
def __init__(self, name):
self.name = name
-class ResourceSpec(object):
- """Resource specification
-
- A resource specification specifies a single resource in a library.
+class ResourceInclusion(object):
+ """Resource inclusion
+
+ A resource inclusion specifies how to include a single resource in
+ a library.
"""
- implements(interfaces.IResourceSpec)
+ implements(interfaces.IResourceInclusion)
- def __init__(self, library, relpath, part_of=None, **kw):
- """Create a resource specification
+ def __init__(self, library, relpath, depends=None, rollups=None, **kw):
+ """Create a resource inclusion
library - the library this resource is in
relpath - the relative path from the root of the library indicating
the actual resource
- part_of - optionally, a resource is also part of a larger
- consolidated resource. This can be a list of paths to the
- larger resource in the same library. A list entry can also
- be a fully specified ResourceSpec.
- key word arguments - different paths that represent the same
+ depends - optionally, a list of resources that this resource depends
+ on. Entries in the list can be
+ ResourceInclusions or strings indicating the path.
+ In case of a string, a ResourceInclusion assumed based
+ on the same library as this inclusion.
+ rollups - optionally, a list of resources that this resource can
+ be rolled up into. Entries in the list can be
+ ResourceInclusions or strings indicating the path.
+ In case of a string, a ResourceInclusion assumed based
+ on the same library as this inclusion.
+ keyword arguments - different paths that represent the same
resource in different modes (debug, minified, etc),
- or alternatively a fully specified ResourceSpec.
+ or alternatively a fully specified ResourceInclusion.
"""
self.library = library
self.relpath = relpath
- assert not isinstance(part_of, basestring)
- part_of = part_of or []
- normalized_part_of = []
- for part in part_of:
- if isinstance(part, ResourceSpec):
- normalized_part_of.append(part)
- else:
- normalized_part_of.append(
- ResourceSpec(self.library, part))
- self.part_of = normalized_part_of
+ assert not isinstance(rollups, basestring)
+ rollups = rollups or []
+ self.rollups = normalize_inclusions(library, rollups)
+
+ assert not isinstance(depends, basestring)
+ depends = depends or []
+ self.depends = normalize_inclusions(library, depends)
+
+ normalized_modes = {}
+ for mode_name, inclusion in kw.items():
+ normalized_modes[mode_name] = normalize_inclusion(
+ library, inclusion)
+ self.modes = normalized_modes
- self.modes = kw
-
+ def __repr__(self):
+ return "<ResourceInclusion '%s' in library '%s'>" % (
+ self.relpath, self.library.name)
+
def ext(self):
name, ext = os.path.splitext(self.relpath)
return ext
@@ -60,57 +72,38 @@
return self
# try getting the alternative
try:
- mode_info = self.modes[mode]
- if isinstance(mode_info, ResourceSpec):
- return mode_info
- return ResourceSpec(self.library, mode_info)
+ return self.modes[mode]
except KeyError:
# fall back on the default mode if mode not found
return self
def key(self):
return self.library.name, self.relpath
-
- def __repr__(self):
- return "<Resource '%s' in library '%s'>" % (
- self.relpath, self.library.name)
-class Inclusion(object):
- implements(interfaces.IInclusion)
-
- def __init__(self, resources, depends=None):
- """Create an inclusion
-
- resources - the list of resource specs that should be on the page
- when this inclusion is used.
- depends - one or more inclusions that this inclusion depends on.
- """
- self._resources = r = {}
- for resource in resources:
- ext_resources = r.setdefault(resource.ext(), [])
- ext_resources.append(resource)
- self.depends = depends or []
-
- def depth(self):
- depth = 0
- for depend in self.depends:
- depend_depth = depend.depth()
- if depend_depth > depth:
- depth = depend_depth
- return depth + 1
-
- def resources_of_ext(self, ext):
- resources = []
- for depend in self.depends:
- resources.extend(depend.resources_of_ext(ext))
- resources.extend(self._resources.get(ext, []))
- return resources
-
def need(self):
needed = component.getUtility(
interfaces.ICurrentNeededInclusions)()
needed.need(self)
+ def inclusions(self):
+ """Get all inclusions needed by this inclusion, including itself.
+ """
+ result = []
+ for depend in self.depends:
+ result.extend(depend.inclusions())
+ result.append(self)
+ return result
+
+def normalize_inclusions(library, inclusions):
+ return [normalize_inclusion(library, inclusion)
+ for inclusion in inclusions]
+
+def normalize_inclusion(library, inclusion):
+ if isinstance(inclusion, ResourceInclusion):
+ return inclusion
+ assert isinstance(inclusion, basestring)
+ return ResourceInclusion(library, inclusion)
+
class NeededInclusions(object):
def __init__(self):
self._inclusions = []
@@ -121,71 +114,94 @@
def _sorted_inclusions(self):
return reversed(sorted(self._inclusions, key=lambda i: i.depth()))
- def resources(self, mode=None):
- resources_of_ext = {}
- for inclusion in self._sorted_inclusions():
- for ext in EXTENSIONS:
- r = resources_of_ext.setdefault(ext, [])
- r.extend(inclusion.resources_of_ext(ext))
- resources = []
- for ext in EXTENSIONS:
- resources.extend(resources_of_ext.get(ext, []))
- resources = apply_mode(resources, mode)
- return remove_duplicates(consolidate(resources))
+ def inclusions(self, mode=None):
+ inclusions = []
+ for inclusion in self._inclusions:
+ inclusions.extend(inclusion.inclusions())
+
+ inclusions = apply_mode(inclusions, mode)
+ inclusions = consolidate(inclusions)
+ inclusions = sort_inclusions(inclusions)
+ inclusions = remove_duplicates(inclusions)
+ return inclusions
def render(self, mode=None):
result = []
- get_resource_url = component.getUtility(interfaces.IResourceUrl)
- for resource in self.resources(mode):
- url = get_resource_url(resource)
- result.append(render_resource(resource, url))
+ get_inclusion_url = component.getUtility(interfaces.IInclusionUrl)
+ for inclusion in self.inclusions(mode):
+ url = get_inclusion_url(inclusion)
+ result.append(render_inclusion(inclusion, url))
return '\n'.join(result)
-def apply_mode(resources, mode):
- result = []
- for resource in resources:
- result.append(resource.mode(mode))
- return result
+def apply_mode(inclusions, mode):
+ return [inclusion.mode(mode) for inclusion in inclusions]
-def remove_duplicates(resources):
- """Given a set of resources, consolidate them so resource only occurs once.
+def remove_duplicates(inclusions):
+ """Given a set of inclusions, consolidate them so each nly occurs once.
"""
seen = set()
result = []
- for resource in resources:
- if resource.key() in seen:
+ for inclusion in inclusions:
+ if inclusion.key() in seen:
continue
- seen.add(resource.key())
- result.append(resource)
+ seen.add(inclusion.key())
+ result.append(inclusion)
return result
-def consolidate(resources):
- # first map rollup -> list of resources that are in this rollup
- rollup_to_resources = {}
- for resource in resources:
- for rollup in resource.part_of:
- rollup_to_resources.setdefault(rollup.key(), []).append(resource)
+def consolidate(inclusions):
+ # first map rollup -> list of inclusions that are in this rollup
+ rollup_to_inclusions = {}
+ for inclusion in inclusions:
+ for rollup in inclusion.rollups:
+ rollup_to_inclusions.setdefault(rollup.key(), []).append(
+ inclusion)
- # now replace resource with rollup consolidated biggest amount of
- # resources, or keep resource if no such rollup exists
+ # now replace inclusion with rollup consolidated biggest amount of
+ # inclusions, or keep inclusion if no such rollup exists
result = []
- for resource in resources:
+ for inclusion in inclusions:
potential_rollups = []
- for rollup in resource.part_of:
- potential_rollups.append((len(rollup_to_resources[rollup.key()]),
+ for rollup in inclusion.rollups:
+ potential_rollups.append((len(rollup_to_inclusions[rollup.key()]),
rollup))
if not potential_rollups:
# no rollups at all
- result.append(resource)
+ result.append(inclusion)
continue
sorted_rollups = sorted(potential_rollups)
amount, rollup = sorted_rollups[-1]
if amount > 1:
result.append(rollup)
else:
- result.append(resource)
+ result.append(inclusion)
return result
+def sort_inclusions(inclusions):
+# XXX this is not necessary as we can rely on Python's stable
+# sort to get the right order for inclusions.
+
+# depend_sortkey = {}
+# for inclusion in inclusions:
+# for d in inclusion.depends:
+# c = depend_sortkey.get(d.key(), 0)
+# c += 1
+# depend_sortkey[d.key()] = c
+
+# depend_sortkey2 = {}
+# for inclusion in inclusions:
+# v = depend_sortkey.get(inclusion.key(), 0)
+# for d in inclusion.depends:
+# c = depend_sortkey2.get(d.key(), 0)
+# c += v
+# depend_sortkey2[d.key()] = c
+
+ def key(inclusion):
+ return EXTENSIONS.index(inclusion.ext())
+ #, -depend_sortkey2.get(inclusion.key(), 0))
+
+ # this relies on stable sorting in Python
+ return sorted(inclusions, key=key)
+
def render_css(url):
return ('<link rel="stylesheet" type="text/css" href="%s" />' %
url)
@@ -197,16 +213,16 @@
return ('<script type="text/javascript" src="%s"></script>' %
url)
-resource_renderers = {
+inclusion_renderers = {
'.css': render_css,
'.kss': render_kss,
'.js': render_js,
}
-def render_resource(resource, url):
- renderer = resource_renderers.get(resource.ext(), None)
+def render_inclusion(inclusion, url):
+ renderer = inclusion_renderers.get(inclusion.ext(), None)
if renderer is None:
raise UnknownResourceExtension(
- "Unknown resource extension %s for resource: %s" %
- (resource.ext(), repr(resource)))
+ "Unknown resource extension %s for resource inclusion: %s" %
+ (inclusion.ext(), repr(inclusion)))
return renderer(url)
Modified: hurry.resource/trunk/src/hurry/resource/interfaces.py
===================================================================
--- hurry.resource/trunk/src/hurry/resource/interfaces.py 2008-09-23 17:10:14 UTC (rev 91400)
+++ hurry.resource/trunk/src/hurry/resource/interfaces.py 2008-09-23 17:30:19 UTC (rev 91401)
@@ -9,16 +9,17 @@
"""
name = Attribute("The unique name of the library")
-class IResourceSpec(Interface):
- """Resource specification
+class IResourceInclusion(Interface):
+ """Resource inclusion
- A resource specification specifies a single resource in a library.
+ A resource inclusion specifies how to include a single resource in a
+ library.
"""
library = Attribute("The resource library this resource is in")
relpath = Attribute("The relative path of the resource "
"within the resource library")
- part_of = Attribute("A list of potential rollups that this "
- "resource is part of")
+ rollups = Attribute("A list of potential rollup ResourceInclusions "
+ "that this resource is part of")
def ext():
"""Get the filesystem extension of this resource.
@@ -28,43 +29,22 @@
"""
def mode(mode):
- """Get the resource in a different mode.
+ """Get the resource inclusion in a different mode.
mode - the mode (minified, debug, etc) that we want this
resource to be in. None is the default mode, and is
this resource spec itself.
- An IResourceSpec for that mode is returned.
+ An IResourceInclusion for that mode is returned.
If we cannot find a particular mode for a resource, the
- resource spec is also used.
+ original resource inclusion is returned.
"""
def key():
- """Returns a unique, hashable identifier for the resource.
+ """Returns a unique, hashable identifier for the resource inclusion.
"""
-class IInclusion(Interface):
- """A resource inclusion.
-
- This represents one or more resources that are included on a page
- together (in the HTML head section). An inclusion may have
- dependencies on other inclusions.
- """
- def depth():
- """The depth of the inclusion tree.
-
- This is used to sort the inclusions. If multiple inclusions are
- required on a page, the ones with the deepest inclusion trees
- are sorted first.
- """
-
- def resources_of_ext(ext):
- """Retrieve all resources with a certain extension in this inclusion.
-
- This also goes up to all inclusions that this inclusion depends on.
- """
-
def need():
"""Express need directly for the current INeededInclusions.
@@ -73,6 +53,10 @@
the HTML that is currently being rendered.
"""
+ def inclusions():
+ """Get all inclusions needed by this inclusion, including itself.
+ """
+
class INeededInclusions(Interface):
"""A collection of inclusions that are needed for page display.
"""
@@ -83,29 +67,28 @@
See also IInclusion.need() for a convenience method.
"""
- def resources(mode=None):
- """Give all resources needed.
+ def inclusions(mode=None):
+ """Give all resource inclusions needed.
mode - optional argument that tries to put inclusions into
a particular mode (such as debug, minified, etc)
Has no effect if an included resource does not know
about that mode; the original resource will be included.
- Returns a list of resource specifications needed.
+ Returns a list of resource inclusions needed.
"""
def render(self, mode=None):
- """Render all resources
+ """Render all resource inclusions for HTML header.
mode - optional argument that tries to put inclusions into
a particular mode (such as debug, minified, etc).
Has no effect if an included resource does not know
about that mode; the original resource will be included.
- Returns a HTML snippet that includes the required resources.
+ Returns a HTML snippet that includes the required resource inclusions.
"""
-
-
+
class ICurrentNeededInclusions(Interface):
def __call__():
"""Return the current needed inclusions object.
@@ -113,7 +96,7 @@
These can for instance be retrieved from the current request.
"""
-class IResourceUrl(Interface):
- def __call__(resource):
- """Return the URL for given resource spec.
+class IInclusionUrl(Interface):
+ def __call__(inclusion):
+ """Return the URL for given resource inclusion.
"""
More information about the Checkins
mailing list