Commits

Jan-Jaap Driessen  committed 8488122

Fixed #72 .need() broken when <head> tag has attributes.

  • Participants
  • Parent commits a529c02

Comments (0)

Files changed (3)

 
 - Documentation fix in code samples, thanks to Toby Dacre.
 
+- Fix #72, `.need() broken when <head> tag has attributes`,
+  thanks to Randall Leeds.
+
 0.11.4 (2012-01-14)
 ===================
 
 - There was another bug with ordering resources when multiple libraries
   were involved. This time the way library_nr was calculated was changed
-  so that it wouldn't happen anymore. 
-   
+  so that it wouldn't happen anymore.
+
   The intent of library_nr was to have it always be 1 higher than the
   maximum library_nr of any libraries this library is based on.
 
   depending on other libraries would consistently get a library_nr too
   low, as each resource they were based on had a library_nr that was
   too low as well, even though another resource could exist in that
-  library with a higher library_nr. This could cause the library_nr of 
+  library with a higher library_nr. This could cause the library_nr of
   all resources in a library to be too low.
 
   This is now fixed to moving library_nr to the place it should've
 =================
 
 - Add bundling support: bundles are collections of Resources that can
-  be served in one HTTP request. Bundle URLs are constructed by the 
+  be served in one HTTP request. Bundle URLs are constructed by the
   fanstatic injector and served by the fanstatic publisher.
 
 - Remove eager_superseder arguments from Resource, as this was not used.

File fanstatic/core.py

 import os
 import sys
+import re
 import threading
 
 import fanstatic.checksum
 DEBUG = 'debug'
 MINIFIED = 'minified'
 
+_head_regex = re.compile('(<head[^>]*>)')
 
 _resource_file_existence_checking = True
 
     If a slot is required, it must be filled in by passing an extra
     dictionary parameter to the ``.need`` method, containing a mapping
     from the required :py:class:`Slot` to :py:class:`Resource`.
-    
+
     When a slot is filled, the resource filled in should have
     the same dependencies as the slot, or a subset of the dependencies
     of the slot. It should also have the same extension as the slot.
     If this is not the case, it is an error.
     """
-    
+
 class Library(object):
     """The resource library.
 
         self._library_deps = set()
         self.known_resources = {}
         self.library_nr = None
-        
+
     def __repr__(self):
         return "<Library '%s' at '%s'>" % (self.name, self.path)
 
                     'Library cycle detected in resource %s' % resource)
 
     def register(self, resource):
-        """Register a Resource with this Library. 
+        """Register a Resource with this Library.
 
         A Resource knows about its Library. After a Resource has registered
-        itself with its Library, the Library knows about the Resources 
-        associated to it. 
+        itself with its Library, the Library knows about the Resources
+        associated to it.
         """
         if resource.relpath in self.known_resources:
             raise ConfigurationError(
 
     :param depends: optionally, a list of resources that this resource
       depends on. Entries in the list are :py:class:`Resource`
-      instances. 
+      instances.
 
     :param supersedes: optionally, a list of :py:class:`Resource`
       instances that this resource supersedes as a rollup
             elif isinstance(argument, basestring):
                 mode_resource = Resource(library, argument)
             else:
-                # The dependencies of a mode resource should be the same 
+                # The dependencies of a mode resource should be the same
                 # or a subset of the dependencies this mode replaces.
                 if len(argument.depends - self.depends) > 0:
                     raise ModeResourceDependencyError
             dependency_nr = max(depend.dependency_nr + 1,
                                 dependency_nr)
         self.dependency_nr = dependency_nr
-   
+
     def render(self, library_url):
         return self.renderer('%s/%s' % (library_url, self.relpath))
 
     real resource by the application when you ``.need()`` that
     resource (or when you need something that depends on the slot
     indirectly).
-  
+
     :param library: the :py:class:`Library` this slot is in.
 
     :param ext: the extension of the slot, for instance '.js'. This
       required to be filled in when a resource that depends on a slot
       is needed, or whether it's optional. By default filling in a
       slot is required.
-      
+
     :param depends: optionally, a list of resources that this slot
       depends on. Resources that are slotted in here need to have
       the same dependencies as that of the slot, or a strict subset.
         assert extension.startswith('.')
         self.ext = extension
         self.required = required
-        
+
         assert not isinstance(depends, basestring)
         self.depends = set()
         if depends is not None:
             dependency_nr = max(depend.dependency_nr + 1,
                                 dependency_nr)
         self.dependency_nr = dependency_nr
-        
+
 class FilledSlot(Renderable, Dependable):
     def __init__(self, slot, resource):
         self.library = resource.library
             raise SlotError(
                 "slot filled in with resource that has dependencies that "
                 "are not a strict subset of dependencies of slot")
-        
+
         # XXX how do slots interact with rollups?
 
     def render(self, library_url):
         except KeyError:
             # fall back on the default mode if mode not found
             return self
-        
+
 class Group(Dependable):
     """A resource used to group resources together.
 
         slots = slots or {}
         self._resources.add(resource)
         self._slots.update(slots)
