Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/template/smartif.py: 42%
107 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Parser and utilities for the smart 'if' tag
3"""
4# Using a simple top down parser, as described here:
5# http://effbot.org/zone/simple-top-down-parsing.htm.
6# 'led' = left denotation
7# 'nud' = null denotation
8# 'bp' = binding power (left = lbp, right = rbp)
11class TokenBase:
12 """
13 Base class for operators and literals, mainly for debugging and for throwing
14 syntax errors.
15 """
17 id = None # node/token type name
18 value = None # used by literals
19 first = second = None # used by tree nodes
21 def nud(self, parser):
22 # Null denotation - called in prefix context
23 raise parser.error_class(
24 "Not expecting '%s' in this position in if tag." % self.id
25 )
27 def led(self, left, parser):
28 # Left denotation - called in infix context
29 raise parser.error_class(
30 "Not expecting '%s' as infix operator in if tag." % self.id
31 )
33 def display(self):
34 """
35 Return what to display in error messages for this node
36 """
37 return self.id
39 def __repr__(self):
40 out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
41 return "(" + " ".join(out) + ")"
44def infix(bp, func):
45 """
46 Create an infix operator, given a binding power and a function that
47 evaluates the node.
48 """
50 class Operator(TokenBase):
51 lbp = bp
53 def led(self, left, parser):
54 self.first = left
55 self.second = parser.expression(bp)
56 return self
58 def eval(self, context):
59 try:
60 return func(context, self.first, self.second)
61 except Exception:
62 # Templates shouldn't throw exceptions when rendering. We are
63 # most likely to get exceptions for things like {% if foo in bar
64 # %} where 'bar' does not support 'in', so default to False
65 return False
67 return Operator
70def prefix(bp, func):
71 """
72 Create a prefix operator, given a binding power and a function that
73 evaluates the node.
74 """
76 class Operator(TokenBase):
77 lbp = bp
79 def nud(self, parser):
80 self.first = parser.expression(bp)
81 self.second = None
82 return self
84 def eval(self, context):
85 try:
86 return func(context, self.first)
87 except Exception:
88 return False
90 return Operator
93# Operator precedence follows Python.
94# We defer variable evaluation to the lambda to ensure that terms are
95# lazily evaluated using Python's boolean parsing logic.
96OPERATORS = { 96 ↛ exitline 96 didn't jump to the function exit
97 "or": infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
98 "and": infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
99 "not": prefix(8, lambda context, x: not x.eval(context)),
100 "in": infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
101 "not in": infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)),
102 "is": infix(10, lambda context, x, y: x.eval(context) is y.eval(context)),
103 "is not": infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)),
104 "==": infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
105 "!=": infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
106 ">": infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
107 ">=": infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
108 "<": infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
109 "<=": infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
110}
112# Assign 'id' to each:
113for key, op in OPERATORS.items():
114 op.id = key
117class Literal(TokenBase):
118 """
119 A basic self-resolvable object similar to a Django template variable.
120 """
122 # IfParser uses Literal in create_var, but TemplateIfParser overrides
123 # create_var so that a proper implementation that actually resolves
124 # variables, filters etc. is used.
125 id = "literal"
126 lbp = 0
128 def __init__(self, value):
129 self.value = value
131 def display(self):
132 return repr(self.value)
134 def nud(self, parser):
135 return self
137 def eval(self, context):
138 return self.value
140 def __repr__(self):
141 return "(%s %r)" % (self.id, self.value)
144class EndToken(TokenBase):
145 lbp = 0
147 def nud(self, parser):
148 raise parser.error_class("Unexpected end of expression in if tag.")
151EndToken = EndToken()
154class IfParser:
155 error_class = ValueError
157 def __init__(self, tokens):
158 # Turn 'is','not' and 'not','in' into single tokens.
159 num_tokens = len(tokens)
160 mapped_tokens = []
161 i = 0
162 while i < num_tokens:
163 token = tokens[i]
164 if token == "is" and i + 1 < num_tokens and tokens[i + 1] == "not":
165 token = "is not"
166 i += 1 # skip 'not'
167 elif token == "not" and i + 1 < num_tokens and tokens[i + 1] == "in":
168 token = "not in"
169 i += 1 # skip 'in'
170 mapped_tokens.append(self.translate_token(token))
171 i += 1
173 self.tokens = mapped_tokens
174 self.pos = 0
175 self.current_token = self.next_token()
177 def translate_token(self, token):
178 try:
179 op = OPERATORS[token]
180 except (KeyError, TypeError):
181 return self.create_var(token)
182 else:
183 return op()
185 def next_token(self):
186 if self.pos >= len(self.tokens):
187 return EndToken
188 else:
189 retval = self.tokens[self.pos]
190 self.pos += 1
191 return retval
193 def parse(self):
194 retval = self.expression()
195 # Check that we have exhausted all the tokens
196 if self.current_token is not EndToken:
197 raise self.error_class(
198 "Unused '%s' at end of if expression." % self.current_token.display()
199 )
200 return retval
202 def expression(self, rbp=0):
203 t = self.current_token
204 self.current_token = self.next_token()
205 left = t.nud(self)
206 while rbp < self.current_token.lbp:
207 t = self.current_token
208 self.current_token = self.next_token()
209 left = t.led(left, self)
210 return left
212 def create_var(self, value):
213 return Literal(value)