summaryrefslogtreecommitdiff
path: root/scss/errors.py
diff options
context:
space:
mode:
Diffstat (limited to 'scss/errors.py')
-rw-r--r--scss/errors.py77
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