~corey.bryant/ubuntu/wily/python-pyscss/thedac

« back to all changes in this revision

Viewing changes to scss/errors.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2014-06-26 12:10:36 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20140626121036-3dv13zn5zptk9fpx
Tags: 1.2.0.post3-1
* Team upload.
* New upstream release (Closes: #738776).
* Added a debian/gbp.conf.
* Added missing ${python:Depends}
* Added Python 3 support.
* Removed duplicate debhelper build-depends.
* Cannonical VCS URLs.
* Standards-Version: is now 3.9.5.
* Added a watch file.
* override dh helpers which the package doesn't use.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import sys
 
2
import traceback
 
3
 
 
4
 
 
5
BROWSER_ERROR_TEMPLATE = """\
 
6
body:before {{
 
7
    content: {0};
 
8
 
 
9
    display: block;
 
10
    position: fixed;
 
11
    top: 0;
 
12
    left: 0;
 
13
    right: 0;
 
14
 
 
15
    font-size: 14px;
 
16
    margin: 1em;
 
17
    padding: 1em;
 
18
    border: 3px double red;
 
19
 
 
20
    white-space: pre;
 
21
    font-family: monospace;
 
22
    background: #fcebeb;
 
23
    color: black;
 
24
}}
 
25
"""
 
26
 
 
27
def add_error_marker(text, position, start_line=1):
 
28
    """Add a caret marking a given position in a string of input.
 
29
 
 
30
    Returns (new_text, caret_line).
 
31
    """
 
32
    indent = "    "
 
33
    lines = []
 
34
    caret_line = start_line
 
35
    for line in text.split("\n"):
 
36
        lines.append(indent + line)
 
37
 
 
38
        if 0 <= position <= len(line):
 
39
            lines.append(indent + (" " * position) + "^")
 
40
            caret_line = start_line
 
41
 
 
42
        position -= len(line)
 
43
        position -= 1  # for the newline
 
44
        start_line += 1
 
45
 
 
46
    return "\n".join(lines), caret_line
 
47
 
 
48
 
 
49
class SassError(Exception):
 
50
    """Error class that wraps another exception and attempts to bolt on some
 
51
    useful context.
 
52
    """
 
53
    def __init__(self, exc, rule=None, expression=None, expression_pos=None):
 
54
        self.exc = exc
 
55
 
 
56
        self.rule_stack = []
 
57
        if rule:
 
58
            self.rule_stack.append(rule)
 
59
 
 
60
        self.expression = expression
 
61
        self.expression_pos = expression_pos
 
62
 
 
63
        _, _, self.original_traceback = sys.exc_info()
 
64
 
 
65
    def add_rule(self, rule):
 
66
        """Add a new rule to the "stack" of rules -- this is used to track,
 
67
        e.g., how a file was ultimately imported.
 
68
        """
 
69
        self.rule_stack.append(rule)
 
70
 
 
71
    def format_prefix(self):
 
72
        """Return the general name of the error and the contents of the rule or
 
73
        property that caused the failure.  This is the initial part of the
 
74
        error message and should be error-specific.
 
75
        """
 
76
        # TODO this contains NULs and line numbers; could be much prettier
 
77
        if self.rule_stack:
 
78
            return (
 
79
                "Error parsing block:\n" +
 
80
                "    " + self.rule_stack[0].unparsed_contents + "\n"
 
81
            )
 
82
        else:
 
83
            return "Unknown error\n"
 
84
 
 
85
    def format_sass_stack(self):
 
86
        """Return a "traceback" of Sass imports."""
 
87
        if not self.rule_stack:
 
88
            return ""
 
89
 
 
90
        ret = ["From ", self.rule_stack[0].file_and_line, "\n"]
 
91
        last_file = self.rule_stack[0].source_file
 
92
 
 
93
        # TODO this could go away if rules knew their import chains...
 
94
        for rule in self.rule_stack[1:]:
 
95
            if rule.source_file is not last_file:
 
96
                ret.extend(("...imported from ", rule.file_and_line, "\n"))
 
97
            last_file = rule.source_file
 
98
 
 
99
        return "".join(ret)
 
100
 
 
101
    def format_python_stack(self):
 
102
        """Return a traceback of Python frames, from where the error occurred
 
103
        to where it was first caught and wrapped.
 
104
        """
 
105
        ret = ["Traceback:\n"]
 
106
        ret.extend(traceback.format_tb(self.original_traceback))
 
107
        return "".join(ret)
 
108
 
 
109
    def format_original_error(self):
 
110
        """Return the typical "TypeError: blah blah" for the original wrapped
 
111
        error.
 
112
        """
 
113
        # TODO eventually we'll have sass-specific errors that will want nicer
 
114
        # "names" in browser display and stderr
 
115
        return "".join((type(self.exc).__name__, ": ", str(self.exc), "\n"))
 
116
 
 
117
    def __str__(self):
 
118
        try:
 
119
            prefix = self.format_prefix()
 
120
            sass_stack = self.format_sass_stack()
 
121
            python_stack = self.format_python_stack()
 
122
            original_error = self.format_original_error()
 
123
 
 
124
            # TODO not very well-specified whether these parts should already
 
125
            # end in newlines, or how many
 
126
            return prefix + "\n" + sass_stack + python_stack + original_error
 
127
        except Exception:
 
128
            # "unprintable error" is not helpful
 
129
            return str(self.exc)
 
130
 
 
131
    def to_css(self):
 
132
        """Return a stylesheet that will show the wrapped error at the top of
 
133
        the browser window.
 
134
        """
 
135
        # TODO should this include the traceback?  any security concerns?
 
136
        prefix = self.format_prefix()
 
137
        original_error = self.format_original_error()
 
138
        sass_stack = self.format_sass_stack()
 
139
 
 
140
        message = prefix + "\n" + sass_stack + original_error
 
141
 
 
142
        # Super simple escaping: only quotes and newlines are illegal in css
 
143
        # strings
 
144
        message = message.replace('\\', '\\\\')
 
145
        message = message.replace('"', '\\"')
 
146
        # use the maximum six digits here so it doesn't eat any following
 
147
        # characters that happen to look like hex
 
148
        message = message.replace('\n', '\\00000A')
 
149
 
 
150
        return BROWSER_ERROR_TEMPLATE.format('"' + message + '"')
 
151
 
 
152
 
 
153
class SassParseError(SassError):
 
154
    """Error raised when parsing a Sass expression fails."""
 
155
 
 
156
    def format_prefix(self):
 
157
        decorated_expr, line = add_error_marker(self.expression, self.expression_pos or -1)
 
158
        return """Error parsing expression:\n{0}\n""".format(decorated_expr)
 
159
 
 
160
 
 
161
class SassEvaluationError(SassError):
 
162
    """Error raised when evaluating a parsed expression fails."""
 
163
 
 
164
    def format_prefix(self):
 
165
        # TODO would be nice for the AST to have position information
 
166
        # TODO might be nice to print the AST and indicate where the failure
 
167
        # was?
 
168
        decorated_expr, line = add_error_marker(self.expression, self.expression_pos or -1)
 
169
        return """Error evaluating expression:\n{0}\n""".format(decorated_expr)