OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """ | 2 """ |
3 jinja2.compiler | 3 jinja2.compiler |
4 ~~~~~~~~~~~~~~~ | 4 ~~~~~~~~~~~~~~~ |
5 | 5 |
6 Compiles nodes into python code. | 6 Compiles nodes into python code. |
7 | 7 |
8 :copyright: (c) 2010 by the Jinja Team. | 8 :copyright: (c) 2010 by the Jinja Team. |
9 :license: BSD, see LICENSE for more details. | 9 :license: BSD, see LICENSE for more details. |
10 """ | 10 """ |
11 from itertools import chain | 11 from itertools import chain |
12 from copy import deepcopy | 12 from copy import deepcopy |
13 from keyword import iskeyword as is_python_keyword | 13 from keyword import iskeyword as is_python_keyword |
14 from jinja2 import nodes | 14 from jinja2 import nodes |
15 from jinja2.nodes import EvalContext | 15 from jinja2.nodes import EvalContext |
16 from jinja2.visitor import NodeVisitor | 16 from jinja2.visitor import NodeVisitor |
17 from jinja2.exceptions import TemplateAssertionError | 17 from jinja2.exceptions import TemplateAssertionError |
18 from jinja2.utils import Markup, concat, escape | 18 from jinja2.utils import Markup, concat, escape |
19 from jinja2._compat import range_type, next, text_type, string_types, \ | 19 from jinja2._compat import range_type, text_type, string_types, \ |
20 iteritems, NativeStringIO, imap | 20 iteritems, NativeStringIO, imap |
21 | 21 |
22 | 22 |
23 operators = { | 23 operators = { |
24 'eq': '==', | 24 'eq': '==', |
25 'ne': '!=', | 25 'ne': '!=', |
26 'gt': '>', | 26 'gt': '>', |
27 'gteq': '>=', | 27 'gteq': '>=', |
28 'lt': '<', | 28 'lt': '<', |
29 'lteq': '<=', | 29 'lteq': '<=', |
(...skipping 20 matching lines...) Expand all Loading... |
50 # no closure is on the function | 50 # no closure is on the function |
51 unoptimize_before_dead_code = bool( | 51 unoptimize_before_dead_code = bool( |
52 getattr(unoptimize_before_dead_code(), '__closure__', None)) | 52 getattr(unoptimize_before_dead_code(), '__closure__', None)) |
53 | 53 |
54 | 54 |
55 def generate(node, environment, name, filename, stream=None, | 55 def generate(node, environment, name, filename, stream=None, |
56 defer_init=False): | 56 defer_init=False): |
57 """Generate the python source for a node tree.""" | 57 """Generate the python source for a node tree.""" |
58 if not isinstance(node, nodes.Template): | 58 if not isinstance(node, nodes.Template): |
59 raise TypeError('Can\'t compile non template nodes') | 59 raise TypeError('Can\'t compile non template nodes') |
60 generator = CodeGenerator(environment, name, filename, stream, defer_init) | 60 generator = environment.code_generator_class(environment, name, filename, |
| 61 stream, defer_init) |
61 generator.visit(node) | 62 generator.visit(node) |
62 if stream is None: | 63 if stream is None: |
63 return generator.stream.getvalue() | 64 return generator.stream.getvalue() |
64 | 65 |
65 | 66 |
66 def has_safe_repr(value): | 67 def has_safe_repr(value): |
67 """Does the node have a safe representation?""" | 68 """Does the node have a safe representation?""" |
68 if value is None or value is NotImplemented or value is Ellipsis: | 69 if value is None or value is NotImplemented or value is Ellipsis: |
69 return True | 70 return True |
70 if isinstance(value, (bool, int, float, complex, range_type, | 71 if isinstance(value, (bool, int, float, complex, range_type, |
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
340 is visited as part of the outer scope. | 341 is visited as part of the outer scope. |
341 """ | 342 """ |
342 self.visit(node.iter) | 343 self.visit(node.iter) |
343 | 344 |
344 def visit_CallBlock(self, node): | 345 def visit_CallBlock(self, node): |
345 self.visit(node.call) | 346 self.visit(node.call) |
346 | 347 |
347 def visit_FilterBlock(self, node): | 348 def visit_FilterBlock(self, node): |
348 self.visit(node.filter) | 349 self.visit(node.filter) |
349 | 350 |
| 351 def visit_AssignBlock(self, node): |
| 352 """Stop visiting at block assigns.""" |
| 353 |
350 def visit_Scope(self, node): | 354 def visit_Scope(self, node): |
351 """Stop visiting at scopes.""" | 355 """Stop visiting at scopes.""" |
352 | 356 |
353 def visit_Block(self, node): | 357 def visit_Block(self, node): |
354 """Stop visiting at blocks.""" | 358 """Stop visiting at blocks.""" |
355 | 359 |
356 | 360 |
357 class CompilerExit(Exception): | 361 class CompilerExit(Exception): |
358 """Raised if the compiler encountered a situation where it just | 362 """Raised if the compiler encountered a situation where it just |
359 doesn't make sense to further process the code. Any block that | 363 doesn't make sense to further process the code. Any block that |
(...skipping 848 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1208 def visit_ExprStmt(self, node, frame): | 1212 def visit_ExprStmt(self, node, frame): |
1209 self.newline(node) | 1213 self.newline(node) |
1210 self.visit(node.node, frame) | 1214 self.visit(node.node, frame) |
1211 | 1215 |
1212 def visit_Output(self, node, frame): | 1216 def visit_Output(self, node, frame): |
1213 # if we have a known extends statement, we don't output anything | 1217 # if we have a known extends statement, we don't output anything |
1214 # if we are in a require_output_check section | 1218 # if we are in a require_output_check section |
1215 if self.has_known_extends and frame.require_output_check: | 1219 if self.has_known_extends and frame.require_output_check: |
1216 return | 1220 return |
1217 | 1221 |
| 1222 allow_constant_finalize = True |
1218 if self.environment.finalize: | 1223 if self.environment.finalize: |
1219 finalize = lambda x: text_type(self.environment.finalize(x)) | 1224 func = self.environment.finalize |
| 1225 if getattr(func, 'contextfunction', False) or \ |
| 1226 getattr(func, 'evalcontextfunction', False): |
| 1227 allow_constant_finalize = False |
| 1228 elif getattr(func, 'environmentfunction', False): |
| 1229 finalize = lambda x: text_type( |
| 1230 self.environment.finalize(self.environment, x)) |
| 1231 else: |
| 1232 finalize = lambda x: text_type(self.environment.finalize(x)) |
1220 else: | 1233 else: |
1221 finalize = text_type | 1234 finalize = text_type |
1222 | 1235 |
1223 # if we are inside a frame that requires output checking, we do so | 1236 # if we are inside a frame that requires output checking, we do so |
1224 outdent_later = False | 1237 outdent_later = False |
1225 if frame.require_output_check: | 1238 if frame.require_output_check: |
1226 self.writeline('if parent_template is None:') | 1239 self.writeline('if parent_template is None:') |
1227 self.indent() | 1240 self.indent() |
1228 outdent_later = True | 1241 outdent_later = True |
1229 | 1242 |
1230 # try to evaluate as many chunks as possible into a static | 1243 # try to evaluate as many chunks as possible into a static |
1231 # string at compile time. | 1244 # string at compile time. |
1232 body = [] | 1245 body = [] |
1233 for child in node.nodes: | 1246 for child in node.nodes: |
1234 try: | 1247 try: |
| 1248 if not allow_constant_finalize: |
| 1249 raise nodes.Impossible() |
1235 const = child.as_const(frame.eval_ctx) | 1250 const = child.as_const(frame.eval_ctx) |
1236 except nodes.Impossible: | 1251 except nodes.Impossible: |
1237 body.append(child) | 1252 body.append(child) |
1238 continue | 1253 continue |
1239 # the frame can't be volatile here, becaus otherwise the | 1254 # the frame can't be volatile here, becaus otherwise the |
1240 # as_const() function would raise an Impossible exception | 1255 # as_const() function would raise an Impossible exception |
1241 # at that point. | 1256 # at that point. |
1242 try: | 1257 try: |
1243 if frame.eval_ctx.autoescape: | 1258 if frame.eval_ctx.autoescape: |
1244 if hasattr(const, '__html__'): | 1259 if hasattr(const, '__html__'): |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1280 close = 1 | 1295 close = 1 |
1281 if frame.eval_ctx.volatile: | 1296 if frame.eval_ctx.volatile: |
1282 self.write('(context.eval_ctx.autoescape and' | 1297 self.write('(context.eval_ctx.autoescape and' |
1283 ' escape or to_string)(') | 1298 ' escape or to_string)(') |
1284 elif frame.eval_ctx.autoescape: | 1299 elif frame.eval_ctx.autoescape: |
1285 self.write('escape(') | 1300 self.write('escape(') |
1286 else: | 1301 else: |
1287 self.write('to_string(') | 1302 self.write('to_string(') |
1288 if self.environment.finalize is not None: | 1303 if self.environment.finalize is not None: |
1289 self.write('environment.finalize(') | 1304 self.write('environment.finalize(') |
| 1305 if getattr(self.environment.finalize, |
| 1306 "contextfunction", False): |
| 1307 self.write('context, ') |
1290 close += 1 | 1308 close += 1 |
1291 self.visit(item, frame) | 1309 self.visit(item, frame) |
1292 self.write(')' * close) | 1310 self.write(')' * close) |
1293 if frame.buffer is not None: | 1311 if frame.buffer is not None: |
1294 self.write(', ') | 1312 self.write(', ') |
1295 if frame.buffer is not None: | 1313 if frame.buffer is not None: |
1296 # close the open parentheses | 1314 # close the open parentheses |
1297 self.outdent() | 1315 self.outdent() |
1298 self.writeline(len(body) == 1 and ')' or '))') | 1316 self.writeline(len(body) == 1 and ')' or '))') |
1299 | 1317 |
1300 # otherwise we create a format string as this is faster in that case | 1318 # otherwise we create a format string as this is faster in that case |
1301 else: | 1319 else: |
1302 format = [] | 1320 format = [] |
1303 arguments = [] | 1321 arguments = [] |
1304 for item in body: | 1322 for item in body: |
1305 if isinstance(item, list): | 1323 if isinstance(item, list): |
1306 format.append(concat(item).replace('%', '%%')) | 1324 format.append(concat(item).replace('%', '%%')) |
1307 else: | 1325 else: |
1308 format.append('%s') | 1326 format.append('%s') |
1309 arguments.append(item) | 1327 arguments.append(item) |
1310 self.writeline('yield ') | 1328 self.writeline('yield ') |
1311 self.write(repr(concat(format)) + ' % (') | 1329 self.write(repr(concat(format)) + ' % (') |
1312 idx = -1 | |
1313 self.indent() | 1330 self.indent() |
1314 for argument in arguments: | 1331 for argument in arguments: |
1315 self.newline(argument) | 1332 self.newline(argument) |
1316 close = 0 | 1333 close = 0 |
1317 if frame.eval_ctx.volatile: | 1334 if frame.eval_ctx.volatile: |
1318 self.write('(context.eval_ctx.autoescape and' | 1335 self.write('(context.eval_ctx.autoescape and' |
1319 ' escape or to_string)(') | 1336 ' escape or to_string)(') |
1320 close += 1 | 1337 close += 1 |
1321 elif frame.eval_ctx.autoescape: | 1338 elif frame.eval_ctx.autoescape: |
1322 self.write('escape(') | 1339 self.write('escape(') |
1323 close += 1 | 1340 close += 1 |
1324 if self.environment.finalize is not None: | 1341 if self.environment.finalize is not None: |
1325 self.write('environment.finalize(') | 1342 self.write('environment.finalize(') |
| 1343 if getattr(self.environment.finalize, |
| 1344 'contextfunction', False): |
| 1345 self.write('context, ') |
| 1346 elif getattr(self.environment.finalize, |
| 1347 'evalcontextfunction', False): |
| 1348 self.write('context.eval_ctx, ') |
| 1349 elif getattr(self.environment.finalize, |
| 1350 'environmentfunction', False): |
| 1351 self.write('environment, ') |
1326 close += 1 | 1352 close += 1 |
1327 self.visit(argument, frame) | 1353 self.visit(argument, frame) |
1328 self.write(')' * close + ', ') | 1354 self.write(')' * close + ', ') |
1329 self.outdent() | 1355 self.outdent() |
1330 self.writeline(')') | 1356 self.writeline(')') |
1331 | 1357 |
1332 if outdent_later: | 1358 if outdent_later: |
1333 self.outdent() | 1359 self.outdent() |
1334 | 1360 |
1335 def visit_Assign(self, node, frame): | 1361 def make_assignment_frame(self, frame): |
1336 self.newline(node) | |
1337 # toplevel assignments however go into the local namespace and | 1362 # toplevel assignments however go into the local namespace and |
1338 # the current template's context. We create a copy of the frame | 1363 # the current template's context. We create a copy of the frame |
1339 # here and add a set so that the Name visitor can add the assigned | 1364 # here and add a set so that the Name visitor can add the assigned |
1340 # names here. | 1365 # names here. |
1341 if frame.toplevel: | 1366 if not frame.toplevel: |
1342 assignment_frame = frame.copy() | 1367 return frame |
1343 assignment_frame.toplevel_assignments = set() | 1368 assignment_frame = frame.copy() |
| 1369 assignment_frame.toplevel_assignments = set() |
| 1370 return assignment_frame |
| 1371 |
| 1372 def export_assigned_vars(self, frame, assignment_frame): |
| 1373 if not frame.toplevel: |
| 1374 return |
| 1375 public_names = [x for x in assignment_frame.toplevel_assignments |
| 1376 if not x.startswith('_')] |
| 1377 if len(assignment_frame.toplevel_assignments) == 1: |
| 1378 name = next(iter(assignment_frame.toplevel_assignments)) |
| 1379 self.writeline('context.vars[%r] = l_%s' % (name, name)) |
1344 else: | 1380 else: |
1345 assignment_frame = frame | 1381 self.writeline('context.vars.update({') |
| 1382 for idx, name in enumerate(assignment_frame.toplevel_assignments): |
| 1383 if idx: |
| 1384 self.write(', ') |
| 1385 self.write('%r: l_%s' % (name, name)) |
| 1386 self.write('})') |
| 1387 if public_names: |
| 1388 if len(public_names) == 1: |
| 1389 self.writeline('context.exported_vars.add(%r)' % |
| 1390 public_names[0]) |
| 1391 else: |
| 1392 self.writeline('context.exported_vars.update((%s))' % |
| 1393 ', '.join(imap(repr, public_names))) |
| 1394 |
| 1395 def visit_Assign(self, node, frame): |
| 1396 self.newline(node) |
| 1397 assignment_frame = self.make_assignment_frame(frame) |
1346 self.visit(node.target, assignment_frame) | 1398 self.visit(node.target, assignment_frame) |
1347 self.write(' = ') | 1399 self.write(' = ') |
1348 self.visit(node.node, frame) | 1400 self.visit(node.node, frame) |
| 1401 self.export_assigned_vars(frame, assignment_frame) |
1349 | 1402 |
1350 # make sure toplevel assignments are added to the context. | 1403 def visit_AssignBlock(self, node, frame): |
1351 if frame.toplevel: | 1404 block_frame = frame.inner() |
1352 public_names = [x for x in assignment_frame.toplevel_assignments | 1405 block_frame.inspect(node.body) |
1353 if not x.startswith('_')] | 1406 aliases = self.push_scope(block_frame) |
1354 if len(assignment_frame.toplevel_assignments) == 1: | 1407 self.pull_locals(block_frame) |
1355 name = next(iter(assignment_frame.toplevel_assignments)) | 1408 self.buffer(block_frame) |
1356 self.writeline('context.vars[%r] = l_%s' % (name, name)) | 1409 self.blockvisit(node.body, block_frame) |
1357 else: | 1410 self.pop_scope(aliases, block_frame) |
1358 self.writeline('context.vars.update({') | 1411 |
1359 for idx, name in enumerate(assignment_frame.toplevel_assignments
): | 1412 assignment_frame = self.make_assignment_frame(frame) |
1360 if idx: | 1413 self.newline(node) |
1361 self.write(', ') | 1414 self.visit(node.target, assignment_frame) |
1362 self.write('%r: l_%s' % (name, name)) | 1415 self.write(' = concat(%s)' % block_frame.buffer) |
1363 self.write('})') | 1416 self.export_assigned_vars(frame, assignment_frame) |
1364 if public_names: | |
1365 if len(public_names) == 1: | |
1366 self.writeline('context.exported_vars.add(%r)' % | |
1367 public_names[0]) | |
1368 else: | |
1369 self.writeline('context.exported_vars.update((%s))' % | |
1370 ', '.join(imap(repr, public_names))) | |
1371 | 1417 |
1372 # -- Expression Visitors | 1418 # -- Expression Visitors |
1373 | 1419 |
1374 def visit_Name(self, node, frame): | 1420 def visit_Name(self, node, frame): |
1375 if node.ctx == 'store' and frame.toplevel: | 1421 if node.ctx == 'store' and frame.toplevel: |
1376 frame.toplevel_assignments.add(node.name) | 1422 frame.toplevel_assignments.add(node.name) |
1377 self.write('l_' + node.name) | 1423 self.write('l_' + node.name) |
1378 frame.assigned_names.add(node.name) | 1424 frame.assigned_names.add(node.name) |
1379 | 1425 |
1380 def visit_Const(self, node, frame): | 1426 def visit_Const(self, node, frame): |
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1631 | 1677 |
1632 def visit_ScopedEvalContextModifier(self, node, frame): | 1678 def visit_ScopedEvalContextModifier(self, node, frame): |
1633 old_ctx_name = self.temporary_identifier() | 1679 old_ctx_name = self.temporary_identifier() |
1634 safed_ctx = frame.eval_ctx.save() | 1680 safed_ctx = frame.eval_ctx.save() |
1635 self.writeline('%s = context.eval_ctx.save()' % old_ctx_name) | 1681 self.writeline('%s = context.eval_ctx.save()' % old_ctx_name) |
1636 self.visit_EvalContextModifier(node, frame) | 1682 self.visit_EvalContextModifier(node, frame) |
1637 for child in node.body: | 1683 for child in node.body: |
1638 self.visit(child, frame) | 1684 self.visit(child, frame) |
1639 frame.eval_ctx.revert(safed_ctx) | 1685 frame.eval_ctx.revert(safed_ctx) |
1640 self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name) | 1686 self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name) |
OLD | NEW |