Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/template/smartif.py: 39%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

107 statements  

1""" 

2Parser and utilities for the smart 'if' tag 

3""" 

4 

5# Using a simple top down parser, as described here: 

6# https://11l-lang.org/archive/simple-top-down-parsing/ 

7# 'led' = left denotation 

8# 'nud' = null denotation 

9# 'bp' = binding power (left = lbp, right = rbp) 

10 

11 

12class TokenBase: 

13 """ 

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

15 syntax errors. 

16 """ 

17 

18 id = None # node/token type name 

19 value = None # used by literals 

20 first = second = None # used by tree nodes 

21 

22 def nud(self, parser): 

23 # Null denotation - called in prefix context 

24 raise parser.error_class( 

25 "Not expecting '%s' in this position in if tag." % self.id 

26 ) 

27 

28 def led(self, left, parser): 

29 # Left denotation - called in infix context 

30 raise parser.error_class( 

31 "Not expecting '%s' as infix operator in if tag." % self.id 

32 ) 

33 

34 def display(self): 

35 """ 

36 Return what to display in error messages for this node 

37 """ 

38 return self.id 

39 

40 def __repr__(self): 

41 out = [str(x) for x in [self.id, self.first, self.second] if x is not None] 

42 return "(" + " ".join(out) + ")" 

43 

44 

45def infix(bp, func): 

46 """ 

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

48 evaluates the node. 

49 """ 

50 

51 class Operator(TokenBase): 

52 lbp = bp 

53 

54 def led(self, left, parser): 

55 self.first = left 

56 self.second = parser.expression(bp) 

57 return self 

58 

59 def eval(self, context): 

60 try: 

61 return func(context, self.first, self.second) 

62 except Exception: 

63 # Templates shouldn't throw exceptions when rendering. We are 

64 # most likely to get exceptions for things like {% if foo in bar 

65 # %} where 'bar' does not support 'in', so default to False 

66 return False 

67 

68 return Operator 

69 

70 

71def prefix(bp, func): 

72 """ 

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

74 evaluates the node. 

75 """ 

76 

77 class Operator(TokenBase): 

78 lbp = bp 

79 

80 def nud(self, parser): 

81 self.first = parser.expression(bp) 

82 self.second = None 

83 return self 

84 

85 def eval(self, context): 

86 try: 

87 return func(context, self.first) 

88 except Exception: 

89 return False 

90 

91 return Operator 

92 

93 

94# Operator precedence follows Python. 

95# We defer variable evaluation to the lambda to ensure that terms are 

96# lazily evaluated using Python's boolean parsing logic. 

97OPERATORS = { 

98 "or": infix(6, lambda context, x, y: x.eval(context) or y.eval(context)), 

99 "and": infix(7, lambda context, x, y: x.eval(context) and y.eval(context)), 

100 "not": prefix(8, lambda context, x: not x.eval(context)), 

101 "in": infix(9, lambda context, x, y: x.eval(context) in y.eval(context)), 

102 "not in": infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)), 

103 "is": infix(10, lambda context, x, y: x.eval(context) is y.eval(context)), 

104 "is not": infix(10, lambda context, x, y: x.eval(context) is not 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 "<=": infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)), 

111} 

112 

113# Assign 'id' to each: 

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

115 op.id = key 

116 

117 

118class Literal(TokenBase): 

119 """ 

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

121 """ 

122 

123 # IfParser uses Literal in create_var, but TemplateIfParser overrides 

124 # create_var so that a proper implementation that actually resolves 

125 # variables, filters etc. is used. 

126 id = "literal" 

127 lbp = 0 

128 

129 def __init__(self, value): 

130 self.value = value 

131 

132 def display(self): 

133 return repr(self.value) 

134 

135 def nud(self, parser): 

136 return self 

137 

138 def eval(self, context): 

139 return self.value 

140 

141 def __repr__(self): 

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

143 

144 

145class EndToken(TokenBase): 

146 lbp = 0 

147 

148 def nud(self, parser): 

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

150 

151 

152EndToken = EndToken() 

153 

154 

155class IfParser: 

156 error_class = ValueError 

157 

158 def __init__(self, tokens): 

159 # Turn 'is','not' and 'not','in' into single tokens. 

160 num_tokens = len(tokens) 

161 mapped_tokens = [] 

162 i = 0 

163 while i < num_tokens: 

164 token = tokens[i] 

165 if token == "is" and i + 1 < num_tokens and tokens[i + 1] == "not": 

166 token = "is not" 

167 i += 1 # skip 'not' 

168 elif token == "not" and i + 1 < num_tokens and tokens[i + 1] == "in": 

169 token = "not in" 

170 i += 1 # skip 'in' 

171 mapped_tokens.append(self.translate_token(token)) 

172 i += 1 

173 

174 self.tokens = mapped_tokens 

175 self.pos = 0 

176 self.current_token = self.next_token() 

177 

178 def translate_token(self, token): 

179 try: 

180 op = OPERATORS[token] 

181 except (KeyError, TypeError): 

182 return self.create_var(token) 

183 else: 

184 return op() 

185 

186 def next_token(self): 

187 if self.pos >= len(self.tokens): 

188 return EndToken 

189 else: 

190 retval = self.tokens[self.pos] 

191 self.pos += 1 

192 return retval 

193 

194 def parse(self): 

195 retval = self.expression() 

196 # Check that we have exhausted all the tokens 

197 if self.current_token is not EndToken: 

198 raise self.error_class( 

199 "Unused '%s' at end of if expression." % self.current_token.display() 

200 ) 

201 return retval 

202 

203 def expression(self, rbp=0): 

204 t = self.current_token 

205 self.current_token = self.next_token() 

206 left = t.nud(self) 

207 while rbp < self.current_token.lbp: 

208 t = self.current_token 

209 self.current_token = self.next_token() 

210 left = t.led(left, self) 

211 return left 

212 

213 def create_var(self, value): 

214 return Literal(value)