def get_path(path): """Get a system path for a given file. This properly handles storing files in `project/app/static`, and any other location that Django's static files system supports. ``path`` should be relative to ``STATIC_ROOT``. """ debug = getattr(settings, 'DEBUG', False) static = getattr(settings, 'JINGO_MINIFY_USE_STATIC', True) full_path = os.path.join(get_media_root(), path) if debug and static: found_path = static_finder(path) # If the path is not found by Django's static finder (like we are # trying to get an output path), it returns None, so fall back. if found_path is not None: full_path = found_path return full_path
def path(*args): return os.path.join(get_media_root(), *args)
def test_static_override(): """Overriding to False uses MEDIA versions.""" eq_(get_media_root(), 'media') eq_(get_media_url(), 'http://example.com/media')
def test_no_override(): """No override uses STATIC versions.""" eq_(get_media_root(), 'static') eq_(get_media_url(), 'http://example.com/static')
import os import re import shutil import time import urllib2 from subprocess import call, PIPE from django.conf import settings from django.core.management.base import BaseCommand, CommandError import git from jingo_minify.utils import get_media_root path = lambda *a: os.path.join(get_media_root(), *a) class Command(BaseCommand): # pragma: no cover help = ("Compresses css and js assets defined in settings.MINIFY_BUNDLES") option_list = BaseCommand.option_list + ( make_option('-u', '--update-only', action='store_true', dest='do_update_only', help='Updates the hash only'), make_option('-t', '--add-timestamp', action='store_true', dest='add_timestamp', help='Add timestamp to hash'), ) requires_model_validation = False do_update_only = False checked_hash = {} bundle_hashes = {}
class Command(BaseCommand): # pragma: no cover help = ("Compresses css and js assets defined in settings.MINIFY_BUNDLES") option_list = BaseCommand.option_list + ( make_option('-u', '--update-only', action='store_true', dest='do_update_only', help='Updates the hash only'), make_option('-t', '--add-timestamp', action='store_true', dest='add_timestamp', help='Add timestamp to hash'), ) requires_model_validation = False do_update_only = False checked_hash = {} bundle_hashes = {} missing_files = 0 minify_skipped = 0 cmd_errors = False ext_media_path = os.path.join(get_media_root(), 'external') def update_hashes(self, update=False): def media_git_id(media_path): id = git.repo.Repo(path(media_path)).log('-1')[0].id_abbrev if update: # Adds a time based hash on to the build id. return '%s-%s' % (id, hex(int(time.time()))[2:]) return id build_id_file = os.path.realpath(os.path.join(settings.ROOT, 'build.py')) with open(build_id_file, 'w') as f: f.write('BUILD_ID_CSS = "%s"\n' % media_git_id('css')) f.write('BUILD_ID_JS = "%s"\n' % media_git_id('js')) f.write('BUILD_ID_IMG = "%s"\n' % media_git_id('img')) f.write('BUNDLE_HASHES = %s\n' % self.bundle_hashes) def handle(self, **options): if options.get('do_update_only', False): self.update_hashes(update=True) return jar_path = (os.path.dirname(__file__), '..', '..', 'bin', 'yuicompressor-2.4.7.jar') self.path_to_jar = os.path.realpath(os.path.join(*jar_path)) self.v = '-v' if options.get('verbosity', False) == '2' else '' cachebust_imgs = getattr(settings, 'CACHEBUST_IMGS', False) if not cachebust_imgs: print "To turn on cache busting, use settings.CACHEBUST_IMGS" # This will loop through every bundle, and do the following: # - Concat all files into one # - Cache bust all images in CSS files # - Minify the concatted files for ftype, bundle in settings.MINIFY_BUNDLES.iteritems(): for name, files in bundle.iteritems(): # Set the paths to the files. concatted_file = path(ftype, '%s-all.%s' % (name, ftype,)) compressed_file = path(ftype, '%s-min.%s' % (name, ftype,)) files_all = [] for fn in files: processed = self._preprocess_file(fn) # If the file can't be processed, we skip it. if processed is not None: files_all.append(processed) # Concat all the files. tmp_concatted = '%s.tmp' % concatted_file if len(files_all) == 0: raise CommandError('No input files specified in ' + 'MINIFY_BUNDLES["%s"]["%s"] in settings.py!' % (ftype, name)) self._call("cat %s > %s" % (' '.join(files_all), tmp_concatted), shell=True) # Cache bust individual images in the CSS. if cachebust_imgs and ftype == "css": bundle_hash = self._cachebust(tmp_concatted, name) self.bundle_hashes["%s:%s" % (ftype, name)] = bundle_hash # Compresses the concatenations. is_changed = self._is_changed(concatted_file) self._clean_tmp(concatted_file) if is_changed: self._minify(ftype, concatted_file, compressed_file) elif self.v: print "File unchanged, skipping minification of %s" % ( concatted_file) else: self.minify_skipped += 1 # Write out the hashes self.update_hashes(options.get('add_timestamp', False)) if not self.v and self.minify_skipped: print "Unchanged files skipped for minification: %s" % ( self.minify_skipped) if self.cmd_errors: raise CommandError('one or more minify commands exited with a ' 'non-zero status. See output above for errors.') def _call(self, *args, **kw): exit = call(*args, **kw) if exit != 0: print '%s exited with a non-zero status.' % args self.cmd_errors = True return exit def _get_url_or_path(self, item): """ Determine whether this is a URL or a relative path. """ if item.startswith('//'): return 'http:%s' % item elif item.startswith(('http', 'https')): return item return None def _preprocess_file(self, filename): """Preprocess files and return new filenames.""" url = self._get_url_or_path(filename) if url: # External files from URLs are placed into a subdirectory. if not os.path.exists(self.ext_media_path): os.makedirs(self.ext_media_path) filename = os.path.basename(url) if filename.endswith(('.js', '.css', '.less', '.styl')): fp = path(filename.lstrip('/')) file_path = '%s/%s' % (self.ext_media_path, filename) try: req = urllib2.urlopen(url) print ' - Fetching %s ...' % url except urllib2.HTTPError, e: print ' - HTTP Error %s for %s, %s' % (url, filename, str(e.code)) return None except urllib2.URLError, e: print ' - Invalid URL %s for %s, %s' % (url, filename, str(e.reason)) return None with open(file_path, 'w+') as fp: try: shutil.copyfileobj(req, fp) except shutil.Error: print ' - Could not copy file %s' % filename filename = os.path.join('external', filename) else: