1# -*- coding: utf-8 -*-
2from ._compat import imap
3from ._compat import implements_to_string
4from ._compat import PY2
5from ._compat import text_type
6
7
8class TemplateError(Exception):
9    """Baseclass for all template errors."""
10
11    if PY2:
12
13        def __init__(self, message=None):
14            if message is not None:
15                message = text_type(message).encode("utf-8")
16            Exception.__init__(self, message)
17
18        @property
19        def message(self):
20            if self.args:
21                message = self.args[0]
22                if message is not None:
23                    return message.decode("utf-8", "replace")
24
25        def __unicode__(self):
26            return self.message or u""
27
28    else:
29
30        def __init__(self, message=None):
31            Exception.__init__(self, message)
32
33        @property
34        def message(self):
35            if self.args:
36                message = self.args[0]
37                if message is not None:
38                    return message
39
40
41@implements_to_string
42class TemplateNotFound(IOError, LookupError, TemplateError):
43    """Raised if a template does not exist.
44
45    .. versionchanged:: 2.11
46        If the given name is :class:`Undefined` and no message was
47        provided, an :exc:`UndefinedError` is raised.
48    """
49
50    # looks weird, but removes the warning descriptor that just
51    # bogusly warns us about message being deprecated
52    message = None
53
54    def __init__(self, name, message=None):
55        IOError.__init__(self, name)
56
57        if message is None:
58            from .runtime import Undefined
59
60            if isinstance(name, Undefined):
61                name._fail_with_undefined_error()
62
63            message = name
64
65        self.message = message
66        self.name = name
67        self.templates = [name]
68
69    def __str__(self):
70        return self.message
71
72
73class TemplatesNotFound(TemplateNotFound):
74    """Like :class:`TemplateNotFound` but raised if multiple templates
75    are selected.  This is a subclass of :class:`TemplateNotFound`
76    exception, so just catching the base exception will catch both.
77
78    .. versionchanged:: 2.11
79        If a name in the list of names is :class:`Undefined`, a message
80        about it being undefined is shown rather than the empty string.
81
82    .. versionadded:: 2.2
83    """
84
85    def __init__(self, names=(), message=None):
86        if message is None:
87            from .runtime import Undefined
88
89            parts = []
90
91            for name in names:
92                if isinstance(name, Undefined):
93                    parts.append(name._undefined_message)
94                else:
95                    parts.append(name)
96
97            message = u"none of the templates given were found: " + u", ".join(
98                imap(text_type, parts)
99            )
100        TemplateNotFound.__init__(self, names and names[-1] or None, message)
101        self.templates = list(names)
102
103
104@implements_to_string
105class TemplateSyntaxError(TemplateError):
106    """Raised to tell the user that there is a problem with the template."""
107
108    def __init__(self, message, lineno, name=None, filename=None):
109        TemplateError.__init__(self, message)
110        self.lineno = lineno
111        self.name = name
112        self.filename = filename
113        self.source = None
114
115        # this is set to True if the debug.translate_syntax_error
116        # function translated the syntax error into a new traceback
117        self.translated = False
118
119    def __str__(self):
120        # for translated errors we only return the message
121        if self.translated:
122            return self.message
123
124        # otherwise attach some stuff
125        location = "line %d" % self.lineno
126        name = self.filename or self.name
127        if name:
128            location = 'File "%s", %s' % (name, location)
129        lines = [self.message, "  " + location]
130
131        # if the source is set, add the line to the output
132        if self.source is not None:
133            try:
134                line = self.source.splitlines()[self.lineno - 1]
135            except IndexError:
136                line = None
137            if line:
138                lines.append("    " + line.strip())
139
140        return u"\n".join(lines)
141
142    def __reduce__(self):
143        # https://bugs.python.org/issue1692335 Exceptions that take
144        # multiple required arguments have problems with pickling.
145        # Without this, raises TypeError: __init__() missing 1 required
146        # positional argument: 'lineno'
147        return self.__class__, (self.message, self.lineno, self.name, self.filename)
148
149
150class TemplateAssertionError(TemplateSyntaxError):
151    """Like a template syntax error, but covers cases where something in the
152    template caused an error at compile time that wasn't necessarily caused
153    by a syntax error.  However it's a direct subclass of
154    :exc:`TemplateSyntaxError` and has the same attributes.
155    """
156
157
158class TemplateRuntimeError(TemplateError):
159    """A generic runtime error in the template engine.  Under some situations
160    Jinja may raise this exception.
161    """
162
163
164class UndefinedError(TemplateRuntimeError):
165    """Raised if a template tries to operate on :class:`Undefined`."""
166
167
168class SecurityError(TemplateRuntimeError):
169    """Raised if a template tries to do something insecure if the
170    sandbox is enabled.
171    """
172
173
174class FilterArgumentError(TemplateRuntimeError):
175    """This error is raised if a filter was called with inappropriate
176    arguments
177    """
178