-        
+
     def resources(self):
         """Retrieve the list of resources needed.
 
                                 resource)
             result.add(FilledSlot(resource, fill_resource))
         return result
-            
+
     def clear(self):
         # Clear out any resources "needed" thusfar.
         # XXX or should we rather revert to the list with resources
           inclusions into. This string must have a ``<head>`` section.
         """
         to_insert = self.render()
-        return html.replace('<head>', '<head>\n    %s\n' % to_insert, 1)
+        return _head_regex.sub('\\1\n    %s\n' % (to_insert,), html, count=1)
 
     def render_topbottom(self):
         """Render resource inclusions separately into top and bottom fragments.
         """
         top, bottom = self.render_topbottom()
         if top:
-            html = html.replace('<head>', '<head>\n    %s\n' % top, 1)
+            html = _head_regex.sub('\\1\n    %s\n' % (top,), html, count=1)
         if bottom:
             html = html.replace('</body>', '%s</body>' % bottom, 1)
         return html
             # nothing to supersede resource so use it directly
             result.append(resource)
     return result
-    
+
 def sort_resources(resources):
     """Sort resources for inclusion on web page.
 
     """
     for resource in resources:
         resource.library.init_library_nr()
-        
+
     def key(resource):
         return (
             resource.order,

File fanstatic/test_core.py

     # Can not use the same relpath for two Resource declarations.
     with pytest.raises(ConfigurationError):
         x2 = Resource(foo, 'a.js')
-    
+
 
 def test_group_resource():
     foo = Library('foo', '')
     c = Resource(foo, 'c.js', depends=[g])
     g2 = Group([g])
     g3 = Group([g, g2])
-    
+
     assert c.depends == set([a, b])
     assert g2.depends == set([a, b])
     assert g3.depends == set([a, b])
-    
+
     needed = NeededResources()
     needed.need(c)
     assert needed.resources() == [a, b, c]
-    
+
 def test_redundant_resource():
     foo = Library('foo', '')
     x1 = Resource(foo, 'a.js')
     needed = NeededResources(resources=[h1, h2], rollup=True, debug=True)
     # no mode available for rollup, use the rollup.
     assert needed.resources() == [gianth]
-     
+
 
 def test_rendering():
     foo = Library('foo', '')
 something more</head></html>'''
 
 
+def test_html_insert_head_with_attributes():
+    # ticket 72: .need() broken when <head> tag has attributes
+    foo = Library('foo', '')
+    x1 = Resource(foo, 'a.js')
+    needed = NeededResources(resources=[x1])
+
+    html = '<html><head profile="http://example.org">something</head></html>'
+    assert needed.render_into_html(html) == '''\
+<html><head profile="http://example.org">
+    <script type="text/javascript" src="/fanstatic/foo/a.js"></script>
+something</head></html>'''
+
 def test_html_top_bottom():
     foo = Library('foo', '')
     x1 = Resource(foo, 'a.js')
     X.init_library_nr()
     Y.init_library_nr()
     Z.init_library_nr()
-                              
+
     assert a.library.library_nr == 0
     assert c.library.library_nr == 0
     assert b.library.library_nr == 1
     lib2 = Library('lib2', '')
     lib3 = Library('lib3', '')
     lib4 = Library('lib4', '')
-    
+
     js1 = Resource(lib1, 'js1.js')
-    js2 = Resource(lib2, 'js2.js', depends=[js1]) 
+    js2 = Resource(lib2, 'js2.js', depends=[js1])
     js3 = Resource(lib3, 'js3.js', depends=[js2])
-    
+
     style1 = Resource(lib3, 'style1.css')
     style2 = Resource(lib4, 'style2.css', depends=[style1])
 
     obviel_lib = Library('obviel', '')
     bread_lib = Library('bread', '')
     app_lib = Library('app', '')
-    
+
     jquery = Resource(jquery_lib, 'jquery.js')
     jqueryui = Resource(jqueryui_lib, 'jqueryui.js', depends=[jquery])
 
     vtab = Resource(bread_lib, 'vtab.js', depends=[jqueryui])
 
     tabview = Resource(bread_lib, 'tabview.js', depends=[obviel, vtab])
-    
+
     bread = Resource(bread_lib, 'bread.js', depends=[tabview, obviel_forms])
 
     app = Resource(app_lib, 'app.js', depends=[bread, obviel_datepicker])
-    
+
     needed = NeededResources()
 
     needed.need(app)
         print resource, resource.library.library_nr
     assert resources == [jquery, jqueryui, obviel, obviel_forms,
                          obviel_datepicker, vtab, tabview, bread, app]
-    
+
 
     #assert resources == [obviel, forms, forms_autocomplete, tabview, bread,
     #                     zorgdas]
-    
+
 # XXX tests for hashed resources when this is enabled. Needs some plausible
 # directory to test for hashes