Repository: web2py/web2py

Issue:
response.files with no file suffix
_From [cfho...@cfhowes.com](https://code.google.com/u/117584905267195773078/) on September 22, 2012 17:44:59_

What steps will reproduce the problem? 1. add response.files.append(' http://maps.google.com/maps/api/js?sensor=false' )
2. render a view that includes web2py_ajax.html What is the expected output? What do you see instead? i expect the file to e output, but since it does not end in a matching suffix it is ignored. What version of the product are you using? On what operating system? 2.0.9 Please provide any additional information below.

_Original issue: http://code.google.com/p/web2py/issues/detail?id=1025_



Discussion:

Resolving patch:
diff --git a/gluon/globals.py b/gluon/globals.py
--- a/gluon/globals.py
+++ b/gluon/globals.py
@@ -26,6 +26,8 @@
 from gluon.utils import web2py_uuid, secure_dumps, secure_loads
 from gluon.settings import global_settings
 from gluon import recfile
+from gluon.cache import CacheInRam
+from gluon.fileutils import copystream
 import hashlib
 import portalocker
 try:
@@ -47,8 +49,7 @@
 import urlparse
 import copy
 import tempfile
-from gluon.cache import CacheInRam
-from gluon.fileutils import copystream
+
 
 FMT = '%a, %d-%b-%Y %H:%M:%S PST'
 PAST = 'Sat, 1-Jan-1971 00:00:00'
@@ -82,13 +83,22 @@
 css_inline = '<style type="text/css">\n%s\n</style>'
 js_inline = '<script type="text/javascript">\n%s\n</script>'
 
+template_mapping = {
+    'css': css_template,
+    'js': js_template,
+    'coffee': coffee_template,
+    'ts': typescript_template,
+    'less': less_template,
+    'css:inline': css_inline,
+    'js:inline': js_inline
+}
 
 # IMPORTANT:
 # this is required so that pickled dict(s) and class.__dict__
 # are sorted and web2py can detect without ambiguity when a session changes
 class SortingPickler(Pickler):
     def save_dict(self, obj):
-        self.write(EMPTY_DICT if self.bin else MARK+DICT)
+        self.write(EMPTY_DICT if self.bin else MARK + DICT)
         self.memoize(obj)
         self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
 
@@ -275,7 +285,7 @@ def parse_all_vars(self):
         """
         self._vars = copy.copy(self.get_vars)
         for key, value in self.post_vars.iteritems():
-            if not key in self._vars:
+            if key not in self._vars:
                 self._vars[key] = value
             else:
                 if not isinstance(self._vars[key], list):
@@ -436,19 +446,20 @@ def render(self, *a, **b):
         return page
 
     def include_meta(self):
-        s = "\n";
+        s = "\n"
         for meta in (self.meta or {}).iteritems():
             k, v = meta
-            if isinstance(v,dict):
-                s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
+            if isinstance(v, dict):
+                s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
             else:
-                s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
+                s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
         self.write(s, escape=False)
 
     def include_files(self, extensions=None):
 
         """
-        Caching method for writing out files.
+        Includes files (usually in the head).
+        Can minify and cache local files
         By default, caches in ram for 5 minutes. To change,
         response.cache_includes = (cache_method, time_expire).
         Example: (cache.disk, 60) # caches to disk for 1 minute.
@@ -456,9 +467,13 @@ def include_files(self, extensions=None):
         from gluon import URL
 
         files = []
+        ext_files = []
         has_js = has_css = False
         for item in self.files:
-            if extensions and not item.split('.')[-1] in extensions:
+            if isinstance(item, (list, tuple)):
+                ext_files.append(item)
+                continue
+            if extensions and not item.rpartition('.')[2] in extensions:
                 continue
             if item in files:
                 continue
@@ -487,10 +502,13 @@ def call_minify(files=files):
                                     time_expire)
             else:
                 files = call_minify()
-        s = ''
+
+        files.extend(ext_files)
+        s = []
         for item in files:
             if isinstance(item, str):
                 f = item.lower().split('?')[0]
