diff options
Diffstat (limited to 'scss/errors.py')
-rw-r--r-- | scss/errors.py | 77 |
1 files changed, 56 insertions, 21 deletions
diff --git a/scss/errors.py b/scss/errors.py index 6e946e5..e06bc87 100644 --- a/scss/errors.py +++ b/scss/errors.py @@ -1,29 +1,53 @@ import sys import traceback + +def add_error_marker(text, position, start_line=1): + """Add a caret marking a given position in a string of input. + + Returns (new_text, caret_line). + """ + indent = " " + lines = [] + caret_line = start_line + for line in text.split("\n"): + lines.append(indent + line) + + if 0 <= position <= len(line): + lines.append(indent + (" " * position) + "^") + caret_line = start_line + + position -= len(line) + position -= 1 # for the newline + 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. """ - def __init__(self, exc, rule=None, expression=None): + def __init__(self, exc, rule=None, expression=None, expression_pos=None): self.exc = exc - self.rule = rule + + self.rule_stack = [] + if rule: + self.rule_stack.append(rule) + self.expression = expression - _, _, self.original_traceback = sys.exc_info() + self.expression_pos = expression_pos - def set_rule(self, rule): - """Set the current rule. + _, _, self.original_traceback = sys.exc_info() - If this error already has a rule, it's left alone; this is because - exceptions propagate outwards and so the first rule seen is the - "innermost". + def add_rule(self, rule): + """Add a new rule to the "stack" of rules -- this is used to track, + e.g., how a file was ultimately imported. """ - if not self.rule: - self.rule = rule + self.rule_stack.append(rule) def format_prefix(self): - # TODO pointer, yadda - # TODO newlines, yadda + # TODO this contains NULs and line numbers; could be much prettier if self.rule: return "Error parsing block:\n" + " " + self.rule.unparsed_contents else: @@ -34,28 +58,39 @@ class SassError(Exception): ret = [prefix, "\n\n"] - if self.rule: - ret.extend(("From ", self.rule.file_and_line, "\n\n")) + 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 + + 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("Traceback:\n") ret.extend(traceback.format_tb(self.original_traceback)) ret.extend((type(self.exc).__name__, ": ", str(self.exc), "\n")) return ''.join(ret) + class SassParseError(SassError): """Error raised when parsing a Sass expression fails.""" def format_prefix(self): - # TODO pointer - # TODO deal with newlines, etc - return """Error parsing expression:\n %s""" % (self.expression,) + decorated_expr, line = add_error_marker(self.expression, self.expression_pos or -1) + return """Error parsing expression:\n""" + decorated_expr class SassEvaluationError(SassError): """Error raised when evaluating a parsed expression fails.""" def format_prefix(self): - # TODO pointer - # TODO ast needs to know where each node came from for that to work :( - # TODO deal with newlines, etc - return """Error evaluating expression:\n %s""" % (self.expression,) + # 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? + decorated_expr, line = add_error_marker(self.expression, self.expression_pos or -1) + return """Error evaluating expression:\n""" + decorated_expr |