[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