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 12c5dff139cfd..1ada0dba5dfe2 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1172,17 +1172,17 @@ def preprocess_css(self): 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 1ada0dba5dfe2..fc9b9fff7f416 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1124,8 +1124,7 @@ def css(self): # 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 @@ def preprocess_css(self): """ 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 @@ def sanitize(matchobj): 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 fc9b9fff7f416..43b81615718c7 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1073,7 +1073,8 @@ def to_html(self, sep=None, css=True, js=True, debug=False): 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 @@ def sanitize(matchobj): 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 | 47 ++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 43b81615718c7..6194c6fcb48f6 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -1074,7 +1074,7 @@ def to_html(self, sep=None, css=True, js=True, debug=False): 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 @@ def js(self): 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 @@ def css_message(self, message): 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]) - - # sanitize @import rules and avoid duplicates imports + 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) + + 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 @@ def sanitize(matchobj): 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 @@ def sanitize(matchobj): 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 @@ def recompose(self, compiled): 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" \