import os import re import traceback from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env from minify.cssmin import cssmin from minify.jsmin import jsmin from tornado.web import StaticFileHandler log = CPLog(__name__) autoload = 'ClientScript' class ClientScript(Plugin): core_static = { 'style': [ 'style/main.css', 'style/uniform.generic.css', 'style/uniform.css', 'style/settings.css', ], 'script': [ 'scripts/library/mootools.js', 'scripts/library/mootools_more.js', 'scripts/library/uniform.js', 'scripts/library/form_replacement/form_check.js', 'scripts/library/form_replacement/form_radio.js', 'scripts/library/form_replacement/form_dropdown.js', 'scripts/library/form_replacement/form_selectoption.js', 'scripts/library/question.js', 'scripts/library/scrollspy.js', 'scripts/library/spin.js', 'scripts/library/Array.stableSort.js', 'scripts/library/async.js', 'scripts/couchpotato.js', 'scripts/api.js', 'scripts/library/history.js', 'scripts/page.js', 'scripts/block.js', 'scripts/block/navigation.js', 'scripts/block/footer.js', 'scripts/block/menu.js', 'scripts/page/home.js', 'scripts/page/wanted.js', 'scripts/page/settings.js', 'scripts/page/about.js', 'scripts/page/manage.js', 'scripts/misc/downloaders.js', ], } urls = {'style': {}, 'script': {}} minified = {'style': {}, 'script': {}} paths = {'style': {}, 'script': {}} comment = { 'style': '/*** %s:%d ***/\n', 'script': '// %s:%d\n' } html = { 'style': '', 'script': '', } def __init__(self): addEvent('register_style', self.registerStyle) addEvent('register_script', self.registerScript) addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_scripts', self.getScripts) if not Env.get('dev'): addEvent('app.load', self.minify) self.addCore() def addCore(self): for static_type in self.core_static: for rel_path in self.core_static.get(static_type): file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path) core_url = 'static/%s' % rel_path if static_type == 'script': self.registerScript(core_url, file_path, position = 'front') else: self.registerStyle(core_url, file_path, position = 'front') def minify(self): # Create cache dir cache = Env.get('cache_dir') parent_dir = os.path.join(cache, 'minified') self.makeDir(parent_dir) Env.get('app').add_handlers(".*$", [(Env.get('web_base') + 'minified/(.*)', StaticFileHandler, {'path': parent_dir})]) for file_type in ['style', 'script']: ext = 'js' if file_type is 'script' else 'css' positions = self.paths.get(file_type, {}) for position in positions: files = positions.get(position) self._minify(file_type, files, position, position + '.' + ext) def _minify(self, file_type, files, position, out): cache = Env.get('cache_dir') out_name = out out = os.path.join(cache, 'minified', out_name) raw = [] for file_path in files: f = open(file_path, 'r').read() if file_type == 'script': data = jsmin(f) else: data = self.prefix(f) data = cssmin(data) data = data.replace('../images/', '../static/images/') data = data.replace('../fonts/', '../static/fonts/') data = data.replace('../../static/', '../static/') # Replace inside plugins raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data}) # Combine all files together with some comments data = '' for r in raw: data += self.comment.get(file_type) % (ss(r.get('file')), r.get('date')) data += r.get('data') + '\n\n' self.createFile(out, data.strip()) if not self.minified.get(file_type): self.minified[file_type] = {} if not self.minified[file_type].get(position): self.minified[file_type][position] = [] minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) self.minified[file_type][position].append(minified_url) def getStyles(self, *args, **kwargs): return self.get('style', *args, **kwargs) def getScripts(self, *args, **kwargs): return self.get('script', *args, **kwargs) def get(self, type, as_html = False, location = 'head'): data = '' if as_html else [] try: try: if not Env.get('dev'): return self.minified[type][location] except: pass return self.urls[type][location] except: log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc())) return data def registerStyle(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'style', position) def registerScript(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'script', position) def register(self, api_path, file_path, type, location): api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) if not self.urls[type].get(location): self.urls[type][location] = [] self.urls[type][location].append(api_path) if not self.paths[type].get(location): self.paths[type][location] = [] self.paths[type][location].append(file_path) prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_tags = ['ms', 'moz', 'webkit'] def prefix(self, data): trimmed_data = re.sub('(\t|\n|\r)+', '', data) new_data = '' colon_split = trimmed_data.split(';') for splt in colon_split: curl_split = splt.strip().split('{') for curly in curl_split: curly = curly.strip() for prop in self.prefix_properties: if curly[:len(prop) + 1] == prop + ':': for tag in self.prefix_tags: new_data += ' -%s-%s; ' % (tag, curly) new_data += curly + (' { ' if len(curl_split) > 1 else ' ') new_data += '; ' new_data = new_data.replace('{ ;', '; ').replace('} ;', '} ') return new_data