import os.path, hashlib, subprocess, re, shutil from django import template from django.conf import settings from django.core.cache import cache register = template.Library() js_or_css = re.compile("(.*)\.(js|css)") is_path = re.compile("\"(.*)\"") # Locations of things for YUI compressor JAVA = "/usr/bin/java" YUI_COMPRESSOR_JAR = "/path/to/yuicompressor-2.3.6/build/yuicompressor-2.3.6.jar" MINIFY = True class CacheableNode(template.Node): def __init__(self, rpath, path_literal=False): self.path_literal = path_literal if path_literal: self.rpath = self.cache_resource(rpath) else: self.rpath = template.Variable(rpath) def cache_resource(self, rpath): if not settings.DEBUG: cached_res = cache.get(rpath, False) if not cached_res: if rpath[0] == "/": rpath = rpath[1:] res_path = os.path.join(settings.DOCUMENT_ROOT, rpath) if os.path.exists(res_path): # Open the file for hashing fin = open(res_path, "r") s = fin.read() fin.close() # Update the MD5 hash = hashlib.md5() hash.update(s) (path, fname) = os.path.split(res_path) cache_name = os.path.join(path, hash.hexdigest() + ".cache" + os.path.splitext(fname)[1]) if not os.path.exists(cache_name): if js_or_css.match(fname) and MINIFY: # Write out the cachable file minified = open(cache_name, "w") yuic = subprocess.Popen((JAVA, "-jar", YUI_COMPRESSOR_JAR, res_path), stdout=minified) rtncode = yuic.wait() minified.write("\n") minified.close() # If an error happened during minification, just copy the file over if rtncode != 0: print "ERROR Minifying %s" % res_path shutil.copyfile(res_path, cache_name) else: shutil.copyfile(res_path, cache_name) # Strip DOCUMENT_ROOT off file, leaving relative path cache_path = cache_name.replace(settings.DOCUMENT_ROOT, "") cache.set(rpath, cache_path, 3600) # Cache mapping for an hour if cache_path[0] != "/": cache_path = "/%s" % cache_path return cache_path else: return rpath # Return mapped path to cachable resource return cached_res return rpath def render(self, context): if self.path_literal: return self.rpath else: rpath = self.cache_resource(self.rpath.resolve(context)) return rpath @register.tag def cachable(parser, token): try: tag_name, rpath = token.split_contents() if is_path.match(rpath): path_literal = True else: path_literal = False rpath = rpath.replace("\"", "") except ValueError: raise template.TemplateSyntaxError, "%r tag requires a valid path relative to settings.DOCUMENT_ROOT" % token.contents.split()[0] return CacheableNode(rpath, path_literal=path_literal)