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

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) 

9 

10 

11class TokenBase: 

12 """ 

13 Base class for operators and literals, mainly for debugging and for throwing 

14 syntax errors. 

15 """ 

16 

17 id = None # node/token type name 

18 value = None # used by literals 

19 first = second = None # used by tree nodes 

20 

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 ) 

26 

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 ) 

32 

33 def display(self): 

34 """ 

35 Return what to display in error messages for this node 

36 """ 

37 return self.id 

38 

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) + ")" 

42 

43 

44def infix(bp, func): 

45 """ 

46 Create an infix operator, given a binding power and a function that 

47 evaluates the node. 

48 """ 

49 

50 class Operator(TokenBase): 

51 lbp = bp 

52 

53 def led(self, left, parser): 

54 self.first = left 

55 self.second = parser.expression(bp) 

56 return self 

57 

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 

66 

67 return Operator 

68 

69 

70def prefix(bp, func): 

71 """ 

72 Create a prefix operator, given a binding power and a function that 

73 evaluates the node. 

74 """ 

75 

76 class Operator(TokenBase): 

77 lbp = bp 

78 

79 def nud(self, parser): 

80 self.first = parser.expression(bp) 

81 self.second = None 

82 return self 

83 

84 def eval(self, context): 

85 try: 

86 return func(context, self.first) 

87 except Exception: 

88 return False 

89 

90 return Operator 

91 

92 

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} 

111 

112# Assign 'id' to each: 

113for key, op in OPERATORS.items(): 

114 op.id = key 

115 

116 

117class Literal(TokenBase): 

118 """ 

119 A basic self-resolvable object similar to a Django template variable. 

120 """ 

121 

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 

127 

128 def __init__(self, value): 

129 self.value = value 

130 

131 def display(self): 

132 return repr(self.value) 

133 

134 def nud(self, parser): 

135 return self 

136 

137 def eval(self, context): 

138 return self.value 

139 

140 def __repr__(self): 

141 return "(%s %r)" % (self.id, self.value) 

142 

143 

144class EndToken(TokenBase): 

145 lbp = 0 

146 

147 def nud(self, parser): 

148 raise parser.error_class("Unexpected end of expression in if tag.") 

149 

150 

151EndToken = EndToken() 

152 

153 

154class IfParser: 

155 error_class = ValueError 

156 

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 

172 

173 self.tokens = mapped_tokens 

174 self.pos = 0 

175 self.current_token = self.next_token() 

176 

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() 

184 

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 

192 

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 

201 

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 

211 

212 def create_var(self, value): 

213 return Literal(value)