From 5ae0fffc030353e697acbd26e6145364afc9ffc8 Mon Sep 17 00:00:00 2001 From: "Eevee (Alex Munroe)" Date: Thu, 28 Aug 2014 13:50:49 -0700 Subject: Shuffle exception hierarchy a bit. --- scss/compiler.py | 14 ++++---- scss/errors.py | 105 +++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/scss/compiler.py b/scss/compiler.py index e07b515..ac9dea7 100644 --- a/scss/compiler.py +++ b/scss/compiler.py @@ -20,6 +20,8 @@ from scss.cssdefs import _spaces_re from scss.cssdefs import _escape_chars_re from scss.cssdefs import _prop_split_re from scss.errors import SassError +from scss.errors import SassBaseError +from scss.errors import SassImportError from scss.expression import Calculator from scss.extension import Extension from scss.extension.core import CoreExtension @@ -322,9 +324,7 @@ class Compilation(object): def manage_children(self, rule, scope): try: self._manage_children_impl(rule, scope) - except SassReturn: - raise - except SassError as e: + except SassBaseError as e: e.add_rule(rule) raise except Exception as e: @@ -815,7 +815,7 @@ class Compilation(object): generated_code = self._at_magic_import( calculator, rule, scope, block) if generated_code is None: - raise + raise SassImportError(sass_path, self.compiler, rule=rule) source = SourceFile.from_string(generated_code) else: @@ -1587,15 +1587,13 @@ class Compilation(object): return result -# TODO: this should inherit from SassError, but can't, because that assumes -# it's wrapping another error. fix this with the exception hierarchy -class SassReturn(Exception): +class SassReturn(SassBaseError): """Special control-flow exception used to hop up the stack from a Sass function's ``@return``. """ def __init__(self, retval): + super(SassReturn, self).__init__() self.retval = retval - Exception.__init__(self) def __str__(self): return "Returning {0!r}".format(self.retval) diff --git a/scss/errors.py b/scss/errors.py index b3b4d53..b1e7f12 100644 --- a/scss/errors.py +++ b/scss/errors.py @@ -30,6 +30,7 @@ body:before {{ }} """ + def add_error_marker(text, position, start_line=1): """Add a caret marking a given position in a string of input. @@ -52,21 +53,18 @@ def add_error_marker(text, position, start_line=1): return "\n".join(lines), caret_line -class SassError(Exception): - """Error class that wraps another exception and attempts to bolt on some - useful context. +class SassBaseError(Exception): + """Base class for all errors caused by Sass code. + + Shouldn't be raising this directly; use or create a subclass instead. """ - def __init__(self, exc, rule=None, expression=None, expression_pos=None): - self.exc = exc - self.rule_stack = [] - if rule: - self.rule_stack.append(rule) + def __init__(self, rule=None): + super(SassBaseError, self).__init__() - self.expression = expression - self.expression_pos = expression_pos - - _, _, self.original_traceback = sys.exc_info() + self.rule_stack = [] + if rule is not None: + self.add_rule(rule) def add_rule(self, rule): """Add a new rule to the "stack" of rules -- this is used to track, @@ -74,36 +72,88 @@ 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 + "\n" - ) - else: - return "Unknown error\n" + def format_file_and_line(self, rule): + return "line {rule.lineno} of {rule.source_file.path}".format( + rule=rule, + ) def format_sass_stack(self): """Return a "traceback" of Sass imports.""" if not self.rule_stack: return "" - ret = ["From ", self.rule_stack[0].file_and_line, "\n"] + ret = ["on ", self.format_file_and_line(self.rule_stack[0]), "\n"] last_file = self.rule_stack[0].source_file # TODO this could go away if rules knew their import chains... + # TODO mixins and the like here too? + # TODO the line number is wrong here, since this doesn't include the + # *block* being parsed! for rule in self.rule_stack[1:]: if rule.source_file is not last_file: - ret.extend(("...imported from ", rule.file_and_line, "\n")) + ret.extend(( + "imported from ", self.format_file_and_line(rule), "\n")) last_file = rule.source_file return "".join(ret) + def format_message(self): + return "" + + def __str__(self): + return "{message}\n{sass_stack}".format( + message=self.format_message(), + sass_stack=self.format_sass_stack(), + ) + + +class SassImportError(SassBaseError): + """Error raised when unable to resolve an @import.""" + + def __init__(self, bad_name, compiler, **kwargs): + super(SassImportError, self).__init__(**kwargs) + + self.bad_name = bad_name + self.compiler = compiler + + def format_message(self): + return ( + "Couldn't find anything to import: {0}\n" + "Search path:\n {1}" + .format( + self.bad_name, + "\n ".join(self.compiler.search_path), + ) + ) + + +class SassError(SassBaseError): + """Error class that wraps another exception and attempts to bolt on some + useful context. + """ + def __init__(self, exc, expression=None, expression_pos=None, **kwargs): + super(SassError, self).__init__(**kwargs) + + self.exc = exc + self.expression = expression + self.expression_pos = expression_pos + + _, _, self.original_traceback = sys.exc_info() + + 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 + "\n" + ) + else: + return "Unknown error\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. @@ -170,6 +220,7 @@ class SassEvaluationError(SassError): """Error raised when evaluating a parsed expression fails.""" def format_prefix(self): + # TODO boy this is getting repeated a lot # TODO would be nice for the AST to have position information # TODO might be nice to print the AST and indicate where the failure # was? -- cgit v1.2.1