From ea3cb1f55e1be15b8c0639a3ed6bbfcc7a7c7b25 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 13 Aug 2014 11:02:43 +0200 Subject: [PATCH 1/4] [FIX] Do not top up all @imports before less/sass compilation This was preventing the bundles fragments to be restored after compilation --- openerp/addons/base/ir/ir_qweb.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 12c5dff139c..1ada0dba5df 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1172,17 +1172,17 @@ class AssetsBundle(object): command = to_compile[0].get_command() source = '\n'.join([asset.get_source() for asset in to_compile]) - # move up all @import rules to the top and exclude file imports + # sanitize @import rules and avoid duplicates imports imports = [] - def push(matchobj): + def sanitize(matchobj): + # TODO: check if a duplicate removal opt-out is necessary ref = matchobj.group(2) line = '@import "%s"%s' % (ref, matchobj.group(3)) if '.' not in ref and line not in imports and not ref.startswith(('.', '/', '~')): imports.append(line) + return line return '' - source = re.sub(self.rx_preprocess_imports, push, source) - imports.append(source) - source = u'\n'.join(imports) + source = re.sub(self.rx_preprocess_imports, sanitize, source) try: compiler = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE) From 5f7435a7b74a6a015a7918f46ab8365fa9842c0e Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 13 Aug 2014 11:38:19 +0200 Subject: [PATCH 2/4] [IMP] Use bundle compilation output instead of fragment recomposition --- openerp/addons/base/ir/ir_qweb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 1ada0dba5df..fc9b9fff7f4 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1124,8 +1124,7 @@ class AssetsBundle(object): # Invalidate cache on version mismach self.cache.pop(key) if key not in self.cache: - self.preprocess_css() - content = '\n'.join(asset.minify() for asset in self.stylesheets) + content = self.preprocess_css() if self.css_errors: msg = '\n'.join(self.css_errors) @@ -1165,7 +1164,7 @@ class AssetsBundle(object): """ to_preprocess = [asset for asset in self.stylesheets if isinstance(asset, PreprocessedCSS)] if not to_preprocess: - return + return '\n'.join(asset.minify() for asset in self.stylesheets) to_compile = [asset for asset in to_preprocess if type(asset) == type(to_preprocess[0])] if len(to_preprocess) != len(to_compile): self.css_errors.append("You can't mix css preprocessors languages in the same bundle. (%s)" % self.xmlid) @@ -1203,6 +1202,7 @@ class AssetsBundle(object): asset_id = fragments.pop(0) asset = next(asset for asset in to_compile if asset.id == asset_id) asset._content = fragments.pop(0) + return compiled def get_preprocessor_error(self, stderr, source=None): # TODO: try to find out which asset the error belongs to From d847a56d569622c998adb93411659f68d5d8b3c7 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 13 Aug 2014 13:00:55 +0200 Subject: [PATCH 3/4] [IMP] recompose bundle only if needed --- openerp/addons/base/ir/ir_qweb.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index fc9b9fff7f4..43b81615718 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1073,7 +1073,8 @@ class AssetsBundle(object): response = [] if debug: if css and self.stylesheets: - self.preprocess_css() + compiled = self.preprocess_css() + self.recompose(compiled) for style in self.stylesheets: response.append(style.to_html()) if js: @@ -1197,12 +1198,14 @@ class AssetsBundle(object): self.css_errors.append(error) return compiled = result[0].strip().decode('utf8') + return compiled + + def recompose(self, compiled): fragments = self.rx_css_split.split(compiled)[1:] while fragments: asset_id = fragments.pop(0) - asset = next(asset for asset in to_compile if asset.id == asset_id) + asset = next(asset for asset in self.stylesheets if asset.id == asset_id) asset._content = fragments.pop(0) - return compiled def get_preprocessor_error(self, stderr, source=None): # TODO: try to find out which asset the error belongs to From 005da0fae6ee6782d8e717017f117802d2e3eba3 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 13 Aug 2014 14:48:07 +0200 Subject: [PATCH 4/4] [IMP] Css preprocessors can now be mixed up in the same bundle --- openerp/addons/base/ir/ir_qweb.py | 45 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 43b81615718..6194c6fcb48 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1074,7 +1074,7 @@ class AssetsBundle(object): if debug: if css and self.stylesheets: compiled = self.preprocess_css() - self.recompose(compiled) + self.recompose_css(compiled) for style in self.stylesheets: response.append(style.to_html()) if js: @@ -1120,6 +1120,7 @@ class AssetsBundle(object): return self.cache[key][1] def css(self): + """Generate css content from given bundle""" key = 'css_%s' % self.xmlid if key in self.cache and self.cache[key][0] != self.version: # Invalidate cache on version mismach @@ -1162,20 +1163,33 @@ class AssetsBundle(object): def preprocess_css(self): """ Checks if the bundle contains any sass/less content, then compiles it to css. + Returns the bundle's flat css. """ - to_preprocess = [asset for asset in self.stylesheets if isinstance(asset, PreprocessedCSS)] - if not to_preprocess: - return '\n'.join(asset.minify() for asset in self.stylesheets) - to_compile = [asset for asset in to_preprocess if type(asset) == type(to_preprocess[0])] - if len(to_preprocess) != len(to_compile): - self.css_errors.append("You can't mix css preprocessors languages in the same bundle. (%s)" % self.xmlid) - command = to_compile[0].get_command() - source = '\n'.join([asset.get_source() for asset in to_compile]) + sass = [asset for asset in self.stylesheets if isinstance(asset, SassStylesheetAsset)] + if sass: + cmd = sass[0].get_command() + source = '\n'.join([asset.get_source() for asset in sass]) + compiled = self.compile_css(cmd, source) + self.recompose_css(compiled) - # sanitize @import rules and avoid duplicates imports + less = [asset for asset in self.stylesheets if isinstance(asset, LessStylesheetAsset)] + if less: + cmd = less[0].get_command() + source = '' + for asset in self.stylesheets: + if isinstance(asset, LessStylesheetAsset): + source += asset.get_source() + else: + source += asset.content + compiled = self.compile_css(cmd, source) + return compiled + + return '\n'.join(asset.minify() for asset in self.stylesheets) + + def compile_css(self, cmd, source): + """Sanitizes @import rules, remove duplicates @import rules, then compile""" imports = [] def sanitize(matchobj): - # TODO: check if a duplicate removal opt-out is necessary ref = matchobj.group(2) line = '@import "%s"%s' % (ref, matchobj.group(3)) if '.' not in ref and line not in imports and not ref.startswith(('.', '/', '~')): @@ -1185,9 +1199,9 @@ class AssetsBundle(object): source = re.sub(self.rx_preprocess_imports, sanitize, source) try: - compiler = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE) + compiler = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) except Exception: - msg = "Could not execute command %r" % command[0] + msg = "Could not execute command %r" % cmd[0] _logger.error(msg) self.css_errors.append(msg) return @@ -1200,7 +1214,8 @@ class AssetsBundle(object): compiled = result[0].strip().decode('utf8') return compiled - def recompose(self, compiled): + def recompose_css(self, compiled): + """Flattenize StylesheetAsset's content from compiled source""" fragments = self.rx_css_split.split(compiled)[1:] while fragments: asset_id = fragments.pop(0) @@ -1208,7 +1223,7 @@ class AssetsBundle(object): asset._content = fragments.pop(0) def get_preprocessor_error(self, stderr, source=None): - # TODO: try to find out which asset the error belongs to + """Improve and remove sensitive information from sass/less compilator error messages""" error = stderr.split('Load paths')[0].replace(' Use --trace for backtrace.', '') if 'Cannot load compass' in error: error += "Maybe you should install the compass gem using this extra argument:\n\n" \