+                ext = f.rpartition('.')[2]
                 # if static_version we need also to check for
                 # static_version_urls. In that case, the _.x.x.x
                 # bit would have already been added by the URL()
@@ -498,24 +516,15 @@ def call_minify(files=files):
                 if self.static_version and not self.static_version_urls:
                     item = item.replace(
                         '/static/', '/static/_%s/' % self.static_version, 1)
-                if f.endswith('.css'):
-                    s += css_template % item
-                elif f.endswith('.js'):
-                    s += js_template % item
-                elif f.endswith('.coffee'):
-                    s += coffee_template % item
-                elif f.endswith('.ts'):
-                    # http://www.typescriptlang.org/
-                    s += typescript_template % item
-                elif f.endswith('.less'):
-                    s += less_template % item
+                tmpl = template_mapping.get(ext)
+                if tmpl:
+                    s.append(tmpl % item)
             elif isinstance(item, (list, tuple)):
                 f = item[0]
-                if f == 'css:inline':
-                    s += css_inline % item[1]
-                elif f == 'js:inline':
-                    s += js_inline % item[1]
-        self.write(s, escape=False)
+                tmpl = template_mapping.get(f)
+                if tmpl:
+                    s.append(tmpl % item[1])
+        self.write(''.join(s), escape=False)
 
     def stream(self,
                stream,


Test patch:
diff --git a/gluon/tests/__init__.py b/gluon/tests/__init__.py
--- a/gluon/tests/__init__.py
+++ b/gluon/tests/__init__.py
@@ -4,6 +4,7 @@
 from test_cache import *
 from test_contenttype import *
 from test_fileutils import *
+from test_globals import *
 from test_html import *
 from test_is_url import *
 from test_languages import *
diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py
new file mode 100644
--- /dev/null
+++ b/gluon/tests/test_globals.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+    Unit tests for gluon.globals
+"""
+
+
+import unittest
+from fix_path import fix_sys_path
+
+fix_sys_path(__file__)
+
+from gluon.globals import Response
+from gluon import URL
+
+
+class testResponse(unittest.TestCase):
+
+    def test_include_files(self):
+
+        def return_includes(response, extensions=None):
+            response.include_files(extensions)
+            return response.body.getvalue()
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.css'))
+        content = return_includes(response)
+        self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.js'))
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.coffee'))
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.ts'))
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.less'))
+        content = return_includes(response)
+        self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
+
+        response = Response()
+        response.files.append(('css:inline', 'background-color; white;'))
+        content = return_includes(response)
+        self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
+
+        response = Response()
+        response.files.append(('js:inline', 'alert("hello")'))
+        content = return_includes(response)
+        self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
+
+        response = Response()
+        response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
+
+        response = Response()
+        response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
+
+        response = Response()
+        response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
+        response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
+        response.files.append(URL('a', 'static', 'css/file.css'))
+        response.files.append(URL('a', 'static', 'css/file.css'))
+        content = return_includes(response)
+        self.assertEqual(content,
+            '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
+            '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
+
+        response = Response()
+        response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
+        response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
+        response.files.append(URL('a', 'static', 'css/file.css'))
+        response.files.append(URL('a', 'static', 'css/file.ts'))
+        content = return_includes(response)
+        self.assertEqual(content,
+            '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
+            '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
+            '<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
+            '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
+        )
+
+
+        response = Response()
+        response.files.append(URL('a', 'static', 'css/file.js'))
+        response.files.append(URL('a', 'static', 'css/file.css'))
+        content = return_includes(response, extensions=['css'])
+        self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
+
+        #regr test for #628
+        response = Response()
+        response.files.append('http://maps.google.com/maps/api/js?sensor=false')
+        content = return_includes(response)
+        self.assertEqual(content, '')
+
+        #regr test for #628
+        response = Response()
+        response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
+
+        response = Response()
+        response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
+        content = return_includes(response)
+        self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
+
+        response = Response()
+        response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
+        content = return_includes(response)
+        self.assertEqual(content, '')
+
+if __name__ == '__main__':
+    unittest.main()