diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-01 17:50:45 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-01 17:50:45 -0700 |
commit | 7ec86928d51f8595aaa89ddfda894e2fbf0d22ce (patch) | |
tree | cf227eb0649ccaf15c81731fff315849d26398eb | |
parent | 817a1d0a9ce16d30690bcffe25d6d74fc4f35046 (diff) | |
download | pyscss-7ec86928d51f8595aaa89ddfda894e2fbf0d22ce.tar.gz |
Support showing compilation problems in-browser. #150
-rw-r--r-- | scss/__init__.py | 18 | ||||
-rw-r--r-- | scss/errors.py | 100 | ||||
-rw-r--r-- | scss/tests/test_misc.py | 7 |
3 files changed, 107 insertions, 18 deletions
diff --git a/scss/__init__.py b/scss/__init__.py index b0487be..f2c6c80 100644 --- a/scss/__init__.py +++ b/scss/__init__.py @@ -293,7 +293,10 @@ class SourceFile(object): class Scss(object): - def __init__(self, scss_vars=None, scss_opts=None, scss_files=None, super_selector=None, library=ALL_BUILTINS_LIBRARY, func_registry=None, search_paths=None): + def __init__(self, + scss_vars=None, scss_opts=None, scss_files=None, super_selector=None, + live_errors=False, library=ALL_BUILTINS_LIBRARY, func_registry=None, search_paths=None): + if super_selector: self.super_selector = super_selector + ' ' else: @@ -319,6 +322,9 @@ class Scss(object): self._library = func_registry or library self._search_paths = search_paths + # If true, swallow compile errors and embed them in the output instead + self.live_errors = live_errors + self.reset() def get_scss_constants(self): @@ -434,7 +440,15 @@ class Scss(object): return final_cont - compile = Compilation + def compile(self, *args, **kwargs): + try: + return self.Compilation(*args, **kwargs) + except SassError as e: + if self.live_errors: + # TODO should this setting also capture and display warnings? + return e.to_css() + else: + raise def parse_selectors(self, raw_selectors): """ diff --git a/scss/errors.py b/scss/errors.py index c898500..52a8f5b 100644 --- a/scss/errors.py +++ b/scss/errors.py @@ -2,6 +2,28 @@ import sys import traceback +BROWSER_ERROR_TEMPLATE = """\ +body:before {{ + content: {0}; + + display: block; + position: fixed; + top: 0; + left: 0; + right: 0; + + font-size: 14px; + margin: 1em; + padding: 1em; + border: 3px double red; + + white-space: pre; + font-family: monospace; + background: #fcebeb; + color: black; +}} +""" + def add_error_marker(text, position, start_line=1): """Add a caret marking a given position in a string of input. @@ -47,34 +69,80 @@ class SassError(Exception): self.rule_stack.append(rule) def format_prefix(self): + """Return the general name of the error and the contents of the rule or + property that caused the failure. This is the initial part of the + error message and should be error-specific. + """ # TODO this contains NULs and line numbers; could be much prettier if self.rule_stack: return "Error parsing block:\n" + " " + self.rule_stack[0].unparsed_contents else: return "Unknown error" - def __str__(self): - prefix = self.format_prefix() + def format_sass_stack(self): + """Return a "traceback" of Sass imports.""" + if not self.rule_stack: + return "" - ret = [prefix, "\n\n"] + ret = ["From ", self.rule_stack[0].file_and_line, "\n"] + last_file = self.rule_stack[0].source_file - if self.rule_stack: - # TODO this looks very neat-o but doesn't actually work because - # manage_children doesn't recurse for imports :) - ret.extend(("From ", self.rule_stack[0].file_and_line, "\n")) - last_file = self.rule_stack[0].source_file + # TODO this could go away if rules knew their import chains... + for rule in self.rule_stack[1:]: + if rule.source_file is not last_file: + ret.extend(("...imported from ", rule.file_and_line, "\n")) + last_file = rule.source_file - for rule in self.rule_stack[1:]: - if rule.source_file is not last_file: - ret.extend(("...imported from ", rule.file_and_line, "\n")) - last_file = rule.source_file + ret.append("\n") - ret.append("\n") + return "".join(ret) - ret.append("Traceback:\n") + def format_python_stack(self): + """Return a traceback of Python frames, from where the error occurred + to where it was first caught and wrapped. + """ + ret = ["Traceback:\n"] ret.extend(traceback.format_tb(self.original_traceback)) - ret.extend((type(self.exc).__name__, ": ", str(self.exc), "\n")) - return ''.join(ret) + return "".join(ret) + + def format_original_error(self): + """Return the typical "TypeError: blah blah" for the original wrapped + error. + """ + # TODO eventually we'll have sass-specific errors that will want nicer + # "names" in browser display and stderr + return "".join((type(self.exc).__name__, ": ", str(self.exc), "\n")) + + def __str__(self): + try: + prefix = self.format_prefix() + sass_stack = self.format_sass_stack() + python_stack = self.format_python_stack() + original_error = self.format_original_error() + + return prefix + sass_stack + python_stack + original_error + except Exception: + # "unprintable error" is not helpful + return str(self.exc) + + def to_css(self): + """Return a stylesheet that will show the wrapped error at the top of + the browser window. + """ + # TODO should this include the traceback? any security concerns? + prefix = self.format_prefix() + original_error = self.format_original_error() + sass_stack = self.format_sass_stack() + + message = prefix + original_error + sass_stack + + # Super simple escaping: only quotes and newlines are illegal in css + # strings + message = message.replace('\\', '\\\\') + message = message.replace('"', '\\"') + message = message.replace('\n', '\\A') + + return BROWSER_ERROR_TEMPLATE.format('"' + message + '"') class SassParseError(SassError): diff --git a/scss/tests/test_misc.py b/scss/tests/test_misc.py index 9cf2cb3..d19ab18 100644 --- a/scss/tests/test_misc.py +++ b/scss/tests/test_misc.py @@ -57,6 +57,13 @@ table { assert expected == output +def test_live_errors(): + compiler = Scss(live_errors=True) + output = compiler.compile("""$foo: unitless(one);""") + assert "body:before" in output + assert "TypeError: Expected" in output + + def test_extend_across_files(): compiler = Scss(scss_opts=dict(compress=0)) compiler._scss_files = {} |