import os import re import time from couchpotato.core.event import addEvent, fireEvent 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 scss import Scss from tornado.web import StaticFileHandler log = CPLog(__name__) autoload = 'ClientScript' class ClientScript(Plugin): core_static = { 'style': [ 'style/main.scss', '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/settings.js', 'scripts/page/about.js', ], } watches = {} original_paths = {'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) addEvent('app.load', self.livereload, priority = 1) addEvent('app.load', self.compile) self.addCore() def livereload(self): if Env.get('dev'): fireEvent('schedule.interval', 'livereload.watcher', self.watcher, seconds = .5) 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 watcher(self): changed = [] for file_path in self.watches: info = self.watches[file_path] old_time = info['file_time'] file_time = os.path.getmtime(file_path) if file_time > old_time: changed.append(info['api_path']) if info['compiled_path']: compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) f = open(file_path, 'r').read() f = compiler.compile(f) self.createFile(info['compiled_path'], f.strip()) # Add file to watchlist again, with current filetime self.watches[file_path]['file_time'] = file_time # Notify fronted with changes if changed: fireEvent('notify.frontend', type = 'watcher.changed', data = changed) def watchFile(self, file_path, api_path, compiled_path = False): self.watches[file_path] = { 'file_time': os.path.getmtime(file_path), 'api_path': api_path, 'compiled_path': compiled_path, } def compile(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.original_paths.get(file_type, {}) for position in positions: files = positions.get(position) self._compile(file_type, files, position, position + '.' + ext) def _compile(self, file_type, paths, position, out): cache = Env.get('cache_dir') out_name = out minified_dir = os.path.join(cache, 'minified') data_combined = '' raw = [] new_paths = [] for x in paths: file_path, url_path = x f = open(file_path, 'r').read() # Compile scss if file_path[-5:] == '.scss': # Compile to css compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) f = compiler.compile(f) # Reload watcher if Env.get('dev'): compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] compiled_file_path = os.path.join(minified_dir, compiled_file_name) self.createFile(compiled_file_path, f.strip()) # Remove scss path x = (file_path, 'minified/%s?%s' % (compiled_file_name, tryInt(time.time()))) self.watchFile(file_path, x[1], compiled_path = compiled_file_path) if not Env.get('dev'): if file_type == 'script': data = f else: data = self.prefix(f) data = data.replace('../images/', '../static/images/') data = data.replace('../fonts/', '../static/fonts/') data = data.replace('../../static/', '../static/') # Replace inside plugins data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path))) data_combined += data + '\n\n' else: new_paths.append(x) # Combine all files together with some comments if not Env.get('dev'): out_path = os.path.join(minified_dir, out_name) self.createFile(out_path, data_combined.strip()) minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) new_paths.append((out_path, {'url': minified_url})) self.paths[file_type][position] = new_paths 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, location = 'head'): paths = self.paths[type][location] return [x[1] for x in paths] 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): self.watchFile(file_path, api_path) api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) if not self.original_paths[type].get(location): self.original_paths[type][location] = [] self.original_paths[type][location].append((file_path, api_path)) if not self.paths[type].get(location): self.paths[type][location] = [] self.paths[type][location].append((file_path, api_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