blob: 51ffc93a29a2ae1d2df5814dce1927ec3f52c104 [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.datastructure
4 ~~~~~~~~~~~~~~~~~~~~
5
6 Module that helds several data types used in the template engine.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10"""
11from operator import itemgetter
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020012from collections import deque
13from jinja2.exceptions import TemplateSyntaxError, TemplateRuntimeError
Armin Ronacher07bc6842008-03-31 14:18:49 +020014
15
Armin Ronacher07bc6842008-03-31 14:18:49 +020016class Token(tuple):
17 """
18 Token class.
19 """
20 __slots__ = ()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020021 lineno, type, value = (property(itemgetter(x)) for x in range(3))
Armin Ronacher07bc6842008-03-31 14:18:49 +020022
23 def __new__(cls, lineno, type, value):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020024 return tuple.__new__(cls, (lineno, intern(str(type)), value))
Armin Ronacher07bc6842008-03-31 14:18:49 +020025
26 def __str__(self):
27 from jinja.lexer import keywords, reverse_operators
28 if self.type in keywords:
29 return self.type
30 elif self.type in reverse_operators:
31 return reverse_operators[self.type]
Armin Ronacheraaf010d2008-05-01 13:14:30 +020032 elif self.type is 'name':
33 return self.value
34 return self.type
Armin Ronacherf59bac22008-04-20 13:11:43 +020035
36 def test(self, expr):
37 """Test a token against a token expression. This can either be a
38 token type or 'token_type:token_value'. This can only test against
39 string values!
40 """
41 # here we do a regular string equality check as test_many is usually
42 # passed an iterable of not interned strings.
43 if self.type == expr:
44 return True
45 elif ':' in expr:
46 return expr.split(':', 1) == [self.type, self.value]
47 return False
48
49 def test_many(self, iterable):
50 """Test against multiple token expressions."""
51 for expr in iterable:
52 if self.test(expr):
53 return True
54 return False
Armin Ronacher07bc6842008-03-31 14:18:49 +020055
56 def __repr__(self):
57 return 'Token(%r, %r, %r)' % (
58 self.lineno,
59 self.type,
60 self.value
61 )
62
63
64class TokenStreamIterator(object):
65 """
66 The iterator for tokenstreams. Iterate over the stream
67 until the eof token is reached.
68 """
69
70 def __init__(self, stream):
71 self._stream = stream
72
73 def __iter__(self):
74 return self
75
76 def next(self):
77 token = self._stream.current
78 if token.type == 'eof':
79 self._stream.close()
80 raise StopIteration()
Armin Ronachere791c2a2008-04-07 18:39:54 +020081 self._stream.next(False)
Armin Ronacher07bc6842008-03-31 14:18:49 +020082 return token
83
84
85class TokenStream(object):
86 """
87 A token stream wraps a generator and supports pushing tokens back.
88 It also provides some functions to expect tokens and similar stuff.
89
90 Important note: Do never push more than one token back to the
91 stream. Although the stream object won't stop you
92 from doing so, the behavior is undefined. Multiple
93 pushed tokens are only used internally!
94 """
95
96 def __init__(self, generator, filename):
97 self._next = generator.next
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020098 self._pushed = deque()
Armin Ronacher07bc6842008-03-31 14:18:49 +020099 self.current = Token(1, 'initial', '')
100 self.filename = filename
101 self.next()
102
103 def __iter__(self):
104 return TokenStreamIterator(self)
105
Armin Ronacher07bc6842008-03-31 14:18:49 +0200106 def __nonzero__(self):
107 """Are we at the end of the tokenstream?"""
108 return bool(self._pushed) or self.current.type != 'eof'
109
110 eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
111
112 def push(self, token):
113 """Push a token back to the stream."""
114 self._pushed.append(token)
115
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200116 def look(self):
117 """Look at the next token."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200118 old_token = self.next()
119 result = self.current
120 self.push(result)
121 self.current = old_token
122 return result
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200123
Armin Ronacher07bc6842008-03-31 14:18:49 +0200124 def skip(self, n):
125 """Got n tokens ahead."""
126 for x in xrange(n):
127 self.next()
128
Armin Ronachere791c2a2008-04-07 18:39:54 +0200129 def next(self, skip_eol=True):
130 """Go one token ahead and return the old one"""
131 rv = self.current
132 while 1:
133 if self._pushed:
134 self.current = self._pushed.popleft()
135 elif self.current.type is not 'eof':
136 try:
137 self.current = self._next()
138 except StopIteration:
139 self.close()
140 if not skip_eol or self.current.type is not 'eol':
141 break
142 return rv
Armin Ronacher07bc6842008-03-31 14:18:49 +0200143
144 def close(self):
145 """Close the stream."""
146 self.current = Token(self.current.lineno, 'eof', '')
147 self._next = None
148
Armin Ronacherf59bac22008-04-20 13:11:43 +0200149 def expect(self, expr):
Armin Ronacher07bc6842008-03-31 14:18:49 +0200150 """Expect a given token type and return it"""
Armin Ronacherf59bac22008-04-20 13:11:43 +0200151 if not self.current.test(expr):
Armin Ronacheraaf010d2008-05-01 13:14:30 +0200152 if ':' in expr:
153 expr = expr.split(':')[1]
154 if self.current.type is 'eof':
155 raise TemplateSyntaxError('unexpected end of template, '
156 'expected %r.' % expr,
157 self.current.lineno,
158 self.filename)
Armin Ronacher07bc6842008-03-31 14:18:49 +0200159 raise TemplateSyntaxError("expected token %r, got %r" %
Armin Ronacheraaf010d2008-05-01 13:14:30 +0200160 (expr, str(self.current)),
Armin Ronacher07bc6842008-03-31 14:18:49 +0200161 self.current.lineno,
162 self.filename)
163 try:
164 return self.current
165 finally:
166 self.next()