Package gluon :: Module template
[hide private]
[frames] | no frames]

Source Code for Module gluon.template

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework (Copyrighted, 2007-2011). 
  6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  7   
  8  Author: Thadeus Burgess 
  9   
 10  Contributors: 
 11   
 12  - Thank you to Massimo Di Pierro for creating the original gluon/template.py 
 13  - Thank you to Jonathan Lundell for extensively testing the regex on Jython. 
 14  - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. 
 15  """ 
 16   
 17  import os 
 18  import cgi 
 19  import logging 
 20  from re import compile, sub, escape, DOTALL 
 21  try: 
 22      import cStringIO as StringIO 
 23  except: 
 24      from io import StringIO 
 25   
 26  try: 
 27      # have web2py 
 28      from gluon.restricted import RestrictedError 
 29      from gluon.globals import current 
 30  except ImportError: 
 31      # do not have web2py 
 32      current = None 
 33   
34 - def RestrictedError(a, b, c):
35 logging.error(str(a) + ':' + str(b) + ':' + str(c)) 36 return RuntimeError
37 38
39 -class Node(object):
40 """ 41 Basic Container Object 42 """
43 - def __init__(self, value=None, pre_extend=False):
44 self.value = value 45 self.pre_extend = pre_extend
46
47 - def __str__(self):
48 return str(self.value)
49 50
51 -class SuperNode(Node):
52 - def __init__(self, name='', pre_extend=False):
53 self.name = name 54 self.value = None 55 self.pre_extend = pre_extend
56
57 - def __str__(self):
58 if self.value: 59 return str(self.value) 60 else: 61 # raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." ) 62 return ''
63
64 - def __repr__(self):
65 return "%s->%s" % (self.name, self.value)
66 67
68 -def output_aux(node, blocks):
69 # If we have a block level 70 # If we can override this block. 71 # Override block from vars. 72 # Else we take the default 73 # Else its just a string 74 return (blocks[node.name].output(blocks) 75 if node.name in blocks else 76 node.output(blocks)) \ 77 if isinstance(node, BlockNode) \ 78 else str(node)
79 80
81 -class BlockNode(Node):
82 """ 83 Block Container. 84 85 This Node can contain other Nodes and will render in a hierarchical order 86 of when nodes were added. 87 88 ie:: 89 90 {{ block test }} 91 This is default block test 92 {{ end }} 93 """
94 - def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
95 """ 96 name - Name of this Node. 97 """ 98 self.nodes = [] 99 self.name = name 100 self.pre_extend = pre_extend 101 self.left, self.right = delimiters
102
103 - def __repr__(self):
104 lines = ['%sblock %s%s' % (self.left, self.name, self.right)] 105 lines += [str(node) for node in self.nodes] 106 lines.append('%send%s' % (self.left, self.right)) 107 return ''.join(lines)
108
109 - def __str__(self):
110 """ 111 Get this BlockNodes content, not including child Nodes 112 """ 113 return ''.join(str(node) for node in self.nodes 114 if not isinstance(node, BlockNode))
115
116 - def append(self, node):
117 """ 118 Add an element to the nodes. 119 120 Keyword Arguments 121 122 - node -- Node object or string to append. 123 """ 124 if isinstance(node, str) or isinstance(node, Node): 125 self.nodes.append(node) 126 else: 127 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
128
129 - def extend(self, other):
130 """ 131 Extend the list of nodes with another BlockNode class. 132 133 Keyword Arguments 134 135 - other -- BlockNode or Content object to extend from. 136 """ 137 if isinstance(other, BlockNode): 138 self.nodes.extend(other.nodes) 139 else: 140 raise TypeError( 141 "Invalid type; must be instance of ``BlockNode``. %s" % other)
142
143 - def output(self, blocks):
144 """ 145 Merges all nodes into a single string. 146 blocks -- Dictionary of blocks that are extending 147 from this template. 148 """ 149 return ''.join(output_aux(node, blocks) for node in self.nodes)
150 151
152 -class Content(BlockNode):
153 """ 154 Parent Container -- Used as the root level BlockNode. 155 156 Contains functions that operate as such. 157 """
158 - def __init__(self, name="ContentBlock", pre_extend=False):
159 """ 160 Keyword Arguments 161 162 name -- Unique name for this BlockNode 163 """ 164 self.name = name 165 self.nodes = [] 166 self.blocks = {} 167 self.pre_extend = pre_extend
168
169 - def __str__(self):
170 return ''.join(output_aux(node, self.blocks) for node in self.nodes)
171
172 - def _insert(self, other, index=0):
173 """ 174 Inserts object at index. 175 """ 176 if isinstance(other, (str, Node)): 177 self.nodes.insert(index, other) 178 else: 179 raise TypeError( 180 "Invalid type, must be instance of ``str`` or ``Node``.")
181
182 - def insert(self, other, index=0):
183 """ 184 Inserts object at index. 185 186 You may pass a list of objects and have them inserted. 187 """ 188 if isinstance(other, (list, tuple)): 189 # Must reverse so the order stays the same. 190 other.reverse() 191 for item in other: 192 self._insert(item, index) 193 else: 194 self._insert(other, index)
195
196 - def append(self, node):
197 """ 198 Adds a node to list. If it is a BlockNode then we assign a block for it. 199 """ 200 if isinstance(node, (str, Node)): 201 self.nodes.append(node) 202 if isinstance(node, BlockNode): 203 self.blocks[node.name] = node 204 else: 205 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
206
207 - def extend(self, other):
208 """ 209 Extends the objects list of nodes with another objects nodes 210 """ 211 if isinstance(other, BlockNode): 212 self.nodes.extend(other.nodes) 213 self.blocks.update(other.blocks) 214 else: 215 raise TypeError( 216 "Invalid type; must be instance of ``BlockNode``. %s" % other)
217
218 - def clear_content(self):
219 self.nodes = []
220 221
222 -class TemplateParser(object):
223 224 default_delimiters = ('{{', '}}') 225 r_tag = compile(r'(\{\{.*?\}\})', DOTALL) 226 227 r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL) 228 229 # These are used for re-indentation. 230 # Indent + 1 231 re_block = compile('^(elif |else:|except:|except |finally:).*$', DOTALL) 232 233 # Indent - 1 234 re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL) 235 # Indent - 1 236 re_pass = compile('^pass( .*)?$', DOTALL) 237
238 - def __init__(self, text, 239 name="ParserContainer", 240 context=dict(), 241 path='views/', 242 writer='response.write', 243 lexers={}, 244 delimiters=('{{', '}}'), 245 _super_nodes = [], 246 ):
247 """ 248 text -- text to parse 249 context -- context to parse in 250 path -- folder path to templates 251 writer -- string of writer class to use 252 lexers -- dict of custom lexers to use. 253 delimiters -- for example ('{{','}}') 254 _super_nodes -- a list of nodes to check for inclusion 255 this should only be set by "self.extend" 256 It contains a list of SuperNodes from a child 257 template that need to be handled. 258 """ 259 260 # Keep a root level name. 261 self.name = name 262 # Raw text to start parsing. 263 self.text = text 264 # Writer to use (refer to the default for an example). 265 # This will end up as 266 # "%s(%s, escape=False)" % (self.writer, value) 267 self.writer = writer 268 269 # Dictionary of custom name lexers to use. 270 if isinstance(lexers, dict): 271 self.lexers = lexers 272 else: 273 self.lexers = {} 274 275 # Path of templates 276 self.path = path 277 # Context for templates. 278 self.context = context 279 280 # allow optional alternative delimiters 281 self.delimiters = delimiters 282 if delimiters != self.default_delimiters: 283 escaped_delimiters = (escape(delimiters[0]), 284 escape(delimiters[1])) 285 self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL) 286 elif hasattr(context.get('response', None), 'delimiters'): 287 if context['response'].delimiters != self.default_delimiters: 288 escaped_delimiters = ( 289 escape(context['response'].delimiters[0]), 290 escape(context['response'].delimiters[1])) 291 self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, 292 DOTALL) 293 294 # Create a root level Content that everything will go into. 295 self.content = Content(name=name) 296 297 # Stack will hold our current stack of nodes. 298 # As we descend into a node, it will be added to the stack 299 # And when we leave, it will be removed from the stack. 300 # self.content should stay on the stack at all times. 301 self.stack = [self.content] 302 303 # This variable will hold a reference to every super block 304 # that we come across in this template. 305 self.super_nodes = [] 306 307 # This variable will hold a reference to the child 308 # super nodes that need handling. 309 self.child_super_nodes = _super_nodes 310 311 # This variable will hold a reference to every block 312 # that we come across in this template 313 self.blocks = {} 314 315 # Begin parsing. 316 self.parse(text)
317
318 - def to_string(self):
319 """ 320 Return the parsed template with correct indentation. 321 322 Used to make it easier to port to python3. 323 """ 324 return self.reindent(str(self.content))
325
326 - def __str__(self):
327 "Make sure str works exactly the same as python 3" 328 return self.to_string()
329
330 - def __unicode__(self):
331 "Make sure str works exactly the same as python 3" 332 return self.to_string()
333
334 - def reindent(self, text):
335 """ 336 Reindents a string of unindented python code. 337 """ 338 339 # Get each of our lines into an array. 340 lines = text.split('\n') 341 342 # Our new lines 343 new_lines = [] 344 345 # Keeps track of how many indents we have. 346 # Used for when we need to drop a level of indentation 347 # only to reindent on the next line. 348 credit = 0 349 350 # Current indentation 351 k = 0 352 353 ################# 354 # THINGS TO KNOW 355 ################# 356 357 # k += 1 means indent 358 # k -= 1 means unindent 359 # credit = 1 means unindent on the next line. 360 361 for raw_line in lines: 362 line = raw_line.strip() 363 364 # ignore empty lines 365 if not line: 366 continue 367 368 # If we have a line that contains python code that 369 # should be unindented for this line of code. 370 # and then reindented for the next line. 371 if TemplateParser.re_block.match(line): 372 k = k + credit - 1 373 374 # We obviously can't have a negative indentation 375 k = max(k, 0) 376 377 # Add the indentation! 378 new_lines.append(' ' * (4 * k) + line) 379 380 # Bank account back to 0 again :( 381 credit = 0 382 383 # If we are a pass block, we obviously de-dent. 384 if TemplateParser.re_pass.match(line): 385 k -= 1 386 387 # If we are any of the following, de-dent. 388 # However, we should stay on the same level 389 # But the line right after us will be de-dented. 390 # So we add one credit to keep us at the level 391 # while moving back one indentation level. 392 if TemplateParser.re_unblock.match(line): 393 credit = 1 394 k -= 1 395 396 # If we are an if statement, a try, or a semi-colon we 397 # probably need to indent the next line. 398 if line.endswith(':') and not line.startswith('#'): 399 k += 1 400 401 # This must come before so that we can raise an error with the 402 # right content. 403 new_text = '\n'.join(new_lines) 404 405 if k > 0: 406 self._raise_error('missing "pass" in view', new_text) 407 elif k < 0: 408 self._raise_error('too many "pass" in view', new_text) 409 410 return new_text
411
412 - def _raise_error(self, message='', text=None):
413 """ 414 Raise an error using itself as the filename and textual content. 415 """ 416 raise RestrictedError(self.name, text or self.text, message)
417
418 - def _get_file_text(self, filename):
419 """ 420 Attempt to open ``filename`` and retrieve its text. 421 422 This will use self.path to search for the file. 423 """ 424 425 # If they didn't specify a filename, how can we find one! 426 if not filename.strip(): 427 self._raise_error('Invalid template filename') 428 429 # Allow Views to include other views dynamically 430 context = self.context 431 if current and not "response" in context: 432 context["response"] = getattr(current, 'response', None) 433 434 # Get the filename; filename looks like ``"template.html"``. 435 # We need to eval to remove the quotes and get the string type. 436 filename = eval(filename, context) 437 438 # Allow empty filename for conditional extend and include directives. 439 if not filename: 440 return '' 441 442 # Get the path of the file on the system. 443 filepath = self.path and os.path.join(self.path, filename) or filename 444 445 # try to read the text. 446 try: 447 fileobj = open(filepath, 'rb') 448 text = fileobj.read() 449 fileobj.close() 450 except IOError: 451 self._raise_error('Unable to open included view file: ' + filepath) 452 453 return text
454
455 - def include(self, content, filename):
456 """ 457 Include ``filename`` here. 458 """ 459 text = self._get_file_text(filename) 460 461 t = TemplateParser(text, 462 name=filename, 463 context=self.context, 464 path=self.path, 465 writer=self.writer, 466 delimiters=self.delimiters) 467 468 content.append(t.content)
469
470 - def extend(self, filename):
471 """ 472 Extend ``filename``. Anything not declared in a block defined by the 473 parent will be placed in the parent templates ``{{include}}`` block. 474 """ 475 # If no filename, create a dummy layout with only an {{include}}. 476 text = self._get_file_text(filename) or '%sinclude%s' % tuple(self.delimiters) 477 478 # Create out nodes list to send to the parent 479 super_nodes = [] 480 # We want to include any non-handled nodes. 481 super_nodes.extend(self.child_super_nodes) 482 # And our nodes as well. 483 super_nodes.extend(self.super_nodes) 484 485 t = TemplateParser(text, 486 name=filename, 487 context=self.context, 488 path=self.path, 489 writer=self.writer, 490 delimiters=self.delimiters, 491 _super_nodes=super_nodes) 492 493 # Make a temporary buffer that is unique for parent 494 # template. 495 buf = BlockNode( 496 name='__include__' + filename, delimiters=self.delimiters) 497 pre = [] 498 499 # Iterate through each of our nodes 500 for node in self.content.nodes: 501 # If a node is a block 502 if isinstance(node, BlockNode): 503 # That happens to be in the parent template 504 if node.name in t.content.blocks: 505 # Do not include it 506 continue 507 508 if isinstance(node, Node): 509 # Or if the node was before the extension 510 # we should not include it 511 if node.pre_extend: 512 pre.append(node) 513 continue 514 515 # Otherwise, it should go int the 516 # Parent templates {{include}} section. 517 buf.append(node) 518 else: 519 buf.append(node) 520 521 # Clear our current nodes. We will be replacing this with 522 # the parent nodes. 523 self.content.nodes = [] 524 525 t_content = t.content 526 527 # Set our include, unique by filename 528 t_content.blocks['__include__' + filename] = buf 529 530 # Make sure our pre_extended nodes go first 531 t_content.insert(pre) 532 533 # Then we extend our blocks 534 t_content.extend(self.content) 535 536 # Work off the parent node. 537 self.content = t_content
538
539 - def parse(self, text):
540 541 # Basically, r_tag.split will split the text into 542 # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' 543 # so if we alternate this variable, we know 544 # what to look for. This is alternate to 545 # line.startswith("{{") 546 in_tag = False 547 extend = None 548 pre_extend = True 549 550 # Use a list to store everything in 551 # This is because later the code will "look ahead" 552 # for missing strings or brackets. 553 ij = self.r_tag.split(text) 554 # j = current index 555 # i = current item 556 stack = self.stack 557 for j in range(len(ij)): 558 i = ij[j] 559 560 if i: 561 if not stack: 562 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') 563 564 # Our current element in the stack. 565 top = stack[-1] 566 567 if in_tag: 568 line = i 569 570 # Get rid of delimiters 571 line = line[len(self.delimiters[0]):-len(self.delimiters[1])].strip() 572 573 # This is bad juju, but let's do it anyway 574 if not line: 575 continue 576 577 # We do not want to replace the newlines in code, 578 # only in block comments. 579 def remove_newline(re_val): 580 # Take the entire match and replace newlines with 581 # escaped newlines. 582 return re_val.group(0).replace('\n', '\\n')
583 584 # Perform block comment escaping. 585 # This performs escaping ON anything 586 # in between """ and """ 587 line = sub(TemplateParser.r_multiline, 588 remove_newline, 589 line) 590 591 if line.startswith('='): 592 # IE: {{=response.title}} 593 name, value = '=', line[1:].strip() 594 else: 595 v = line.split(' ', 1) 596 if len(v) == 1: 597 # Example 598 # {{ include }} 599 # {{ end }} 600 name = v[0] 601 value = '' 602 else: 603 # Example 604 # {{ block pie }} 605 # {{ include "layout.html" }} 606 # {{ for i in range(10): }} 607 name = v[0] 608 value = v[1] 609 610 # This will replace newlines in block comments 611 # with the newline character. This is so that they 612 # retain their formatting, but squish down to one 613 # line in the rendered template. 614 615 # First check if we have any custom lexers 616 if name in self.lexers: 617 # Pass the information to the lexer 618 # and allow it to inject in the environment 619 620 # You can define custom names such as 621 # '{{<<variable}}' which could potentially 622 # write unescaped version of the variable. 623 self.lexers[name](parser=self, 624 value=value, 625 top=top, 626 stack=stack) 627 628 elif name == '=': 629 # So we have a variable to insert into 630 # the template 631 buf = "\n%s(%s)" % (self.writer, value) 632 top.append(Node(buf, pre_extend=pre_extend)) 633 634 elif name == 'block' and not value.startswith('='): 635 # Make a new node with name. 636 node = BlockNode(name=value.strip(), 637 pre_extend=pre_extend, 638 delimiters=self.delimiters) 639 640 # Append this node to our active node 641 top.append(node) 642 643 # Make sure to add the node to the stack. 644 # so anything after this gets added 645 # to this node. This allows us to 646 # "nest" nodes. 647 stack.append(node) 648 649 elif name == 'end' and not value.startswith('='): 650 # We are done with this node. 651 652 # Save an instance of it 653 self.blocks[top.name] = top 654 655 # Pop it. 656 stack.pop() 657 658 elif name == 'super' and not value.startswith('='): 659 # Get our correct target name 660 # If they just called {{super}} without a name 661 # attempt to assume the top blocks name. 662 if value: 663 target_node = value 664 else: 665 target_node = top.name 666 667 # Create a SuperNode instance 668 node = SuperNode(name=target_node, 669 pre_extend=pre_extend) 670 671 # Add this to our list to be taken care of 672 self.super_nodes.append(node) 673 674 # And put in in the tree 675 top.append(node) 676 677 elif name == 'include' and not value.startswith('='): 678 # If we know the target file to include 679 if value: 680 self.include(top, value) 681 682 # Otherwise, make a temporary include node 683 # That the child node will know to hook into. 684 else: 685 include_node = BlockNode( 686 name='__include__' + self.name, 687 pre_extend=pre_extend, 688 delimiters=self.delimiters) 689 top.append(include_node) 690 691 elif name == 'extend' and not value.startswith('='): 692 # We need to extend the following 693 # template. 694 extend = value 695 pre_extend = False 696 697 else: 698 # If we don't know where it belongs 699 # we just add it anyways without formatting. 700 if line and in_tag: 701 702 # Split on the newlines >.< 703 tokens = line.split('\n') 704 705 # We need to look for any instances of 706 # for i in range(10): 707 # = i 708 # pass 709 # So we can properly put a response.write() in place. 710 continuation = False 711 len_parsed = 0 712 for k, token in enumerate(tokens): 713 714 token = tokens[k] = token.strip() 715 len_parsed += len(token) 716 717 if token.startswith('='): 718 if token.endswith('\\'): 719 continuation = True 720 tokens[k] = "\n%s(%s" % ( 721 self.writer, token[1:].strip()) 722 else: 723 tokens[k] = "\n%s(%s)" % ( 724 self.writer, token[1:].strip()) 725 elif continuation: 726 tokens[k] += ')' 727 continuation = False 728 729 buf = "\n%s" % '\n'.join(tokens) 730 top.append(Node(buf, pre_extend=pre_extend)) 731 732 else: 733 # It is HTML so just include it. 734 buf = "\n%s(%r, escape=False)" % (self.writer, i) 735 top.append(Node(buf, pre_extend=pre_extend)) 736 737 # Remember: tag, not tag, tag, not tag 738 in_tag = not in_tag 739 740 # Make a list of items to remove from child 741 to_rm = [] 742 743 # Go through each of the children nodes 744 for node in self.child_super_nodes: 745 # If we declared a block that this node wants to include 746 if node.name in self.blocks: 747 # Go ahead and include it! 748 node.value = self.blocks[node.name] 749 # Since we processed this child, we don't need to 750 # pass it along to the parent 751 to_rm.append(node) 752 753 # Remove some of the processed nodes 754 for node in to_rm: 755 # Since this is a pointer, it works beautifully. 756 # Sometimes I miss C-Style pointers... I want my asterisk... 757 self.child_super_nodes.remove(node) 758 759 # If we need to extend a template. 760 if extend: 761 self.extend(extend)
762 763 # We need this for integration with gluon 764 765
766 -def parse_template(filename, 767 path='views/', 768 context=dict(), 769 lexers={}, 770 delimiters=('{{', '}}') 771 ):
772 """ 773 filename can be a view filename in the views folder or an input stream 774 path is the path of a views folder 775 context is a dictionary of symbols used to render the template 776 """ 777 778 # First, if we have a str try to open the file 779 if isinstance(filename, str): 780 try: 781 fp = open(os.path.join(path, filename), 'rb') 782 text = fp.read() 783 fp.close() 784 except IOError: 785 raise RestrictedError(filename, '', 'Unable to find the file') 786 else: 787 text = filename.read() 788 789 # Use the file contents to get a parsed template and return it. 790 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
791 792
793 -def get_parsed(text):
794 """ 795 Returns the indented python code of text. Useful for unit testing. 796 797 """ 798 return str(TemplateParser(text))
799 800
801 -class DummyResponse():
802 - def __init__(self):
803 self.body = StringIO.StringIO()
804
805 - def write(self, data, escape=True):
806 if not escape: 807 self.body.write(str(data)) 808 elif hasattr(data, 'xml') and callable(data.xml): 809 self.body.write(data.xml()) 810 else: 811 # make it a string 812 if not isinstance(data, (str, unicode)): 813 data = str(data) 814 elif isinstance(data, unicode): 815 data = data.encode('utf8', 'xmlcharrefreplace') 816 data = cgi.escape(data, True).replace("'", "&#x27;") 817 self.body.write(data)
818 819
820 -class NOESCAPE():
821 """ 822 A little helper to avoid escaping. 823 """
824 - def __init__(self, text):
825 self.text = text
826
827 - def xml(self):
828 return self.text
829 830 # And this is a generic render function. 831 # Here for integration with gluon. 832 833
834 -def render(content="hello world", 835 stream=None, 836 filename=None, 837 path=None, 838 context={}, 839 lexers={}, 840 delimiters=('{{', '}}'), 841 writer='response.write' 842 ):
843 """ 844 >>> render() 845 'hello world' 846 >>> render(content='abc') 847 'abc' 848 >>> render(content='abc\'') 849 "abc'" 850 >>> render(content='a"\'bc') 851 'a"\\'bc' 852 >>> render(content='a\\nbc') 853 'a\\nbc' 854 >>> render(content='a"bcd"e') 855 'a"bcd"e' 856 >>> render(content="'''a\\nc'''") 857 "'''a\\nc'''" 858 >>> render(content="'''a\\'c'''") 859 "'''a\'c'''" 860 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) 861 '0<br />1<br />2<br />3<br />4<br />' 862 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) 863 '0<br />1<br />2<br />3<br />4<br />' 864 >>> render(content="{{='''hello\\nworld'''}}") 865 'hello\\nworld' 866 >>> render(content='{{for i in range(3):\\n=i\\npass}}') 867 '012' 868 """ 869 # here to avoid circular Imports 870 try: 871 from globals import Response 872 except ImportError: 873 # Working standalone. Build a mock Response object. 874 Response = DummyResponse 875 876 # Add it to the context so we can use it. 877 if not 'NOESCAPE' in context: 878 context['NOESCAPE'] = NOESCAPE 879 880 # save current response class 881 if context and 'response' in context: 882 old_response_body = context['response'].body 883 context['response'].body = StringIO.StringIO() 884 else: 885 old_response_body = None 886 context['response'] = Response() 887 888 # If we don't have anything to render, why bother? 889 if not content and not stream and not filename: 890 raise SyntaxError("Must specify a stream or filename or content") 891 892 # Here for legacy purposes, probably can be reduced to 893 # something more simple. 894 close_stream = False 895 if not stream: 896 if filename: 897 stream = open(filename, 'rb') 898 close_stream = True 899 elif content: 900 stream = StringIO.StringIO(content) 901 902 # Execute the template. 903 code = str(TemplateParser(stream.read( 904 ), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer)) 905 try: 906 exec(code) in context 907 except Exception: 908 # for i,line in enumerate(code.split('\n')): print i,line 909 raise 910 911 if close_stream: 912 stream.close() 913 914 # Returned the rendered content. 915 text = context['response'].body.getvalue() 916 if old_response_body is not None: 917 context['response'].body = old_response_body 918 return text
919 920 921 if __name__ == '__main__': 922 import doctest 923 doctest.testmod() 924