Commits

Anonymous committed 921bb30

[svn] Added a distutils command for combining Javascript and CSS files

Comments (0)

Files changed (4)

   Fixes #235.
 * Added support for the defer attribute to javascript_include_tag. Suggested by
   s0undt3ch. Fixes #214.
+* Added a distutils command compress_resources, which can combine CSS
+  and Javascript files, and compress Javascript via ShrinkSafe.  Add
+  "command_packages=webhelpers.commands" in [global] in setup.cfg to
+  enable this command for your package.
 
 0.3 (03/18/2007)
 * WARNING: paginate now takes arguments intended for the collection object as
                  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
                  "Topic :: Software Development :: Libraries :: Python Modules",
                ],
+    entry_points="""
+    [buildutils.optional_commands]
+    compress_resources = webhelpers.commands
+    """,
 )

webhelpers/commands/__init__.py

+#

webhelpers/commands/compress_resources.py

+from distutils.cmd import Command
+from distutils.errors import *
+from distutils import log
+import pkg_resources
+import os
+import glob
+import random
+import mimetypes
+import urllib2
+import urllib
+import textwrap
+
+class compress_resources(Command):
+    description = 'Compress Javascript and CSS files into smaller/combined forms'
+    long_description = textwrap.dedent("""\
+    hey there
+
+    you 
+    """)
+    
+
+    user_options = [
+        ('resource-dirs=', 'r', 'Relative directory where resources are located'),
+        ('resource-files=', None, 'Filenames to include (in order they should be included)'),
+        ('combined-name=', None, 'Names of combined files'),
+        ('extensions=', None, 'Extensions to compress (default .js and .css)'),
+        ('compress-js', None, 'Compress Javascript using ShrinkSafe'),
+        ('example-settings', None, 'Put some example settings in setup.cfg'),
+        ]
+    boolean_options = ['compress-js', 'example-settings']
+
+    shrinksafe_url = 'http://alex.dojotoolkit.org/shrinksafe/shrinksafe.php'
+
+    def initialize_options(self):
+        self.resource_dirs = []
+        self.resource_files = []
+        self.extensions = ['.js', '.css']
+        self.combined_name = 'combined'
+        self.compress_js = False
+        self.example_settings = False
+        
+    def finalize_options(self):
+        if self.example_settings:
+            return
+        if (isinstance(self.resource_files, basestring)
+            and len(self.resource_files.splitlines()) > 1):
+            log.debug('Splitting resource_files by line')
+            self.resource_files = [
+                line.strip() for line in self.resource_files.splitlines()
+                if line.strip()]
+        else:
+            self.ensure_string_list('resource_files')
+        self.ensure_string_list('resource_dirs')
+        self.ensure_string_list('extensions')
+        self.ensure_string('combined_name')
+        
+    def run(self):
+        if self.example_settings:
+            self.run_example_settings()
+            return
+        for dir in self.resource_dirs:
+            for ext in self.extensions:
+                self.compress_dir(dir, ext)
+    
+    comment_styles = {
+        '.js': '/* From %(filename)s: */',
+        '.css': '/* From %(filename)s: */',
+        }
+    
+    def compress_dir(self, dir, ext):
+        filenames = []
+        for name in self.resource_files:
+            if name.startswith('#'):
+                continue
+            if not os.path.splitext(name)[1]:
+                name += ext
+            if not name.endswith(ext):
+                continue
+            globbed = glob.glob(os.path.join(dir, name))
+            globbed.sort()
+            filenames.extend(globbed)
+        if not filenames:
+            return
+        combined_fn = self.combined_name + ext
+        combined_fn = os.path.join(dir, combined_fn)
+        if combined_fn in filenames:
+            filenames.remove(combined_fn)
+        content = []
+        for fn in filenames:
+            comment = self.comment_styles[ext] % {'filename': fn}
+            content.append(comment + '\n\n')
+            f = open(fn, 'rb')
+            content.append(f.read())
+            f.close()
+            content.append('\n\n\n\n')
+        content = ''.join(content)
+        need_write = True
+        if os.path.exists(combined_fn):
+            f = open(combined_fn, 'rb')
+            existing = f.read()
+            f.close()
+            if content == existing:
+                status = 'no change'
+                need_write = False
+            else:
+                status = 'content changed'
+        else:
+            status = 'created'
+        if need_write:
+            f = open(combined_fn, 'wb')
+            f.write(content)
+            f.close()
+        log.info('%s: %iKb (%s)' % (combined_fn, len(content)/1024, status))
+        if self.compress_js and ext == '.js':
+            compress_fn = os.path.join(dir, 'compressed.js')
+            if (not os.path.exists(compress_fn)
+                or os.path.getmtime(compress_fn) < os.path.getmtime(combined_fn)):
+                compress_js = self.compress_javascript(content)
+                f = open(compress_fn, 'wb')
+                f.write(compress_js)
+                f.close()
+                log.info('%s: compressed to %iKb (%i%%)',
+                         compress_fn, len(compress_js)/1024,
+                         100*len(compress_js)/len(content))
+            else:
+                log.debug('%s: skipping recreation (seems up-to-date)' % compress_fn)
+
+    def compress_javascript(self, content):
+        result = self.submit_file_upload(
+            self.shrinksafe_url,
+            {'stripnewlines': '1',
+             'shrinkfile[]': ('content.js', content)})
+        return result
+        
+    http_boundary = '----' + str(random.random()) + str(random.random())
+        
+    def submit_file_upload(self, url, fields):
+        content_type, body = self._encode_data(fields)
+        req = urllib2.Request(url)
+        req.add_header('Content-Type', content_type)
+        req.add_data(body)
+        res = urllib2.urlopen(req)
+        return res.read()
+
+    def _encode_data(self, fields):
+        data = []
+        for fieldname, value in fields.items():
+            data.append('--' + self.http_boundary)
+            header = 'Content-Disposition: form-data; name="%s"' % fieldname
+            if isinstance(value, tuple):
+                header += '; filename="%s"' % value[0]
+            data.append(header)
+            if isinstance(value, tuple):
+                data.append('Content-Type: %s' % self._get_content_type(value[0]))
+                value = value[1]
+            data.append('')
+            data.append(value)
+        data.append('--' + self.http_boundary)
+        data.append('')
+        body = '\r\n'.join(data)
+        content_type = 'multipart/form-data; boundary=%s' % self.http_boundary
+        return content_type, body
+    
+    def _get_content_type(self, filename):
+        return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+
+    def run_example_settings(self):
+        from setuptools.command.setopt import edit_config
+        dist = self.distribution
+        packages = list(dist.packages)
+        packages.sort(lambda a, b: cmp(len(a), len(b)))
+        package_dir = os.path.join(dist.package_dir or '.', packages[0])
+        settings = {
+            'compress_resources':
+            {'resource_dirs': os.path.join(package_dir, 'public'),
+             'resource_files': 'file1.js\nfile2.js',
+             'compress_js': 'True'}}
+        edit_config('setup.cfg', settings, self.dry_run)
+        
+        
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.